From d99d9460ea86ba48cd5e6b4eec1efe6682d1b904 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 19:34:50 +0000 Subject: [PATCH] feat(psychometric): restore Driver p.16 TIPREDVARstd v/v=1 on main Restore recover_standardised_time_independent_predictor_variance as the footnote-4 quadratic form of Table 3 TIPREDVAR after strictly positive v. Zero predictor variance fails closed, matching 2017-era solve(sqrt(0)). Distinct named quantities remain distinct when they equal 1. JSS PDF re-opened 2026-09-01T19:20Z. Meredith (1993) and Mislevy (1991) remain unread. --- CHANGELOG.md | 1 + crates/psychometric_core/src/error.rs | 57 ++++ crates/psychometric_core/src/event_time.rs | 277 ++++++++++++++++-- crates/psychometric_core/src/lib.rs | 24 ++ ...multilevel_event_time_recovery_contract.rs | 139 ++++++++- .../scientific_claim_boundary_contract.rs | 99 ++++++- docs/adr/0005-posterior-esem-dsem.md | 1 + .../multilevel-event-time-recovery.md | 4 +- 8 files changed, 566 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 062a69412..4e2bc8f99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,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, Table 3, p. 13 `TIPREDVAR`; p. 16 `TIPREDVARstd`; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-09-01T19:20Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104) scalar standardised time-independent predictor variance on current main after `0ce16e8` dropped the pre-consolidation code while research notes already named the map (register items 87–88). Table 3 names `TIPREDVAR` the lower-triangular `n.TIpred × n.TIpred` Cholesky matrix of time-independent predictors variance/covariance. Table 2 (p. 12) does not name `TIPREDVAR`. Page 16 prints standardised matrices with the suffix `std` when appropriate. The printed example on p. 16 is `discreteDRIFTstd`, not `TIPREDVARstd`. Footnote 4 standardises using only the relevant variance, not the total. The relevant variance for that named predictor correlation is `TIPREDVAR`, not process-dynamics `asymDIFFUSION` and not residual `MANIFESTVAR` `θ`. The 2017-era source forms `TIPREDVARstd` whenever `verbose = TRUE` and `n.TIpred > 0`, as `solve(sqrt(diag(TIPREDVAR) + ridging)) %&% TIPREDVAR`. OpenMx `%&%` is `t(A) %*% B %*% A`. Unlike `TRAITVARstd`, that formation adds `diag(c(ridging), n.TIpred)`. The default `ridging = FALSE` adds 0, not `0.0001`; that ridge is a numerical hack and is not this exact map. The 2017-era source assigns `dimnames(TIPREDVARstd)` to `TIpredNames`; that assignment matches the `n.TIpred × n.TIpred` matrix and is this map. The scalar correlation is `v / v = 1` after strictly positive `TIPREDVAR`. Form strictly positive `v` first, then `1 / √v`, then `(1 / √v) v (1 / √v)`. Unstandardised `TIPREDVAR` is defined for a zero predictor; standardised `TIPREDVAR` is not. Zero `v` makes `solve(sqrt(0))` fail in the 2017-era source and fails closed here. Unlike `TRAITVAR` / `MANIFESTTRAITVAR`, that source does not skip forming `TIPREDVARstd` when `v = 0`. Predictor variance is an event-time structural quantity, so a non-event clock fails closed. `TIPREDVAR` does not require stable `a < 0`. Distinct positive `v` recover the same 1. `θ / θ = 1` is `MANIFESTVARstd` and recovers the same number and remains a distinct named quantity. `(B / a)² v` is §7.2 `addedTIPREDVAR` and is extra process variance, not this correlation. Meredith (1993) remains unread (Unpaywall historically `is_oa: false`; Springer `content/pdf` is an HTML stub). Mislevy (1991, *Psychometrika, 56*, 177–196) remains unread on the same terms (DOI `10.1007/bf02294457`). Still not a Kalman filter, not a matrix `expm`, not ESEM estimation, not DSEM, and not ctsem estimation. - `event_core` adds bounded Allen interval-consistency classification, atomic path-consistency closure, contradiction/resource refusals, and an explicit dependency-error fallback without claiming unrestricted global satisfiability. - `psychometric_core` recovers the Driver, Oud, and Voelkle (2017, Table 2, p. 12 `MANIFESTTRAITVAR`; §7.1, p. 19; p. 16 `MANIFESTTRAITVARstd`; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-27T14:20Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104) scalar standardised manifest-trait variance on current main after `0ce16e8` dropped the pre-consolidation code while research notes already named the map (register items 83–84). Table 2 names `MANIFESTTRAITVAR` `Ψ_τ` the additional time-invariant variance-covariance on the measurement level and sets it `NULL` when there is no manifest trait. Equation 5 writes `Γ ~ N(τ, Ψ)` and names that covariance the manifest traits. Section 7.1 names manifest traits stable individual differences in indicator levels, distinct from process-level `TRAITVAR` `φ_ξ`. Page 16 prints standardised matrices with the suffix `std` when appropriate. The printed example on p. 16 is `discreteDRIFTstd`, not `MANIFESTTRAITVARstd`. Footnote 4 standardises using only the relevant variance, not the total. The relevant variance for that named indicator-level correlation is `MANIFESTTRAITVAR`, not process-level `TRAITVAR` and not residual `MANIFESTVAR` `θ`. The 2017-era source forms `MANIFESTTRAITVARstd` only when `MANIFESTTRAITVAR != 0`, as `solve(sqrt(diag(MANIFESTTRAITVAR) + ridging)) %&% MANIFESTTRAITVAR` when `verbose = TRUE`. OpenMx `%&%` is `t(A) %*% B %*% A`. Unlike `TRAITVARstd`, that formation adds `diag(c(ridging), n.manifest)`. The default `ridging = FALSE` adds 0, not `0.0001`; that ridge is a numerical hack and is not this exact map. The scalar correlation is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR`. Form strictly positive `ψ` first, then `1 / √ψ`, then `(1 / √ψ) ψ (1 / √ψ)`. Unstandardised `MANIFESTTRAITVAR` is defined for a zero trait; standardised `MANIFESTTRAITVAR` is not. Zero `MANIFESTTRAITVAR` skips forming `MANIFESTTRAITVARstd` in the 2017-era source and fails closed here. Indicator-level trait variance is an event-time structural quantity, so a non-event clock fails closed. `MANIFESTTRAITVAR` does not require stable `a < 0`. Distinct positive `ψ` recover the same 1. `trait / trait = 1` is `TRAITVARstd` and recovers the same number and remains a distinct named quantity. `θ` is `MANIFESTVAR` and is measurement error, not this correlation. Meredith (1993) remains unread (web search 2026-08-27T14:20Z: Springer/Cambridge Core paywalled; Unpaywall historically `is_oa: false`; Springer `content/pdf` is an HTML stub). Mislevy (1991, *Psychometrika, 56*, 177–196) remains unread on the same terms (DOI `10.1007/bf02294457`). Still not a Kalman filter, not a matrix `expm`, not ESEM estimation, not DSEM, and not ctsem estimation. diff --git a/crates/psychometric_core/src/error.rs b/crates/psychometric_core/src/error.rs index 4ab2695e0..c89ff37b9 100644 --- a/crates/psychometric_core/src/error.rs +++ b/crates/psychometric_core/src/error.rs @@ -612,6 +612,27 @@ pub enum PsychometricError { /// process-dynamics `asymDIFFUSION`; `TIPREDVARstd` is the /// correlation form of `TIPREDVAR`. StandardisedTimeIndependentPredictorVarianceIsNotStandardisedAsymptoticDiffusion, + /// Driver p. 16 `TIPREDVARstd` was requested with a non-positive + /// `TIPREDVAR`. Unlike `TRAITVAR` / `MANIFESTTRAITVAR`, the + /// 2017-era source still forms `TIPREDVARstd` when + /// `TIPREDVAR == 0`; `solve(sqrt(0))` fails. Footnote 4 + /// standardisation requires strictly positive `TIPREDVAR`. + StandardisedTimeIndependentPredictorVarianceRequiresPositivePredictorVariance, + /// Driver Table 3 unstandardised `TIPREDVAR` was treated as + /// p. 16 `TIPREDVARstd`. Unstandardised predictor variance is + /// defined for a zero predictor; standardised `TIPREDVAR` is + /// not. + UnstandardisedTimeIndependentPredictorVarianceIsNotStandardisedTimeIndependentPredictorVariance, + /// Driver p. 16 `MANIFESTVARstd` was treated as p. 16 + /// `TIPREDVARstd`. Equal numbers when both correlations equal + /// 1 are still distinct named quantities. `TIPREDVAR` is + /// time-independent predictor variance; `MANIFESTVAR` is + /// contemporaneous measurement error. + StandardisedManifestVarianceIsNotStandardisedTimeIndependentPredictorVariance, + /// Driver §7.2 `addedTIPREDVAR` `(B / a)² v` was treated as + /// p. 16 `TIPREDVARstd`. Extra process variance is not the + /// correlation form of `TIPREDVAR`. + AsymptoticTimeIndependentPredictorVarianceIsNotStandardisedTimeIndependentPredictorVariance, /// Driver p. 16 `TRAITVARstd` was requested with a non-positive /// trait variance. The 2017-era source skips forming @@ -1171,6 +1192,18 @@ impl fmt::Display for PsychometricError { Self::StandardisedTimeIndependentPredictorVarianceIsNotStandardisedAsymptoticDiffusion => { "standardised time-independent predictor variance is not standardised asymptotic diffusion" } + Self::StandardisedTimeIndependentPredictorVarianceRequiresPositivePredictorVariance => { + "standardised time-independent predictor variance requires strictly positive time-independent predictor variance" + } + Self::UnstandardisedTimeIndependentPredictorVarianceIsNotStandardisedTimeIndependentPredictorVariance => { + "unstandardised time-independent predictor variance is not standardised time-independent predictor variance" + } + Self::StandardisedManifestVarianceIsNotStandardisedTimeIndependentPredictorVariance => { + "standardised measurement-error variance is not standardised time-independent predictor variance" + } + Self::AsymptoticTimeIndependentPredictorVarianceIsNotStandardisedTimeIndependentPredictorVariance => { + "asymptotic time-independent predictor variance is not standardised time-independent predictor variance" + } Self::StandardisedTraitVarianceRequiresPositiveTraitVariance => { "standardised trait variance requires strictly positive trait variance" } @@ -1979,6 +2012,30 @@ mod tests { ); } + #[test] + fn standardised_time_independent_predictor_variance_boundary_messages_are_stable() { + assert_eq!( + PsychometricError::StandardisedTimeIndependentPredictorVarianceRequiresPositivePredictorVariance + .to_string(), + "standardised time-independent predictor variance requires strictly positive time-independent predictor variance" + ); + assert_eq!( + PsychometricError::UnstandardisedTimeIndependentPredictorVarianceIsNotStandardisedTimeIndependentPredictorVariance + .to_string(), + "unstandardised time-independent predictor variance is not standardised time-independent predictor variance" + ); + assert_eq!( + PsychometricError::StandardisedManifestVarianceIsNotStandardisedTimeIndependentPredictorVariance + .to_string(), + "standardised measurement-error variance is not standardised time-independent predictor variance" + ); + assert_eq!( + PsychometricError::AsymptoticTimeIndependentPredictorVarianceIsNotStandardisedTimeIndependentPredictorVariance + .to_string(), + "asymptotic time-independent predictor variance is not standardised time-independent predictor variance" + ); + } + #[test] fn standardised_trait_variance_boundary_messages_are_stable() { assert_eq!( diff --git a/crates/psychometric_core/src/event_time.rs b/crates/psychometric_core/src/event_time.rs index a29bc5c18..3f31660d2 100644 --- a/crates/psychometric_core/src/event_time.rs +++ b/crates/psychometric_core/src/event_time.rs @@ -1930,10 +1930,11 @@ pub fn refuse_standardised_asymptotic_diffusion_as_standardised_initial_latent_v /// and remains a distinct named quantity. `DIFFUSIONstd` /// `q / p = −2 a` is the continuous-diffusion ratio and is not this /// correlation. `TIPREDVARstd` `v / v = 1` recovers the same number -/// and remains a distinct named quantity. This crate does not -/// currently export `DIFFUSIONstd` or `TIPREDVARstd`; the refuse -/// still names those quantities. This is not a Kalman filter, not a -/// matrix `expm`, not DSEM, and not ctsem estimation. +/// and remains a distinct named quantity. This crate now exports +/// `TIPREDVARstd`. This crate does not currently export +/// `DIFFUSIONstd`; the refuse still names that quantity. This is +/// not a Kalman filter, not a matrix `expm`, not DSEM, and not +/// ctsem estimation. /// Exact scalar p. 16 `TRAITVARstd` after strictly positive `TRAITVAR`. /// /// Driver, Oud, and Voelkle (2017, Table 2, p. 12; §7.1, pp. 18–19; @@ -2125,16 +2126,13 @@ pub fn refuse_standardised_continuous_diffusion_as_standardised_asymptotic_diffu Err(PsychometricError::StandardisedContinuousDiffusionIsNotStandardisedAsymptoticDiffusion) } -/// Refuse treating p. 16 `TIPREDVARstd` as p. 16 -/// `asymDIFFUSIONstd`. +/// Refuse treating p. 16 `T0VARstd` as p. 16 `TRAITVARstd`. /// -/// Both scalar maps equal 1 after a strictly positive relevant -/// variance. `asymDIFFUSIONstd` is the correlation form of -/// process-dynamics `asymDIFFUSION`. `TIPREDVARstd` is the -/// correlation form of `TIPREDVAR`. Equal numbers remain distinct -/// named quantities. This crate does not currently export -/// `TIPREDVARstd`; the refuse still names that quantity. -/// [`PsychometricError::StandardisedInitialLatentVarianceIsNotStandardisedTraitVariance`]. +/// Both scalar correlations equal 1 after strictly positive +/// variances. `TRAITVARstd` is the correlation form of +/// between-subject `TRAITVAR`. `T0VARstd` is the correlation form +/// of free first-occasion `T0VAR`. Equal numbers remain distinct +/// named quantities. This crate already exports `T0VARstd`. /// /// # Errors /// @@ -2147,12 +2145,14 @@ pub fn refuse_standardised_initial_latent_variance_as_standardised_trait_varianc Err(PsychometricError::StandardisedInitialLatentVarianceIsNotStandardisedTraitVariance) } -/// Refuse treating 2017-era `addedT0TIPREDVAR` as p. 16 `TRAITVARstd`. +/// Refuse treating p. 16 `TIPREDVARstd` as p. 16 +/// `asymDIFFUSIONstd`. /// -/// `t0_b² v` is extra first-occasion TI variance. `TRAITVARstd` is -/// the correlation form of between-subject `TRAITVAR`. Those are -/// not the same map. This crate does not currently export -/// `addedT0TIPREDVAR`; the refuse still names that quantity. +/// Both scalar maps equal 1 after a strictly positive relevant +/// variance. `asymDIFFUSIONstd` is the correlation form of +/// process-dynamics `asymDIFFUSION`. `TIPREDVARstd` is the +/// correlation form of `TIPREDVAR`. Equal numbers remain distinct +/// named quantities. This crate now exports `TIPREDVARstd`. /// /// # Errors /// @@ -2171,6 +2171,154 @@ pub fn refuse_standardised_time_independent_predictor_variance_as_standardised_a ) } +/// Exact scalar p. 16 `TIPREDVARstd` after strictly positive +/// `TIPREDVAR`. +/// +/// Driver, Oud, and Voelkle (2017, Table 3, p. 13; p. 16; footnote +/// 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened +/// 2026-09-01T19:20Z from +/// ) +/// name `TIPREDVAR` the lower-triangular `n.TIpred × n.TIpred` +/// Cholesky matrix of time-independent predictors +/// variance/covariance. Table 2 (p. 12) does not name `TIPREDVAR`. +/// Page 16 prints standardised matrices with the suffix `std` when +/// appropriate. The printed example on p. 16 is `discreteDRIFTstd`, +/// not `TIPREDVARstd`. Footnote 4: standardisations use only the +/// relevant variance, not the total. The relevant variance for that +/// named predictor correlation is `TIPREDVAR`, not process-dynamics +/// `asymDIFFUSION` and not residual `MANIFESTVAR` `θ`. The +/// 2017-era `summary.ctsemFit.R` forms `TIPREDVARstd` whenever +/// `verbose = TRUE` and `n.TIpred > 0`, as +/// `solve(sqrt(diag(TIPREDVAR) + ridging)) %&% TIPREDVAR`. `OpenMx` +/// `%&%` is the quadratic form `t(A) %*% B %*% A`. Unlike +/// `TRAITVARstd`, that formation adds `diag(c(ridging), n.TIpred)`. +/// The default `ridging = FALSE` adds 0, not `0.0001`; that ridge +/// is a numerical hack and is not this exact map. The 2017-era +/// source assigns `dimnames(TIPREDVARstd)` to `TIpredNames`; that +/// assignment matches the `n.TIpred × n.TIpred` matrix and is this +/// map. The scalar correlation is `v / v = 1` after strictly +/// positive `TIPREDVAR`. Form strictly positive `v` first, then +/// `1 / √v`, then `(1 / √v) v (1 / √v)`. Unstandardised +/// `TIPREDVAR` is defined for a zero predictor; standardised +/// `TIPREDVAR` is not. Zero `v` makes `solve(sqrt(0))` fail in the +/// 2017-era source and fails closed here. Unlike `TRAITVAR` / +/// `MANIFESTTRAITVAR`, that source does not skip forming +/// `TIPREDVARstd` when `v = 0`; the quadratic still fails. +/// Predictor variance is an event-time structural quantity, so a +/// non-event clock fails closed. `TIPREDVAR` does not require +/// stable `a < 0`. Distinct positive `v` recover the same 1. +/// `MANIFESTVARstd` `θ / θ = 1` recovers the same number and +/// remains a distinct named quantity. This crate already exports +/// `MANIFESTVARstd`. Section 7.2 `addedTIPREDVAR` `(B / a)² v` is +/// extra process variance, not this correlation. This is not a +/// Kalman filter, not a matrix `expm`, not DSEM, and not ctsem +/// estimation. +/// +/// # Errors +/// +/// Returns [`PsychometricError::EventTimeRequired`] for any +/// non-event clock, +/// [`PsychometricError::StandardisedTimeIndependentPredictorVarianceRequiresPositivePredictorVariance`] +/// when `TIPREDVAR` is zero, and +/// [`PsychometricError::InvalidNumericInput`] when the variance is +/// non-finite, negative, or the quadratic form overflows. +pub fn recover_standardised_time_independent_predictor_variance( + time_independent_predictor_variance: f64, + clock: LagClock, +) -> Result { + if !clock.admits_structural_lag() { + return Err(PsychometricError::EventTimeRequired); + } + if !time_independent_predictor_variance.is_finite() || time_independent_predictor_variance < 0.0 + { + return Err(PsychometricError::InvalidNumericInput); + } + if time_independent_predictor_variance == 0.0 { + return Err( + PsychometricError::StandardisedTimeIndependentPredictorVarianceRequiresPositivePredictorVariance, + ); + } + let predictor_sd = time_independent_predictor_variance.sqrt(); + let inverse_sd = require_finite(1.0 / predictor_sd)?; + let scaled = require_finite(inverse_sd * time_independent_predictor_variance)?; + require_finite(scaled * inverse_sd) +} + +/// Refuse treating unstandardised `TIPREDVAR` as p. 16 +/// `TIPREDVARstd`. +/// +/// Unstandardised `v` is defined for a zero predictor. Footnote 4 +/// `TIPREDVARstd` requires strictly positive `TIPREDVAR`. Equal +/// numbers when `v = 1` are still distinct named quantities. +/// +/// # Errors +/// +/// Always returns +/// [`PsychometricError::UnstandardisedTimeIndependentPredictorVarianceIsNotStandardisedTimeIndependentPredictorVariance`]. +pub fn refuse_unstandardised_time_independent_predictor_variance_as_standardised_time_independent_predictor_variance( + unstandardised_predictor_variance: f64, + standardised_predictor_variance: f64, +) -> Result { + let _ = ( + unstandardised_predictor_variance, + standardised_predictor_variance, + ); + Err( + PsychometricError::UnstandardisedTimeIndependentPredictorVarianceIsNotStandardisedTimeIndependentPredictorVariance, + ) +} + +/// Refuse treating p. 16 `MANIFESTVARstd` as p. 16 `TIPREDVARstd`. +/// +/// Both scalar correlations equal 1 after strictly positive +/// variances. `MANIFESTVARstd` standardises contemporaneous +/// measurement error `Θ`. `TIPREDVARstd` standardises +/// time-independent predictor variance `TIPREDVAR`. Equal numbers +/// remain distinct named quantities. This crate already exports +/// `MANIFESTVARstd`. +/// +/// # Errors +/// +/// Always returns +/// [`PsychometricError::StandardisedManifestVarianceIsNotStandardisedTimeIndependentPredictorVariance`]. +pub fn refuse_standardised_manifest_variance_as_standardised_time_independent_predictor_variance( + standardised_manifest_variance: f64, + standardised_predictor_variance: f64, +) -> Result { + let _ = ( + standardised_manifest_variance, + standardised_predictor_variance, + ); + Err( + PsychometricError::StandardisedManifestVarianceIsNotStandardisedTimeIndependentPredictorVariance, + ) +} + +/// Refuse treating §7.2 `addedTIPREDVAR` as p. 16 `TIPREDVARstd`. +/// +/// `(B / a)² v` is extra process variance accounted for by a +/// time-independent predictor. `TIPREDVARstd` is the correlation +/// form of `TIPREDVAR`. Those are not the same map. This crate +/// already exports `addedTIPREDVAR` as +/// [`recover_asymptotic_time_independent_predictor_variance`]. +/// +/// # Errors +/// +/// Always returns +/// [`PsychometricError::AsymptoticTimeIndependentPredictorVarianceIsNotStandardisedTimeIndependentPredictorVariance`]. +pub fn refuse_asymptotic_time_independent_predictor_variance_as_standardised_time_independent_predictor_variance( + asymptotic_predictor_variance: f64, + standardised_predictor_variance: f64, +) -> Result { + let _ = ( + asymptotic_predictor_variance, + standardised_predictor_variance, + ); + Err( + PsychometricError::AsymptoticTimeIndependentPredictorVarianceIsNotStandardisedTimeIndependentPredictorVariance, + ) +} + /// Exact scalar p. 16 `MANIFESTTRAITVARstd` after strictly positive /// `MANIFESTTRAITVAR`. /// @@ -6891,6 +7039,8 @@ mod tests { recover_standardised_discrete_continuous_intercept, recover_standardised_initial_latent_mean, recover_standardised_initial_latent_variance, recover_standardised_manifest_mean, recover_standardised_manifest_trait_variance, + recover_standardised_manifest_variance, + recover_standardised_time_independent_predictor_variance, recover_standardised_trait_variance, recover_stationary_initial_latent_mean, recover_stationary_initial_latent_variance, recover_stationary_initial_observed_mean, recover_stationary_initial_observed_variance, recover_stationary_lagged_latent_covariance, @@ -6912,6 +7062,7 @@ mod tests { refuse_asymptotic_time_independent_effect_as_continuous_intercept, refuse_asymptotic_time_independent_effect_as_discrete_effect, refuse_asymptotic_time_independent_effect_as_time_dependent_impulse, + refuse_asymptotic_time_independent_predictor_variance_as_standardised_time_independent_predictor_variance, refuse_asymptotic_time_independent_variance_as_asymptotic_effect, refuse_asymptotic_time_independent_variance_as_stationary_within_subject, refuse_asymptotic_time_independent_variance_as_trait_variance, @@ -6988,6 +7139,7 @@ mod tests { refuse_standardised_initial_latent_variance_as_standardised_initial_latent_mean, refuse_standardised_initial_latent_variance_as_standardised_trait_variance, refuse_standardised_manifest_variance_as_standardised_manifest_mean, + refuse_standardised_manifest_variance_as_standardised_time_independent_predictor_variance, refuse_standardised_time_independent_predictor_variance_as_standardised_asymptotic_diffusion, refuse_standardised_trait_variance_as_standardised_manifest_trait_variance, refuse_stationary_initial_latent_mean_as_asymptotic_continuous_intercept, @@ -7038,6 +7190,7 @@ mod tests { refuse_unstandardised_initial_latent_variance_as_standardised_initial_latent_variance, refuse_unstandardised_manifest_mean_as_standardised_manifest_mean, refuse_unstandardised_manifest_trait_variance_as_standardised_manifest_trait_variance, + refuse_unstandardised_time_independent_predictor_variance_as_standardised_time_independent_predictor_variance, refuse_unstandardised_trait_variance_as_standardised_trait_variance, refuse_within_subject_scaled_initial_latent_mean_as_standardised_initial_latent_mean, }; @@ -16105,6 +16258,94 @@ mod tests { ); } + #[test] + fn standardised_time_independent_predictor_variance_recovers_driver_table_three_after_positive_v() + { + // Driver et al. (2017, Table 3 TIPREDVAR; p. 16 TIPREDVARstd; + // footnote 4; 2017-era summary.ctsemFit.R): form strictly + // positive TIPREDVAR, then (1/√v) v (1/√v) = 1. Default ridge + // is 0. JSS PDF re-opened 2026-09-01T19:20Z. + let predictor_variance = 1.6_f64; + let recovered = recover_standardised_time_independent_predictor_variance( + predictor_variance, + LagClock::EventTime, + ) + .expect("TIPREDVARstd"); + assert!((recovered - 1.0).abs() < 1e-15); + let larger_v = + recover_standardised_time_independent_predictor_variance(6.4, LagClock::EventTime) + .expect("TIPREDVARstd v=6.4"); + assert!((larger_v - recovered).abs() < 1e-15); + let manifest_std = recover_standardised_manifest_variance(0.4, LagClock::EventTime) + .expect("MANIFESTVARstd"); + assert!((manifest_std - recovered).abs() < 1e-15); + let added = recover_asymptotic_time_independent_predictor_variance( + 0.4, + predictor_variance, + -0.5, + LagClock::EventTime, + ) + .expect("addedTIPREDVAR"); + assert!((added - recovered).abs() > 1e-3); + assert_eq!( + refuse_unstandardised_time_independent_predictor_variance_as_standardised_time_independent_predictor_variance( + predictor_variance, + recovered + ), + Err( + PsychometricError::UnstandardisedTimeIndependentPredictorVarianceIsNotStandardisedTimeIndependentPredictorVariance + ) + ); + assert_eq!( + refuse_standardised_manifest_variance_as_standardised_time_independent_predictor_variance( + manifest_std, + recovered + ), + Err( + PsychometricError::StandardisedManifestVarianceIsNotStandardisedTimeIndependentPredictorVariance + ) + ); + assert_eq!( + refuse_asymptotic_time_independent_predictor_variance_as_standardised_time_independent_predictor_variance( + added, + recovered + ), + Err( + PsychometricError::AsymptoticTimeIndependentPredictorVarianceIsNotStandardisedTimeIndependentPredictorVariance + ) + ); + } + + #[test] + fn standardised_time_independent_predictor_variance_fails_closed_when_unstandardised_is_defined() + { + assert_eq!( + recover_standardised_time_independent_predictor_variance(0.0, LagClock::EventTime), + Err( + PsychometricError::StandardisedTimeIndependentPredictorVarianceRequiresPositivePredictorVariance + ) + ); + assert_eq!( + recover_standardised_time_independent_predictor_variance(1.6, LagClock::SystemTime), + Err(PsychometricError::EventTimeRequired) + ); + assert_eq!( + recover_standardised_time_independent_predictor_variance(-1.6, LagClock::EventTime), + Err(PsychometricError::InvalidNumericInput) + ); + assert_eq!( + recover_standardised_time_independent_predictor_variance(f64::NAN, LagClock::EventTime), + Err(PsychometricError::InvalidNumericInput) + ); + assert_eq!( + recover_standardised_time_independent_predictor_variance( + f64::INFINITY, + LagClock::EventTime + ), + Err(PsychometricError::InvalidNumericInput) + ); + } + #[test] fn standardised_trait_variance_fails_closed_when_unstandardised_is_defined() { diff --git a/crates/psychometric_core/src/lib.rs b/crates/psychometric_core/src/lib.rs index c081a63f6..0b13ea65c 100644 --- a/crates/psychometric_core/src/lib.rs +++ b/crates/psychometric_core/src/lib.rs @@ -261,6 +261,22 @@ //! correlation; zero `MANIFESTTRAITVAR` fails closed; a non-event //! clock fails closed; `MANIFESTTRAITVAR` does not require `a < 0`; //! JSS PDF re-opened 2026-08-27T14:20Z), +//! recovers the Driver p. 16 `TIPREDVARstd` as `v / v = 1` after +//! strictly positive `TIPREDVAR` (Table 3 names `TIPREDVAR` the +//! `n.TIpred × n.TIpred` Cholesky of time-independent predictors; +//! Table 2 does not name `TIPREDVAR`; 2017-era +//! `summary.ctsemFit.R` forms `TIPREDVARstd` whenever +//! `verbose = TRUE` and `n.TIpred > 0` as +//! `solve(sqrt(diag(TIPREDVAR) + ridging)) %&% TIPREDVAR`; unlike +//! `TRAITVARstd` that formation adds ridging; the default ridge is +//! 0; unlike `TRAITVAR` / `MANIFESTTRAITVAR` that source does not +//! skip forming `TIPREDVARstd` when `v = 0`; unstandardised +//! `TIPREDVAR` is defined for a zero predictor and is not that map; +//! `θ / θ = 1` is `MANIFESTVARstd` and is not that map even when +//! both equal 1; `(B / a)² v` is `addedTIPREDVAR` and is not that +//! correlation; zero `TIPREDVAR` fails closed; a non-event clock +//! fails closed; `TIPREDVAR` does not require `a < 0`; JSS PDF +//! re-opened 2026-09-01T19:20Z), //! and refuses //! latent-mean comparison below strong invariance. @@ -417,6 +433,8 @@ pub use event_time::recover_standardised_manifest_mean; pub use event_time::recover_standardised_manifest_trait_variance; /// Exact scalar p. 16 `MANIFESTVARstd` `θ/...` after strictly positive `MANIFESTVAR`. pub use event_time::recover_standardised_manifest_variance; +/// Exact scalar p. 16 `TIPREDVARstd` `v / v = 1` after strictly positive `TIPREDVAR`. +pub use event_time::recover_standardised_time_independent_predictor_variance; /// Exact scalar p. 16 `TRAITVARstd` `trait / trait = 1` after strictly positive `TRAITVAR`. pub use event_time::recover_standardised_trait_variance; @@ -637,8 +655,12 @@ pub use event_time::refuse_standardised_manifest_trait_variance_as_standardised_ /// Refuse treating `MANIFESTVARstd` as `MANIFESTMEANSstd`. pub use event_time::refuse_standardised_manifest_variance_as_standardised_manifest_mean; +/// Refuse treating §7.2 `addedTIPREDVAR` as p. 16 `TIPREDVARstd`. +pub use event_time::refuse_asymptotic_time_independent_predictor_variance_as_standardised_time_independent_predictor_variance; /// Refuse treating observed θ as p. 16 `MANIFESTVARstd`. pub use event_time::refuse_observed_variance_as_standardised_manifest_variance; +/// Refuse treating p. 16 `MANIFESTVARstd` as `TIPREDVARstd`. +pub use event_time::refuse_standardised_manifest_variance_as_standardised_time_independent_predictor_variance; /// Refuse treating p. 16 `TIPREDVARstd` as `asymDIFFUSIONstd`. pub use event_time::refuse_standardised_time_independent_predictor_variance_as_standardised_asymptotic_diffusion; /// Refuse treating p. 16 `TRAITVARstd` as `MANIFESTTRAITVARstd`. @@ -743,6 +765,8 @@ pub use event_time::refuse_unstandardised_manifest_mean_as_standardised_manifest pub use event_time::refuse_unstandardised_manifest_trait_variance_as_standardised_manifest_trait_variance; /// Refuse treating unstandardised `MANIFESTVAR` as p. 16 `MANIFESTVARstd`. pub use event_time::refuse_unstandardised_manifest_variance_as_standardised_manifest_variance; +/// Refuse treating unstandardised `TIPREDVAR` as p. 16 `TIPREDVARstd`. +pub use event_time::refuse_unstandardised_time_independent_predictor_variance_as_standardised_time_independent_predictor_variance; /// Refuse treating unstandardised `TRAITVAR` as p. 16 `TRAITVARstd`. pub use event_time::refuse_unstandardised_trait_variance_as_standardised_trait_variance; diff --git a/crates/psychometric_core/tests/multilevel_event_time_recovery_contract.rs b/crates/psychometric_core/tests/multilevel_event_time_recovery_contract.rs index 1c0027f44..af28d6914 100644 --- a/crates/psychometric_core/tests/multilevel_event_time_recovery_contract.rs +++ b/crates/psychometric_core/tests/multilevel_event_time_recovery_contract.rs @@ -39,14 +39,14 @@ use psychometric_core::{ recover_standardised_discrete_continuous_intercept, recover_standardised_initial_latent_mean, recover_standardised_initial_latent_variance, recover_standardised_manifest_mean, recover_standardised_manifest_trait_variance, recover_standardised_manifest_variance, - recover_standardised_trait_variance, recover_stationary_initial_latent_mean, - recover_stationary_initial_latent_variance, recover_stationary_initial_observed_mean, - recover_stationary_initial_observed_variance, recover_stationary_lagged_latent_covariance, - recover_stationary_lagged_observed_covariance, recover_stationary_latent_variance, - recover_stationary_later_latent_variance, recover_stationary_later_observed_variance, - recover_time_dependent_predictor_impulse, recover_time_dependent_predictor_impulse_carry, - recover_trait_plus_state_lagged_covariance, recover_trait_plus_state_latent_variance, - recover_within_residual_event_time_log_rate, + recover_standardised_time_independent_predictor_variance, recover_standardised_trait_variance, + recover_stationary_initial_latent_mean, recover_stationary_initial_latent_variance, + recover_stationary_initial_observed_mean, recover_stationary_initial_observed_variance, + recover_stationary_lagged_latent_covariance, recover_stationary_lagged_observed_covariance, + recover_stationary_latent_variance, recover_stationary_later_latent_variance, + recover_stationary_later_observed_variance, recover_time_dependent_predictor_impulse, + recover_time_dependent_predictor_impulse_carry, recover_trait_plus_state_lagged_covariance, + recover_trait_plus_state_latent_variance, recover_within_residual_event_time_log_rate, refuse_after_extra_process_contribution_as_observed_mean, refuse_after_extra_process_latent_mean_as_observed_mean, refuse_asymptotic_continuous_intercept_as_asymptotic_time_independent_effect, @@ -58,6 +58,7 @@ use psychometric_core::{ refuse_asymptotic_time_independent_effect_as_continuous_intercept, refuse_asymptotic_time_independent_effect_as_discrete_effect, refuse_asymptotic_time_independent_effect_as_time_dependent_impulse, + refuse_asymptotic_time_independent_predictor_variance_as_standardised_time_independent_predictor_variance, refuse_asymptotic_time_independent_variance_as_asymptotic_effect, refuse_asymptotic_time_independent_variance_as_stationary_within_subject, refuse_asymptotic_time_independent_variance_as_trait_variance, @@ -122,6 +123,7 @@ use psychometric_core::{ refuse_process_noise_as_unconditional_variance, refuse_standardised_initial_latent_variance_as_standardised_trait_variance, refuse_standardised_manifest_trait_variance_as_standardised_manifest_variance, + refuse_standardised_manifest_variance_as_standardised_time_independent_predictor_variance, refuse_standardised_trait_variance_as_standardised_manifest_trait_variance, refuse_stationary_initial_latent_mean_as_asymptotic_continuous_intercept, refuse_stationary_initial_latent_mean_as_asymptotic_time_independent_effect, @@ -164,6 +166,7 @@ use psychometric_core::{ refuse_unmatched_time_varying_predictor_interval, refuse_unstandardised_manifest_trait_variance_as_standardised_manifest_trait_variance, refuse_unstandardised_manifest_variance_as_standardised_manifest_variance, + refuse_unstandardised_time_independent_predictor_variance_as_standardised_time_independent_predictor_variance, refuse_unstandardised_trait_variance_as_standardised_trait_variance, }; @@ -6540,3 +6543,123 @@ fn manifest_variance_std_clock_path_is_runtime_opaque() { Err(PsychometricError::EventTimeRequired) ); } + +#[test] +fn standardised_time_independent_predictor_variance_recovers_driver_table_three_correlation() { + let predictor_variance = 0.4_f64; + let recovered = recover_standardised_time_independent_predictor_variance( + predictor_variance, + LagClock::EventTime, + ) + .expect("TIPREDVARstd"); + let recovered_error = (recovered - 1.0).abs(); + assert!( + recovered_error < 1e-15, + "Driver et al. (2017, p. 16 TIPREDVARstd): RMSE {recovered_error} for v / v = 1" + ); + let larger_v = + recover_standardised_time_independent_predictor_variance(1.6, LagClock::EventTime) + .expect("TIPREDVARstd v=1.6"); + assert_eq!( + larger_v.to_bits(), + recovered.to_bits(), + "Driver et al. (2017, p. 16): distinct positive TIPREDVAR recover the same TIPREDVARstd" + ); + let manifest_std = + recover_standardised_manifest_variance(0.4, LagClock::EventTime).expect("MANIFESTVARstd"); + assert!( + (manifest_std - recovered).abs() < 1e-15, + "Driver et al. (2017, p. 16): MANIFESTVARstd equals 1 after strictly positive MANIFESTVAR" + ); + let added = recover_asymptotic_time_independent_predictor_variance( + 0.4, + predictor_variance, + -0.5, + LagClock::EventTime, + ) + .expect("addedTIPREDVAR"); + let added_error = (added - 1.0).abs(); + assert!( + recovered_error < added_error, + "Driver et al. (2017, §7.2): addedTIPREDVAR RMSE {added_error} must exceed TIPREDVARstd RMSE {recovered_error}" + ); + let unstandardised_error = (predictor_variance - 1.0).abs(); + assert!( + recovered_error < unstandardised_error, + "Driver et al. (2017, Table 3): unstandardised TIPREDVAR RMSE {unstandardised_error} must exceed TIPREDVARstd RMSE {recovered_error}" + ); + assert_eq!( + refuse_unstandardised_time_independent_predictor_variance_as_standardised_time_independent_predictor_variance( + predictor_variance, + recovered + ), + Err(PsychometricError::UnstandardisedTimeIndependentPredictorVarianceIsNotStandardisedTimeIndependentPredictorVariance) + ); + assert_eq!( + refuse_standardised_manifest_variance_as_standardised_time_independent_predictor_variance( + manifest_std, + recovered + ), + Err(PsychometricError::StandardisedManifestVarianceIsNotStandardisedTimeIndependentPredictorVariance) + ); + assert_eq!( + refuse_asymptotic_time_independent_predictor_variance_as_standardised_time_independent_predictor_variance( + added, + recovered + ), + Err(PsychometricError::AsymptoticTimeIndependentPredictorVarianceIsNotStandardisedTimeIndependentPredictorVariance) + ); + assert_eq!( + recover_standardised_time_independent_predictor_variance(0.4, LagClock::SystemTime), + Err(PsychometricError::EventTimeRequired) + ); +} + +#[test] +fn standardised_time_independent_predictor_variance_refuses_non_event_clocks_and_does_not_keep_zero_variance() + { + assert_eq!( + recover_standardised_time_independent_predictor_variance(0.4, LagClock::AssertionTime), + Err(PsychometricError::EventTimeRequired) + ); + assert_eq!( + recover_standardised_time_independent_predictor_variance(0.4, LagClock::KnowledgeCutoff), + Err(PsychometricError::EventTimeRequired) + ); + assert_eq!( + recover_standardised_time_independent_predictor_variance(-0.4, LagClock::EventTime), + Err(PsychometricError::InvalidNumericInput) + ); + assert_eq!( + recover_standardised_time_independent_predictor_variance(f64::NAN, LagClock::EventTime), + Err(PsychometricError::InvalidNumericInput) + ); + assert_eq!( + recover_standardised_time_independent_predictor_variance( + f64::INFINITY, + LagClock::EventTime + ), + Err(PsychometricError::InvalidNumericInput) + ); + assert_eq!( + recover_standardised_time_independent_predictor_variance(0.0, LagClock::EventTime), + Err( + PsychometricError::StandardisedTimeIndependentPredictorVarianceRequiresPositivePredictorVariance + ) + ); +} + +#[test] +fn time_independent_predictor_variance_std_clock_path_is_runtime_opaque() { + let clocks = [ + LagClock::SystemTime, + LagClock::DocumentTime, + LagClock::AssertionTime, + ]; + let non_event_index = std::process::id() as usize % clocks.len(); + let non_event = clocks[non_event_index]; + assert_eq!( + recover_standardised_time_independent_predictor_variance(0.4, non_event), + Err(PsychometricError::EventTimeRequired) + ); +} diff --git a/crates/psychometric_core/tests/scientific_claim_boundary_contract.rs b/crates/psychometric_core/tests/scientific_claim_boundary_contract.rs index 6ccf7f38b..c065b0108 100644 --- a/crates/psychometric_core/tests/scientific_claim_boundary_contract.rs +++ b/crates/psychometric_core/tests/scientific_claim_boundary_contract.rs @@ -34,14 +34,14 @@ use psychometric_core::{ recover_standardised_discrete_continuous_intercept, recover_standardised_initial_latent_mean, recover_standardised_initial_latent_variance, recover_standardised_manifest_mean, recover_standardised_manifest_trait_variance, recover_standardised_manifest_variance, - recover_standardised_trait_variance, recover_stationary_initial_latent_mean, - recover_stationary_initial_latent_variance, recover_stationary_initial_observed_mean, - recover_stationary_initial_observed_variance, recover_stationary_lagged_latent_covariance, - recover_stationary_lagged_observed_covariance, recover_stationary_latent_variance, - recover_stationary_later_latent_variance, recover_stationary_later_observed_variance, - recover_time_dependent_predictor_impulse, recover_time_dependent_predictor_impulse_carry, - recover_trait_plus_state_lagged_covariance, recover_trait_plus_state_latent_variance, - recover_within_residual_event_time_log_rate, + recover_standardised_time_independent_predictor_variance, recover_standardised_trait_variance, + recover_stationary_initial_latent_mean, recover_stationary_initial_latent_variance, + recover_stationary_initial_observed_mean, recover_stationary_initial_observed_variance, + recover_stationary_lagged_latent_covariance, recover_stationary_lagged_observed_covariance, + recover_stationary_latent_variance, recover_stationary_later_latent_variance, + recover_stationary_later_observed_variance, recover_time_dependent_predictor_impulse, + recover_time_dependent_predictor_impulse_carry, recover_trait_plus_state_lagged_covariance, + recover_trait_plus_state_latent_variance, recover_within_residual_event_time_log_rate, refuse_after_extra_process_contribution_as_observed_mean, refuse_after_extra_process_latent_mean_as_observed_mean, refuse_asymptotic_continuous_intercept_as_asymptotic_time_independent_effect, @@ -55,6 +55,7 @@ use psychometric_core::{ refuse_asymptotic_time_independent_effect_as_continuous_intercept, refuse_asymptotic_time_independent_effect_as_discrete_effect, refuse_asymptotic_time_independent_effect_as_time_dependent_impulse, + refuse_asymptotic_time_independent_predictor_variance_as_standardised_time_independent_predictor_variance, refuse_asymptotic_time_independent_variance_as_asymptotic_effect, refuse_asymptotic_time_independent_variance_as_stationary_within_subject, refuse_asymptotic_time_independent_variance_as_trait_variance, @@ -129,6 +130,7 @@ use psychometric_core::{ refuse_standardised_initial_latent_variance_as_standardised_trait_variance, refuse_standardised_manifest_trait_variance_as_standardised_manifest_variance, refuse_standardised_manifest_variance_as_standardised_manifest_mean, + refuse_standardised_manifest_variance_as_standardised_time_independent_predictor_variance, refuse_standardised_time_independent_predictor_variance_as_standardised_asymptotic_diffusion, refuse_standardised_trait_variance_as_standardised_manifest_trait_variance, refuse_stationary_initial_latent_mean_as_asymptotic_continuous_intercept, @@ -179,6 +181,7 @@ use psychometric_core::{ refuse_unstandardised_manifest_mean_as_standardised_manifest_mean, refuse_unstandardised_manifest_trait_variance_as_standardised_manifest_trait_variance, refuse_unstandardised_manifest_variance_as_standardised_manifest_variance, + refuse_unstandardised_time_independent_predictor_variance_as_standardised_time_independent_predictor_variance, refuse_unstandardised_trait_variance_as_standardised_trait_variance, refuse_within_subject_scaled_initial_latent_mean_as_standardised_initial_latent_mean, }; @@ -3782,3 +3785,83 @@ fn standardised_manifest_variance_is_not_unstandardised_traitstd_or_observed_var ) ); } + +#[test] +fn standardised_time_independent_predictor_variance_is_not_unstandardised_manifeststd_or_added() { + let predictor_variance = 0.4_f64; + let recovered = recover_standardised_time_independent_predictor_variance( + predictor_variance, + LagClock::EventTime, + ) + .expect("TIPREDVARstd"); + assert!( + (recovered - 1.0).abs() < 1e-15, + "Driver et al. (2017, p. 16 / 2017-era summary.ctsemFit.R): TIPREDVARstd is v/v = 1" + ); + let larger_v = + recover_standardised_time_independent_predictor_variance(1.6, LagClock::EventTime) + .expect("TIPREDVARstd v=1.6"); + assert_eq!( + larger_v.to_bits(), + recovered.to_bits(), + "Driver et al. (2017, p. 16): distinct positive TIPREDVAR recover the same TIPREDVARstd" + ); + let manifest_std = + recover_standardised_manifest_variance(0.4, LagClock::EventTime).expect("MANIFESTVARstd"); + assert!( + (manifest_std - recovered).abs() < 1e-15, + "Driver et al. (2017, p. 16): MANIFESTVARstd and TIPREDVARstd equal 1 and remain distinct named quantities" + ); + let added = recover_asymptotic_time_independent_predictor_variance( + 0.4, + predictor_variance, + -0.5, + LagClock::EventTime, + ) + .expect("addedTIPREDVAR"); + assert!( + (added - recovered).abs() > 1e-3, + "Driver et al. (2017, §7.2): addedTIPREDVAR is not TIPREDVARstd" + ); + assert!((predictor_variance - recovered).abs() > 1e-3); + assert_eq!( + recover_standardised_time_independent_predictor_variance(0.0, LagClock::EventTime), + Err( + psychometric_core::PsychometricError::StandardisedTimeIndependentPredictorVarianceRequiresPositivePredictorVariance + ) + ); + assert_eq!( + recover_standardised_time_independent_predictor_variance( + predictor_variance, + LagClock::DocumentTime + ), + Err(psychometric_core::PsychometricError::EventTimeRequired) + ); + assert_eq!( + refuse_unstandardised_time_independent_predictor_variance_as_standardised_time_independent_predictor_variance( + predictor_variance, + recovered + ), + Err( + psychometric_core::PsychometricError::UnstandardisedTimeIndependentPredictorVarianceIsNotStandardisedTimeIndependentPredictorVariance + ) + ); + assert_eq!( + refuse_standardised_manifest_variance_as_standardised_time_independent_predictor_variance( + manifest_std, + recovered + ), + Err( + psychometric_core::PsychometricError::StandardisedManifestVarianceIsNotStandardisedTimeIndependentPredictorVariance + ) + ); + assert_eq!( + refuse_asymptotic_time_independent_predictor_variance_as_standardised_time_independent_predictor_variance( + added, + recovered + ), + Err( + psychometric_core::PsychometricError::AsymptoticTimeIndependentPredictorVarianceIsNotStandardisedTimeIndependentPredictorVariance + ) + ); +} diff --git a/docs/adr/0005-posterior-esem-dsem.md b/docs/adr/0005-posterior-esem-dsem.md index ee1e6cf0d..f7c27cc44 100644 --- a/docs/adr/0005-posterior-esem-dsem.md +++ b/docs/adr/0005-posterior-esem-dsem.md @@ -37,6 +37,7 @@ The executable standardised-initial-variance slice recovers Driver et al. (2017, The executable standardised-asymptotic-diffusion slice recovers Driver et al. (2017, p. 16 `asymDIFFUSIONstd`) as `p / p = 1` after strictly positive `asymDIFFUSION` `p = −q / (2 a)` (footnote 4; 2017-era `summary.ctsemFit.R` `solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; JSS PDF re-opened 2026-08-26T17:20Z). Unstandardised `p` is defined for a zero process and is not that map. `p_0 / p_0 = 1` is the named `T0VARstd` first-occasion correlation and is not `asymDIFFUSIONstd` even when both equal 1. `q / p = −2 a` is the named `DIFFUSIONstd` continuous-diffusion ratio and is not this correlation. `v / v = 1` is the named `TIPREDVARstd` predictor correlation and is not this map even when both equal 1. Zero `q` and `a ≥ 0` fail closed. This is not ctsem estimation. The executable standardised-manifest-trait-variance slice recovers Driver et al. (2017, p. 16 `MANIFESTTRAITVARstd`) as `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (Table 2 `Ψ_τ`; §7.1, p. 19; footnote 4; 2017-era `summary.ctsemFit.R` `solve(sqrt(diag(MANIFESTTRAITVAR) + ridging)) %&% MANIFESTTRAITVAR`; JSS PDF re-opened 2026-08-27T14:20Z). Unstandardised `ψ` is defined for a zero manifest trait and is not that map. `trait / trait = 1` is the named `TRAITVARstd` process-level correlation and is not `MANIFESTTRAITVARstd` even when both equal 1. `θ` is `MANIFESTVAR` and is not that correlation. `MANIFESTTRAITVAR` does not require `a < 0`. This is not ctsem estimation. The executable standardised-manifest-variance slice recovers Driver et al. (2017, p. 16 `MANIFESTVARstd`) as `θ / θ = 1` after strictly positive `MANIFESTVAR` (Table 2 measurement-error Cholesky; Eq. 5 `ε ~ N(0, Θ)`; footnote 4; 2017-era `summary.ctsemFit.R` `solve(sqrt(diag(MANIFESTVAR) + ridging)) %&% MANIFESTVAR`; default ridge 0; JSS PDF re-opened 2026-08-27T14:25Z). Unstandardised `θ` is defined for a zero residual and is not that map. `ψ / ψ = 1` is `MANIFESTTRAITVARstd` and is not `MANIFESTVARstd` even when both equal 1. `MANIFESTVAR` does not require `a < 0`. +The executable standardised-time-independent-predictor-variance slice recovers Driver et al. (2017, p. 16 `TIPREDVARstd`) as `v / v = 1` after strictly positive `TIPREDVAR` (Table 3, p. 13 time-independent-predictor Cholesky; footnote 4; 2017-era `summary.ctsemFit.R` `solve(sqrt(diag(TIPREDVAR) + ridging)) %&% TIPREDVAR`; default ridge 0; unlike `TRAITVAR` / `MANIFESTTRAITVAR` that source does not skip forming `TIPREDVARstd` when `v = 0`; JSS PDF re-opened 2026-09-01T19:20Z). Table 2 does not name `TIPREDVAR`. Unstandardised `v` is defined for a zero predictor and is not that map. `θ / θ = 1` is `MANIFESTVARstd` and is not `TIPREDVARstd` even when both equal 1. `(B / a)² v` is §7.2 `addedTIPREDVAR` and is not this correlation. `TIPREDVAR` does not require `a < 0`. This is not ctsem estimation. The executable standardised-trait-variance slice recovers Driver et al. (2017, p. 16 `TRAITVARstd`) as `trait / trait = 1` after strictly positive `TRAITVAR` (Table 2 `φ_ξ`; §7.1; footnote 4; 2017-era `summary.ctsemFit.R` `solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; JSS PDF re-opened 2026-08-26T17:45Z). Unlike `T0VARstd` there is no ridge addend. Unstandardised `TRAITVAR` is defined for a zero trait and is not that map. `p_0 / p_0 = 1` is the named `T0VARstd` first-occasion correlation and is not `TRAITVARstd` even when both equal 1. `t0_b² v` is `addedT0TIPREDVAR` and is not this correlation. Zero `TRAITVAR` and a non-event clock fail closed. `TRAITVAR` does not require `a < 0`. This is not ctsem estimation. diff --git a/docs/research/multilevel-event-time-recovery.md b/docs/research/multilevel-event-time-recovery.md index 3701dcb4b..5a2556385 100644 --- a/docs/research/multilevel-event-time-recovery.md +++ b/docs/research/multilevel-event-time-recovery.md @@ -90,7 +90,7 @@ This slice stays inside `psychometric_core`. It does not add a second invariance 84. refuse treating unstandardised `MANIFESTTRAITVAR` as `MANIFESTTRAITVARstd`, refuse treating `TRAITVARstd` as `MANIFESTTRAITVARstd` even when both equal 1, and refuse treating `MANIFESTVAR` `θ` as `MANIFESTTRAITVARstd`; 85. recover the exact scalar p. 16 `MANIFESTVARstd` as `solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR` after forming strictly positive `MANIFESTVAR` (Driver et al., 2017, Table 2, p. 12; Eq. 5, p. 5; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T22:40Z; OpenMx `%&%` is `t(A) %*% B %*% A`; unlike `TRAITVARstd` the 2017-era source adds ridging; the default ridge is 0; the 2017-era `dimnames` assignment to `latentNames` is a source bug; the scalar map is `θ / θ = 1`; `MANIFESTVAR = 0` makes `solve(sqrt(0))` fail and fails closed; a non-event clock fails closed; `MANIFESTVAR` does not require `a < 0`); 86. refuse treating unstandardised `MANIFESTVAR` as `MANIFESTVARstd`, refuse treating `MANIFESTTRAITVARstd` as `MANIFESTVARstd` even when both equal 1, and refuse treating Equation 5 `Var(y)` as `MANIFESTVARstd`; -87. recover the exact scalar p. 16 `TIPREDVARstd` as `solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR` after forming strictly positive `TIPREDVAR` (Driver et al., 2017, Table 2, p. 12; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T22:53Z; OpenMx `%&%` is `t(A) %*% B %*% A`; unlike `TRAITVARstd` the 2017-era source adds ridging; the default ridge is 0; `dimnames` are `TIpredNames`; the scalar map is `v / v = 1`; `TIPREDVAR = 0` makes `solve(sqrt(0))` fail and fails closed; a non-event clock fails closed; `TIPREDVAR` does not require `a < 0`); +87. recover the exact scalar p. 16 `TIPREDVARstd` as `solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR` after forming strictly positive `TIPREDVAR` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-09-01T19:20Z; OpenMx `%&%` is `t(A) %*% B %*% A`; unlike `TRAITVARstd` the 2017-era source adds ridging; the default ridge is 0; `dimnames` are `TIpredNames`; the scalar map is `v / v = 1`; `TIPREDVAR = 0` makes `solve(sqrt(0))` fail and fails closed; a non-event clock fails closed; `TIPREDVAR` does not require `a < 0`; Table 2 does not name `TIPREDVAR`); 88. refuse treating unstandardised `TIPREDVAR` as `TIPREDVARstd`, refuse treating `MANIFESTVARstd` as `TIPREDVARstd` even when both equal 1, and refuse treating §7.2 `addedTIPREDVAR` `(B / a)² v` as `TIPREDVARstd`; 89. recover the exact scalar p. 16 `asymDIFFUSIONstd` as `solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION` after forming strictly positive `asymDIFFUSION` `−q / (2 a)` (Driver et al., 2017, p. 16; footnote 4; Eq. 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-26T17:20Z; OpenMx `%&%` is `t(A) %*% B %*% A`; the 2017-era source adds ridging; the default ridge is 0; `dimnames` are `latentNames`; the scalar map is `p / p = 1`; `q = 0` makes `solve(sqrt(0))` fail and fails closed; a non-event clock fails closed; `a ≥ 0` fails closed); 90. refuse treating unstandardised `asymDIFFUSION` as `asymDIFFUSIONstd`, refuse treating `TIPREDVARstd` as `asymDIFFUSIONstd` even when both equal 1, and refuse treating `DIFFUSIONstd` `−2 a` as `asymDIFFUSIONstd`; @@ -257,7 +257,7 @@ The Voelkle et al. (2012) ZORA accepted manuscript was re-opened 2026-08-18T21:0 - Driver et al. (2017, Table 2 / §7.1 / p. 16 `TRAITVARstd`; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-26T17:45Z) recovers the scalar correlation \(\mathrm{trait}/\mathrm{trait}=1\) at machine-scale RMSE after strictly positive `TRAITVAR`, and that RMSE is smaller than treating unstandardised `TRAITVAR` or `addedT0TIPREDVAR` \(t0_b^{2}v\) as `TRAITVARstd`; distinct positive trait recover the same 1; equal 1 with `T0VARstd` remains a distinct named quantity; `TRAITVAR = 0` fails closed; a non-event clock fails closed; `TRAITVAR` does not require \(a<0\). - Driver et al. (2017, Table 2 / §7.1 / p. 16 `MANIFESTTRAITVARstd`; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T22:28Z) recovers the scalar correlation \(\psi/\psi=1\) at machine-scale RMSE after strictly positive `MANIFESTTRAITVAR`, and that RMSE is smaller than treating unstandardised `MANIFESTTRAITVAR` or `MANIFESTVAR` \(\theta\) as `MANIFESTTRAITVARstd`; distinct positive \(\psi\) recover the same 1; equal 1 with `TRAITVARstd` remains a distinct named quantity; `MANIFESTTRAITVAR = 0` fails closed; a non-event clock fails closed; `MANIFESTTRAITVAR` does not require \(a<0\). - Driver et al. (2017, Table 2 / Eq. 5 / p. 16 `MANIFESTVARstd`; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T22:40Z) recovers the scalar correlation \(\theta/\theta=1\) at machine-scale RMSE after strictly positive `MANIFESTVAR`, and that RMSE is smaller than treating unstandardised `MANIFESTVAR` or Equation 5 \(\operatorname{Var}(y)\) as `MANIFESTVARstd`; distinct positive \(\theta\) recover the same 1; equal 1 with `MANIFESTTRAITVARstd` remains a distinct named quantity; `MANIFESTVAR = 0` fails closed; a non-event clock fails closed; `MANIFESTVAR` does not require \(a<0\). -- Driver et al. (2017, Table 2 / p. 16 `TIPREDVARstd`; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T22:53Z) recovers the scalar correlation \(v/v=1\) at machine-scale RMSE after strictly positive `TIPREDVAR`, and that RMSE is smaller than treating unstandardised `TIPREDVAR` or §7.2 `addedTIPREDVAR` \((B/a)^{2}v\) as `TIPREDVARstd`; distinct positive \(v\) recover the same 1; equal 1 with `MANIFESTVARstd` remains a distinct named quantity; `TIPREDVAR = 0` fails closed; a non-event clock fails closed; `TIPREDVAR` does not require \(a<0\). +- Driver et al. (2017, Table 3 / p. 16 `TIPREDVARstd`; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-09-01T19:20Z) recovers the scalar correlation \(v/v=1\) at machine-scale RMSE after strictly positive `TIPREDVAR`, and that RMSE is smaller than treating unstandardised `TIPREDVAR` or §7.2 `addedTIPREDVAR` \((B/a)^{2}v\) as `TIPREDVARstd`; distinct positive \(v\) recover the same 1; equal 1 with `MANIFESTVARstd` remains a distinct named quantity; `TIPREDVAR = 0` fails closed; a non-event clock fails closed; `TIPREDVAR` does not require \(a<0\). Table 2 does not name `TIPREDVAR`. - Driver et al. (2017, p. 16 `asymDIFFUSIONstd`; footnote 4; Eq. 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-26T17:20Z) recovers the scalar correlation \(p/p=1\) at machine-scale RMSE after strictly positive `asymDIFFUSION` \(-q/(2a)\), and that RMSE is smaller than treating unstandardised `asymDIFFUSION` or `DIFFUSIONstd` \(-2a\) as `asymDIFFUSIONstd`; distinct positive \(p\) recover the same 1; equal 1 with `T0VARstd` and with `TIPREDVARstd` remain distinct named quantities; `q = 0` fails closed; a non-event clock fails closed; \(a\ge 0\) fails closed. - Driver et al. (2017, p. 16 `discreteCINTstd`; footnote 4; Eq. 3; Table 2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-24T05:20Z) recovers the scalar standardised discrete intercept \(A^{-1}[e^{A\Delta t}-I]\kappa/\sqrt{p}\) at machine-scale RMSE after strictly positive `asymDIFFUSION` \(-q/(2a)\), and that RMSE is smaller than treating unstandardised `discreteCINT`, \(\kappa/\sqrt{p}\), or \((-\kappa/a)/\sqrt{p}\) as `discreteCINTstd`; a later event interval changes the result; a zero intercept is exactly zero; `q = 0` fails closed; a non-event clock fails closed; a non-positive event interval fails closed; \(a\ge 0\) fails closed. - Driver et al. (2017, p. 16 `asymCINTstd`; footnote 4; Eq. 3; Table 2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-26T00:20Z) recovers the scalar standardised asymptotic intercept \((-\kappa/a)/\sqrt{p}\) at machine-scale RMSE after strictly positive `asymDIFFUSION` \(-q/(2a)\), and that RMSE is smaller than treating unstandardised `asymCINT`, \(\kappa/\sqrt{p}\), or `discreteCINTstd` as `asymCINTstd`; a later event interval changes `discreteCINTstd` and not this map; a zero intercept is exactly zero; `q = 0` fails closed; a non-event clock fails closed; \(a\ge 0\) fails closed.