diff --git a/CHANGELOG.md b/CHANGELOG.md index aad5f4ee..4e4be5e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,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 2, p. 12 `TRAITVAR`; §7.1, pp. 18–19; p. 16 `TRAITVARstd`; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-26T17:45Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104) scalar standardised trait variance on current main after `0ce16e8` dropped the pre-consolidation code while research notes already named the map (register items 81–82). Table 2 names `TRAITVAR` `φ_ξ` the latent trait variance/covariance and sets it `NULL` when there is no trait. Section 7.1 names traits the stable between-subject differences (unit-level unobserved heterogeneity). Page 16 prints standardised matrices with the suffix `std` when appropriate. The printed example on p. 16 is `discreteDRIFTstd`, not `TRAITVARstd`. Footnote 4 standardises using only the relevant variance, not the total. The relevant variance for that named between-subject correlation is `TRAITVAR`, not free first-occasion `T0VAR` and not process-dynamics `asymDIFFUSION`. The 2017-era source forms `TRAITVARstd` only when `TRAITVAR != 0`, as `solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR` when `verbose = TRUE`. OpenMx `%&%` is `t(A) %*% B %*% A`. Unlike `T0VARstd`, that formation uses `diag(diag(TRAITVAR))` and does not add `diag(c(ridging))`. The ridge is a `T0VAR` numerical hack and is not this exact map. The scalar correlation is `trait / trait = 1` after strictly positive `TRAITVAR`. Form strictly positive `trait` first, then `1 / √trait`, then `(1 / √trait) trait (1 / √trait)`. Unstandardised `TRAITVAR` is defined for a zero trait; standardised `TRAITVAR` is not. Zero `TRAITVAR` skips forming `TRAITVARstd` in the 2017-era source and fails closed here. Between-subject variance is an event-time structural quantity, so a non-event clock fails closed. `TRAITVAR` does not require stable `a < 0`. Distinct positive `trait` recover the same 1. `p_0 / p_0 = 1` is `T0VARstd` and recovers the same number and remains a distinct named quantity. `t0_b² v` is `addedT0TIPREDVAR` and is extra first-occasion TI variance, not this correlation. Meredith (1993) remains unread (Unpaywall 2026-08-26T17:20Z: `is_oa: false`; OpenAlex closed; Springer `content/pdf` is an HTML stub). Mislevy (1991, *Psychometrika, 56*, 177–196) remains unread on the same terms (DOI `10.1007/bf02294457`; Unpaywall `is_oa: false`). Still not a Kalman filter, not a matrix `expm`, not ESEM estimation, not DSEM, and not ctsem estimation. - `psychometric_core` recovers the Driver, Oud, and Voelkle (2017, p. 16 `T0VARstd`; Table 2, p. 12; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-26T07:17Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104) scalar standardised initial latent variance on current main after `0ce16e8` dropped the pre-consolidation code while research notes already named the map (register items 79–80). Page 16 prints standardised matrices with the suffix `std` when appropriate. The printed example on p. 16 is `discreteDRIFTstd`, not `T0VARstd`. Footnote 4 standardises using only the relevant variance, not the total. Table 2 names `T0VAR` the latent process initial variance/covariance. The first-occasion relevant variance is free `T0VAR` `p_0`, not process-dynamics `asymDIFFUSION` `-q / (2 a)`. The 2017-era source forms `T0VARstd` as `solve(sqrt(diag(T0VAR))) %&% T0VAR` when `verbose = TRUE`. OpenMx `%&%` is `t(A) %*% B %*% A`. 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 `p_0 / p_0 = 1` after strictly positive `p_0`. Form strictly positive `p_0` first, then `1 / √p_0`, then `(1 / √p_0) p_0 (1 / √p_0)`. A zero first-occasion variance has no positive SD and fails closed. `T0` is an event-time occasion, so a non-event clock fails closed. Free `T0VAR` does not require stable `a < 0`. Distinct positive `p_0` recover the same 1. `μ_0 / √p_0` is `T0MEANSstd` and recovers the same number when `μ_0 = √p_0` and remains a distinct named quantity. `p / p = 1` is `asymDIFFUSIONstd` and recovers the same number and remains a distinct named quantity. Meredith (1993) remains unread (Unpaywall 2026-08-26T07:17Z: `is_oa: false`; OpenAlex closed; Springer `content/pdf` is a 3038-byte HTML stub). Mislevy (1991, *Psychometrika, 56*, 177–196) remains unread on the same terms (DOI `10.1007/bf02294457`; Unpaywall `is_oa: false`). Still not a Kalman filter, not a matrix `expm`, not ESEM estimation, not DSEM, and not ctsem estimation. - `psychometric_core` recovers the Driver, Oud, and Voelkle (2017, p. 16 `asymCINTstd`; Eq. 3, p. 4; Table 2, p. 12; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-26T00:20Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104) scalar standardised asymptotic continuous intercept on current main after consolidation dropped the pre-consolidation `(-κ / a) / √p` slice. Page 16 prints standardised matrices with the suffix `std` when appropriate, and asymptotic values as `Δt → ∞`. Footnote 4 standardises using only the relevant variance, not the total. Table 2 names `κ` `CINT`. The relevant variance for that process intercept is within-subject `asymDIFFUSION` `p = −q / (2 a)`. The 2017-era source forms unstandardised `asymCINT` whenever `verbose = TRUE` as `-solve(DRIFT) %*% CINT` and does not form an `asymCINTstd` matrix. Form strictly positive `p` first, then the asymptotic intercept, then divide by `√p`. A zero intercept is exactly zero after that positive SD. Zero `q` has no positive process SD and fails closed. Lasting `p` requires stable `a < 0`. A non-event clock fails closed. `κ / √p` is `CINTstd` and is not this total-change map. `A^{-1}[e^{A Δt} − I] κ / √p` is `discreteCINTstd` and depends on the event interval. Meredith (1993) remains unread (Unpaywall 2026-08-25T18:22Z: `is_oa: false`; Springer `content/pdf` is a 3038-byte HTML stub). Mislevy (1991) remains unread on the same terms. Still not a Kalman filter, not a matrix `expm`, not ESEM estimation, not DSEM, and not ctsem estimation. - `psychometric_core` recovers the Driver, Oud, and Voelkle (2017, p. 16 `T0MEANSstd`; Table 2, p. 12; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-26T04:09Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104) scalar standardised initial latent mean on current main after `0ce16e8` dropped the pre-consolidation code while research notes already named the map. Page 16 prints standardised matrices with the suffix `std` when appropriate. Footnote 4 standardises using only the relevant variance, not the total. Table 2 names `T0MEANS` the `n.latent × 1` matrix of latent process means at the first time point `T0` and names `T0VAR` the latent process initial variance/covariance. The first-occasion relevant variance is free `T0VAR` `p_0`, not process-dynamics `asymDIFFUSION` `-q / (2 a)`. The 2017-era source forms unstandardised `T0MEANS` and does not form a `T0MEANSstd` matrix; the scalar map is `μ_0 / √p_0` after strictly positive `p_0`. A zero mean is exactly zero. Zero `p_0` has no positive SD and fails closed. `T0` is an event-time occasion, so a non-event clock fails closed. Free `T0MEANS` does not require stable `a < 0`. `p_0 / p_0 = 1` recovers the same number when `μ_0 = √p_0` and remains a distinct named quantity. `μ_0 / √asymDIFFUSION` uses process-dynamics variance and is not this first-occasion map. Meredith (1993) remains unread (Unpaywall 2026-08-26T00:22Z: `is_oa: false`; OpenAlex closed; Springer `content/pdf` is a 3038-byte HTML stub). Mislevy (1991, *Psychometrika, 56*, 177–196) remains unread on the same terms. 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 e83d1a43..1151a48f 100644 --- a/crates/psychometric_core/src/error.rs +++ b/crates/psychometric_core/src/error.rs @@ -585,6 +585,25 @@ pub enum PsychometricError { /// `asymDIFFUSIONstd` is the correlation form of process- /// dynamics `asymDIFFUSION`. StandardisedAsymptoticDiffusionIsNotStandardisedInitialLatentVariance, + /// Driver p. 16 `TRAITVARstd` was requested with a non-positive + /// trait variance. The 2017-era source skips forming + /// `TRAITVARstd` when `TRAITVAR == 0`; footnote 4 + /// standardisation requires strictly positive `TRAITVAR`. + StandardisedTraitVarianceRequiresPositiveTraitVariance, + /// Driver Table 2 unstandardised `TRAITVAR` was treated as + /// p. 16 `TRAITVARstd`. Unstandardised trait variance is + /// defined for a zero trait; standardised `TRAITVAR` is not. + UnstandardisedTraitVarianceIsNotStandardisedTraitVariance, + /// Driver p. 16 `T0VARstd` was treated as p. 16 `TRAITVARstd`. + /// Equal numbers when both correlations equal 1 are still + /// distinct named quantities. `TRAITVARstd` is the correlation + /// form of between-subject `TRAITVAR`; `T0VARstd` is the + /// correlation form of free first-occasion `T0VAR`. + StandardisedInitialLatentVarianceIsNotStandardisedTraitVariance, + /// Driver 2017-era `addedT0TIPREDVAR` `t0_b² v` was treated as + /// p. 16 `TRAITVARstd`. Extra first-occasion TI variance is not + /// the correlation form of between-subject `TRAITVAR`. + InitialTimeIndependentVarianceIsNotStandardisedTraitVariance, /// Driver p. 16 `discreteCINTstd` was requested without a strictly /// positive `asymDIFFUSION`. Footnote 4 standardises using only the /// relevant variance; zero `q` has no positive process SD. @@ -1063,6 +1082,18 @@ impl fmt::Display for PsychometricError { Self::StandardisedAsymptoticDiffusionIsNotStandardisedInitialLatentVariance => { "standardised asymptotic diffusion is not standardised initial latent variance" } + Self::StandardisedTraitVarianceRequiresPositiveTraitVariance => { + "standardised trait variance requires strictly positive trait variance" + } + Self::UnstandardisedTraitVarianceIsNotStandardisedTraitVariance => { + "unstandardised trait variance is not standardised trait variance" + } + Self::StandardisedInitialLatentVarianceIsNotStandardisedTraitVariance => { + "standardised initial latent variance is not standardised trait variance" + } + Self::InitialTimeIndependentVarianceIsNotStandardisedTraitVariance => { + "initial time-independent predictor variance is not standardised trait variance" + } Self::StandardisedDiscreteContinuousInterceptRequiresPositiveStationaryVariance => { "standardised discrete continuous intercept requires strictly positive stationary within-subject variance" } @@ -1802,6 +1833,29 @@ mod tests { ); } + #[test] + fn standardised_trait_variance_boundary_messages_are_stable() { + assert_eq!( + PsychometricError::StandardisedTraitVarianceRequiresPositiveTraitVariance.to_string(), + "standardised trait variance requires strictly positive trait variance" + ); + assert_eq!( + PsychometricError::UnstandardisedTraitVarianceIsNotStandardisedTraitVariance + .to_string(), + "unstandardised trait variance is not standardised trait variance" + ); + assert_eq!( + PsychometricError::StandardisedInitialLatentVarianceIsNotStandardisedTraitVariance + .to_string(), + "standardised initial latent variance is not standardised trait variance" + ); + assert_eq!( + PsychometricError::InitialTimeIndependentVarianceIsNotStandardisedTraitVariance + .to_string(), + "initial time-independent predictor variance is not standardised trait variance" + ); + } + #[test] fn standardised_discrete_continuous_intercept_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 89492353..4e5fd8c0 100644 --- a/crates/psychometric_core/src/event_time.rs +++ b/crates/psychometric_core/src/event_time.rs @@ -1760,6 +1760,129 @@ pub fn refuse_standardised_asymptotic_diffusion_as_standardised_initial_latent_v Err(PsychometricError::StandardisedAsymptoticDiffusionIsNotStandardisedInitialLatentVariance) } +/// Exact scalar p. 16 `TRAITVARstd` after strictly positive `TRAITVAR`. +/// +/// Driver, Oud, and Voelkle (2017, Table 2, p. 12; §7.1, pp. 18–19; +/// p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF +/// re-opened 2026-08-26T17:45Z from +/// ) +/// name `TRAITVAR` `φ_ξ` the latent trait variance/covariance. +/// Table 2 sets it `NULL` when there is no trait variance. +/// Section 7.1 names traits the stable between-subject differences +/// (unit-level unobserved heterogeneity) and estimates `φ_ξ` of the +/// intercepts `ξ` across individuals. Page 16 prints standardised +/// matrices with the suffix `std` when appropriate. The printed +/// example on p. 16 is `discreteDRIFTstd`, not `TRAITVARstd`. +/// Footnote 4: standardisations use only the relevant variance, not +/// the total. The relevant variance for that named between-subject +/// correlation is `TRAITVAR`, not free first-occasion `T0VAR` and +/// not process-dynamics `asymDIFFUSION`. The 2017-era +/// `summary.ctsemFit.R` forms `TRAITVARstd` only when +/// `TRAITVAR != 0`, as `solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR` +/// when `verbose = TRUE`. `OpenMx` `%&%` is the quadratic form +/// `t(A) %*% B %*% A`. Unlike `T0VARstd`, that formation uses +/// `diag(diag(TRAITVAR))` and does not add `diag(c(ridging))`. The +/// ridge is a `T0VAR` numerical hack and is not this exact map. The +/// scalar correlation is `trait / trait = 1` after strictly +/// positive `TRAITVAR`. Form strictly positive `trait` first, then +/// `1 / √trait`, then `(1 / √trait) trait (1 / √trait)`. +/// Unstandardised `TRAITVAR` is defined for a zero trait; +/// standardised `TRAITVAR` is not. Zero `TRAITVAR` skips forming +/// `TRAITVARstd` in the 2017-era source and fails closed here. +/// Between-subject variance is an event-time structural quantity, +/// so a non-event clock fails closed. `TRAITVAR` does not require +/// stable `a < 0`. Distinct positive `trait` recover the same 1. +/// `T0VARstd` `p_0 / p_0 = 1` recovers the same number and remains +/// a distinct named quantity. This crate already exports +/// `T0VARstd`. `addedT0TIPREDVAR` `t0_b² v` is extra first-occasion +/// TI variance, not this correlation. This crate does not currently +/// export `addedT0TIPREDVAR`; the refuse still names that quantity. +/// 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::StandardisedTraitVarianceRequiresPositiveTraitVariance`] +/// when `TRAITVAR` is zero, and +/// [`PsychometricError::InvalidNumericInput`] when the variance is +/// non-finite, negative, or the quadratic form overflows. +pub fn recover_standardised_trait_variance( + trait_variance: f64, + clock: LagClock, +) -> Result { + if !clock.admits_structural_lag() { + return Err(PsychometricError::EventTimeRequired); + } + if !trait_variance.is_finite() || trait_variance < 0.0 { + return Err(PsychometricError::InvalidNumericInput); + } + if trait_variance == 0.0 { + return Err(PsychometricError::StandardisedTraitVarianceRequiresPositiveTraitVariance); + } + let process_sd = trait_variance.sqrt(); + let inverse_sd = require_finite(1.0 / process_sd)?; + let scaled = require_finite(inverse_sd * trait_variance)?; + require_finite(scaled * inverse_sd) +} + +/// Refuse treating unstandardised `TRAITVAR` as p. 16 `TRAITVARstd`. +/// +/// Unstandardised `TRAITVAR` is defined for a zero trait. Footnote +/// 4 `TRAITVARstd` requires strictly positive `TRAITVAR`. Equal +/// numbers when `trait = 1` are still distinct named quantities. +/// +/// # Errors +/// +/// Always returns +/// [`PsychometricError::UnstandardisedTraitVarianceIsNotStandardisedTraitVariance`]. +pub fn refuse_unstandardised_trait_variance_as_standardised_trait_variance( + unstandardised_trait_variance: f64, + standardised_trait_variance: f64, +) -> Result { + let _ = (unstandardised_trait_variance, standardised_trait_variance); + Err(PsychometricError::UnstandardisedTraitVarianceIsNotStandardisedTraitVariance) +} + +/// Refuse treating p. 16 `T0VARstd` as p. 16 `TRAITVARstd`. +/// +/// Both scalar correlations equal 1 after strictly positive +/// variances. `T0VARstd` standardises free first-occasion `T0VAR`. +/// `TRAITVARstd` standardises between-subject `TRAITVAR`. Equal +/// numbers remain distinct named quantities. +/// +/// # Errors +/// +/// Always returns +/// [`PsychometricError::StandardisedInitialLatentVarianceIsNotStandardisedTraitVariance`]. +pub fn refuse_standardised_initial_latent_variance_as_standardised_trait_variance( + standardised_initial_variance: f64, + standardised_trait_variance: f64, +) -> Result { + let _ = (standardised_initial_variance, standardised_trait_variance); + Err(PsychometricError::StandardisedInitialLatentVarianceIsNotStandardisedTraitVariance) +} + +/// Refuse treating 2017-era `addedT0TIPREDVAR` as p. 16 `TRAITVARstd`. +/// +/// `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. +/// +/// # Errors +/// +/// Always returns +/// [`PsychometricError::InitialTimeIndependentVarianceIsNotStandardisedTraitVariance`]. +pub fn refuse_initial_time_independent_variance_as_standardised_trait_variance( + initial_predictor_variance: f64, + standardised_trait_variance: f64, +) -> Result { + let _ = (initial_predictor_variance, standardised_trait_variance); + Err(PsychometricError::InitialTimeIndependentVarianceIsNotStandardisedTraitVariance) +} + /// Exact scalar discrete intercept increment from Driver Equation 3. /// /// Driver, Oud, and Voelkle (2017, Eq. 3, p. 4; Table 2, p. 12; JSS @@ -6321,9 +6444,8 @@ mod tests { recover_standardised_asymptotic_continuous_intercept, recover_standardised_continuous_intercept, recover_standardised_discrete_continuous_intercept, - recover_standardised_initial_latent_mean, - recover_standardised_initial_latent_variance, - recover_standardised_manifest_mean, + recover_standardised_initial_latent_mean, recover_standardised_initial_latent_variance, + recover_standardised_manifest_mean, 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, @@ -6391,6 +6513,7 @@ mod tests { refuse_initial_time_independent_effect_as_process_increment, refuse_initial_time_independent_effect_as_time_dependent_impulse, refuse_initial_time_independent_observed_mean_as_initial_time_dependent_observed_mean, + refuse_initial_time_independent_variance_as_standardised_trait_variance, refuse_latent_lagged_covariance_as_observed_covariance, refuse_latent_mean_as_observed_mean, refuse_latent_variance_as_observed_variance, refuse_level_change_extra_process_as_impulse, @@ -6414,6 +6537,7 @@ mod tests { refuse_standardised_continuous_intercept_as_standardised_discrete_continuous_intercept, refuse_standardised_initial_latent_mean_as_standardised_initial_latent_variance, 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_stationary_initial_latent_mean_as_asymptotic_continuous_intercept, refuse_stationary_initial_latent_mean_as_asymptotic_time_independent_effect, @@ -6461,6 +6585,7 @@ mod tests { refuse_unstandardised_initial_latent_mean_as_standardised_initial_latent_mean, refuse_unstandardised_initial_latent_variance_as_standardised_initial_latent_variance, refuse_unstandardised_manifest_mean_as_standardised_manifest_mean, + refuse_unstandardised_trait_variance_as_standardised_trait_variance, refuse_within_subject_scaled_initial_latent_mean_as_standardised_initial_latent_mean, }; use crate::error::PsychometricError; @@ -15298,6 +15423,71 @@ mod tests { ); } + #[test] + fn standardised_trait_variance_recovers_driver_table_two_after_positive_traitvar() { + // Driver et al. (2017, Table 2 TRAITVAR; §7.1; p. 16 TRAITVARstd; + // 2017-era summary.ctsemFit.R): form strictly positive TRAITVAR, + // then (1/√trait) trait (1/√trait) = 1. No ridge addend. + let trait_variance = 1.6_f64; + let recovered = recover_standardised_trait_variance(trait_variance, LagClock::EventTime) + .expect("TRAITVARstd"); + assert!((recovered - 1.0).abs() < 1e-15); + let larger_trait = recover_standardised_trait_variance(6.4, LagClock::EventTime) + .expect("TRAITVARstd trait=6.4"); + assert!((larger_trait - recovered).abs() < 1e-15); + let t0var_std = + recover_standardised_initial_latent_variance(trait_variance, LagClock::EventTime) + .expect("T0VARstd"); + assert!((t0var_std - recovered).abs() < 1e-15); + // 2017-era addedT0TIPREDVAR is t0_b² v. This crate does not + // currently export that map; the refuse still names it. + let extra = 0.3_f64 * 0.3_f64 * 4.0_f64; + assert!((extra - recovered).abs() > 1e-3); + assert_eq!( + refuse_unstandardised_trait_variance_as_standardised_trait_variance( + trait_variance, + recovered + ), + Err(PsychometricError::UnstandardisedTraitVarianceIsNotStandardisedTraitVariance) + ); + assert_eq!( + refuse_standardised_initial_latent_variance_as_standardised_trait_variance( + t0var_std, recovered + ), + Err(PsychometricError::StandardisedInitialLatentVarianceIsNotStandardisedTraitVariance) + ); + assert_eq!( + refuse_initial_time_independent_variance_as_standardised_trait_variance( + extra, recovered + ), + Err(PsychometricError::InitialTimeIndependentVarianceIsNotStandardisedTraitVariance) + ); + } + + #[test] + fn standardised_trait_variance_fails_closed_when_unstandardised_is_defined() { + assert_eq!( + recover_standardised_trait_variance(0.0, LagClock::EventTime), + Err(PsychometricError::StandardisedTraitVarianceRequiresPositiveTraitVariance) + ); + assert_eq!( + recover_standardised_trait_variance(1.6, LagClock::SystemTime), + Err(PsychometricError::EventTimeRequired) + ); + assert_eq!( + recover_standardised_trait_variance(-1.6, LagClock::EventTime), + Err(PsychometricError::InvalidNumericInput) + ); + assert_eq!( + recover_standardised_trait_variance(f64::NAN, LagClock::EventTime), + Err(PsychometricError::InvalidNumericInput) + ); + assert_eq!( + recover_standardised_trait_variance(f64::INFINITY, LagClock::EventTime), + Err(PsychometricError::InvalidNumericInput) + ); + } + #[test] fn standardised_discrete_continuous_intercept_fails_closed_when_unstandardised_is_defined() { assert_eq!( diff --git a/crates/psychometric_core/src/lib.rs b/crates/psychometric_core/src/lib.rs index 2c9c2b0f..7651aab6 100644 --- a/crates/psychometric_core/src/lib.rs +++ b/crates/psychometric_core/src/lib.rs @@ -234,6 +234,18 @@ //! `μ_0 / √p_0` is `T0MEANSstd` and is not that map even when //! `μ_0 = √p_0`; `p / p = 1` is `asymDIFFUSIONstd` and is not that //! map even when both equal 1; JSS PDF re-opened 2026-08-26T07:17Z), +//! recovers the Driver p. 16 `TRAITVARstd` as `trait / trait = 1` +//! after strictly positive `TRAITVAR` (Table 2 names `TRAITVAR` +//! `φ_ξ`; §7.1 names traits the stable between-subject differences; +//! 2017-era `summary.ctsemFit.R` forms `TRAITVARstd` only when +//! `TRAITVAR != 0` as `solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; +//! 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 `T0VARstd` and is not that map even when both +//! equal 1; `t0_b² v` is `addedT0TIPREDVAR` and is not that +//! correlation; zero `TRAITVAR` fails closed; a non-event clock +//! fails closed; `TRAITVAR` does not require `a < 0`; JSS PDF +//! re-opened 2026-08-26T17:45Z), //! and refuses //! latent-mean comparison below strong invariance. @@ -384,6 +396,8 @@ pub use event_time::recover_standardised_initial_latent_mean; pub use event_time::recover_standardised_initial_latent_variance; /// Exact scalar p. 16 `MANIFESTMEANSstd` `τ / √θ`. pub use event_time::recover_standardised_manifest_mean; +/// Exact scalar p. 16 `TRAITVARstd` `trait / trait = 1` after strictly positive `TRAITVAR`. +pub use event_time::recover_standardised_trait_variance; /// Exact scalar p. 16 stationary `T0MEANS` `-κ / a + −B z / a`. pub use event_time::recover_stationary_initial_latent_mean; /// Exact scalar §4.3 / p. 16 stationary `T0VAR` `trait + −q / (2 a) + (B / a)² v`. @@ -534,6 +548,8 @@ pub use event_time::refuse_initial_time_independent_effect_as_process_increment; pub use event_time::refuse_initial_time_independent_effect_as_time_dependent_impulse; /// Refuse treating first-occasion TI observed mean as the first-occasion TD observed mean. pub use event_time::refuse_initial_time_independent_observed_mean_as_initial_time_dependent_observed_mean; +/// Refuse treating 2017-era `addedT0TIPREDVAR` as p. 16 `TRAITVARstd`. +pub use event_time::refuse_initial_time_independent_variance_as_standardised_trait_variance; /// Refuse treating Driver Eq. 3–4 lagged latent covariance as `cov(y_t, y_{t-1})`. pub use event_time::refuse_latent_lagged_covariance_as_observed_covariance; /// Refuse treating Driver Eq. 5 latent mean as `E(y)`. @@ -586,6 +602,8 @@ pub use event_time::refuse_standardised_continuous_intercept_as_standardised_dis pub use event_time::refuse_standardised_initial_latent_mean_as_standardised_initial_latent_variance; /// Refuse treating p. 16 `T0VARstd` as `T0MEANSstd`. pub use event_time::refuse_standardised_initial_latent_variance_as_standardised_initial_latent_mean; +/// Refuse treating p. 16 `T0VARstd` as `TRAITVARstd`. +pub use event_time::refuse_standardised_initial_latent_variance_as_standardised_trait_variance; /// Refuse treating `MANIFESTVARstd` as `MANIFESTMEANSstd`. pub use event_time::refuse_standardised_manifest_variance_as_standardised_manifest_mean; /// Refuse treating p. 16 stationary `T0MEANS` as `asymCINT`. @@ -682,6 +700,8 @@ pub use event_time::refuse_unstandardised_initial_latent_mean_as_standardised_in pub use event_time::refuse_unstandardised_initial_latent_variance_as_standardised_initial_latent_variance; /// Refuse treating unstandardised `MANIFESTMEANS` as `MANIFESTMEANSstd`. pub use event_time::refuse_unstandardised_manifest_mean_as_standardised_manifest_mean; +/// Refuse treating unstandardised `TRAITVAR` as `TRAITVARstd`. +pub use event_time::refuse_unstandardised_trait_variance_as_standardised_trait_variance; /// Refuse treating `μ_0 / √asymDIFFUSION` as `T0MEANSstd`. pub use event_time::refuse_within_subject_scaled_initial_latent_mean_as_standardised_initial_latent_mean; /// Indicator coordinate kind. 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 0693fd62..5248f44c 100644 --- a/crates/psychometric_core/tests/multilevel_event_time_recovery_contract.rs +++ b/crates/psychometric_core/tests/multilevel_event_time_recovery_contract.rs @@ -37,7 +37,7 @@ use psychometric_core::{ recover_standardised_asymptotic_continuous_intercept, recover_standardised_continuous_intercept, recover_standardised_discrete_continuous_intercept, recover_standardised_initial_latent_mean, recover_standardised_initial_latent_variance, - recover_standardised_manifest_mean, + recover_standardised_manifest_mean, 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, @@ -101,6 +101,7 @@ use psychometric_core::{ refuse_initial_time_independent_effect_as_process_increment, refuse_initial_time_independent_effect_as_time_dependent_impulse, refuse_initial_time_independent_observed_mean_as_initial_time_dependent_observed_mean, + refuse_initial_time_independent_variance_as_standardised_trait_variance, refuse_latent_lagged_covariance_as_observed_covariance, refuse_latent_mean_as_observed_mean, refuse_latent_variance_as_observed_variance, refuse_level_change_extra_process_as_impulse, refuse_level_change_extra_process_as_increment, refuse_level_change_extra_process_as_intercept, @@ -115,6 +116,7 @@ use psychometric_core::{ refuse_measurement_error_as_stationary_later_observed_variance, refuse_pooled_discrete_lag_across_unequal_intervals, refuse_process_noise_as_unconditional_variance, + refuse_standardised_initial_latent_variance_as_standardised_trait_variance, refuse_stationary_initial_latent_mean_as_asymptotic_continuous_intercept, refuse_stationary_initial_latent_mean_as_asymptotic_time_independent_effect, refuse_stationary_initial_latent_mean_as_discrete_mean, @@ -154,6 +156,7 @@ use psychometric_core::{ refuse_trait_plus_state_lagged_covariance_as_stationary_lagged_latent_covariance, refuse_trait_variance_as_process_noise, refuse_trait_variance_as_stationary_within_subject, refuse_unmatched_time_varying_predictor_interval, + refuse_unstandardised_trait_variance_as_standardised_trait_variance, }; fn rmse(truth: &[f64], recovered: &[f64]) -> f64 { @@ -6040,6 +6043,75 @@ fn standardised_initial_latent_variance_refuses_non_event_clocks_and_does_not_ke ); } +#[test] +fn standardised_trait_variance_recovers_driver_table_two_correlation() { + let trait_variance = 1.6_f64; + let recovered = recover_standardised_trait_variance(trait_variance, LagClock::EventTime) + .expect("TRAITVARstd"); + let recovered_error = (recovered - 1.0).abs(); + assert!( + recovered_error < 1e-15, + "Driver et al. (2017, p. 16 TRAITVARstd): RMSE {recovered_error} for trait / trait = 1" + ); + let larger_trait = recover_standardised_trait_variance(6.4, LagClock::EventTime) + .expect("TRAITVARstd trait=6.4"); + assert!( + (larger_trait - recovered).abs() < 1e-15, + "Driver et al. (2017, p. 16): distinct positive TRAITVAR recover the same TRAITVARstd" + ); + let t0var_std = + recover_standardised_initial_latent_variance(trait_variance, LagClock::EventTime) + .expect("T0VARstd"); + assert!( + (t0var_std - recovered).abs() < 1e-15, + "Driver et al. (2017, p. 16): T0VARstd equals 1 after strictly positive p_0" + ); + let extra = 0.3_f64 * 0.3_f64 * 4.0_f64; + let extra_error = (extra - 1.0).abs(); + assert!( + recovered_error < extra_error, + "Driver et al. (2017, 2017-era addedT0TIPREDVAR): extra RMSE {extra_error} must exceed TRAITVARstd RMSE {recovered_error}" + ); + let unstandardised_error = (trait_variance - 1.0).abs(); + assert!( + recovered_error < unstandardised_error, + "Driver et al. (2017, Table 2): unstandardised TRAITVAR RMSE {unstandardised_error} must exceed TRAITVARstd RMSE {recovered_error}" + ); + assert_eq!( + refuse_unstandardised_trait_variance_as_standardised_trait_variance( + trait_variance, + recovered + ), + Err(PsychometricError::UnstandardisedTraitVarianceIsNotStandardisedTraitVariance) + ); + assert_eq!( + refuse_standardised_initial_latent_variance_as_standardised_trait_variance( + t0var_std, recovered + ), + Err(PsychometricError::StandardisedInitialLatentVarianceIsNotStandardisedTraitVariance) + ); + assert_eq!( + refuse_initial_time_independent_variance_as_standardised_trait_variance(extra, recovered), + Err(PsychometricError::InitialTimeIndependentVarianceIsNotStandardisedTraitVariance) + ); +} + +#[test] +fn standardised_trait_variance_refuses_non_event_clocks_and_does_not_keep_zero_variance() { + assert_eq!( + recover_standardised_trait_variance(1.6, LagClock::AssertionTime), + Err(PsychometricError::EventTimeRequired) + ); + assert_eq!( + recover_standardised_trait_variance(1.6, LagClock::KnowledgeCutoff), + Err(PsychometricError::EventTimeRequired) + ); + assert_eq!( + recover_standardised_trait_variance(0.0, LagClock::EventTime), + Err(PsychometricError::StandardisedTraitVarianceRequiresPositiveTraitVariance) + ); +} + #[test] fn standardised_discrete_continuous_intercept_recovers_driver_page_sixteen_after_positive_p() { let intercept = 0.4_f64; diff --git a/crates/psychometric_core/tests/scientific_claim_boundary_contract.rs b/crates/psychometric_core/tests/scientific_claim_boundary_contract.rs index 50e72f74..fdde33d3 100644 --- a/crates/psychometric_core/tests/scientific_claim_boundary_contract.rs +++ b/crates/psychometric_core/tests/scientific_claim_boundary_contract.rs @@ -32,7 +32,7 @@ use psychometric_core::{ recover_standardised_asymptotic_continuous_intercept, recover_standardised_continuous_intercept, recover_standardised_discrete_continuous_intercept, recover_standardised_initial_latent_mean, recover_standardised_initial_latent_variance, - recover_standardised_manifest_mean, + recover_standardised_manifest_mean, 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, @@ -100,6 +100,7 @@ use psychometric_core::{ refuse_initial_time_independent_effect_as_process_increment, refuse_initial_time_independent_effect_as_time_dependent_impulse, refuse_initial_time_independent_observed_mean_as_initial_time_dependent_observed_mean, + refuse_initial_time_independent_variance_as_standardised_trait_variance, refuse_latent_lagged_covariance_as_observed_covariance, refuse_latent_mean_as_observed_mean, refuse_latent_variance_as_observed_variance, refuse_level_change_extra_process_as_impulse, refuse_level_change_extra_process_as_increment, refuse_level_change_extra_process_as_intercept, @@ -119,6 +120,7 @@ use psychometric_core::{ refuse_standardised_continuous_intercept_as_standardised_discrete_continuous_intercept, refuse_standardised_initial_latent_mean_as_standardised_initial_latent_variance, 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_stationary_initial_latent_mean_as_asymptotic_continuous_intercept, refuse_stationary_initial_latent_mean_as_asymptotic_time_independent_effect, @@ -165,6 +167,7 @@ use psychometric_core::{ refuse_unstandardised_initial_latent_mean_as_standardised_initial_latent_mean, refuse_unstandardised_initial_latent_variance_as_standardised_initial_latent_variance, refuse_unstandardised_manifest_mean_as_standardised_manifest_mean, + refuse_unstandardised_trait_variance_as_standardised_trait_variance, refuse_within_subject_scaled_initial_latent_mean_as_standardised_initial_latent_mean, }; @@ -3290,6 +3293,70 @@ fn standardised_initial_latent_variance_is_not_unstandardised_mean_or_asymptotic ); } +#[test] +fn standardised_trait_variance_is_not_unstandardised_or_t0varstd() { + let trait_variance = 1.6_f64; + let recovered = recover_standardised_trait_variance(trait_variance, LagClock::EventTime) + .expect("TRAITVARstd"); + assert!( + (recovered - 1.0).abs() < 1e-15, + "Driver et al. (2017, p. 16 / 2017-era summary.ctsemFit.R): TRAITVARstd is trait/trait = 1" + ); + let larger_trait = recover_standardised_trait_variance(6.4, LagClock::EventTime) + .expect("TRAITVARstd trait=6.4"); + assert!( + (larger_trait - recovered).abs() < 1e-15, + "Driver et al. (2017, p. 16): distinct positive TRAITVAR recover the same TRAITVARstd" + ); + let t0var_std = + recover_standardised_initial_latent_variance(trait_variance, LagClock::EventTime) + .expect("T0VARstd"); + assert!( + (t0var_std - recovered).abs() < 1e-15, + "Driver et al. (2017, p. 16): T0VARstd and TRAITVARstd equal 1 and remain distinct named quantities" + ); + let extra = 0.3_f64 * 0.3_f64 * 4.0_f64; + assert!( + (extra - recovered).abs() > 1e-3, + "Driver et al. (2017, 2017-era addedT0TIPREDVAR): t0_b² v is not TRAITVARstd" + ); + assert!((trait_variance - recovered).abs() > 1e-3); + assert_eq!( + recover_standardised_trait_variance(0.0, LagClock::EventTime), + Err( + psychometric_core::PsychometricError::StandardisedTraitVarianceRequiresPositiveTraitVariance + ) + ); + assert_eq!( + recover_standardised_trait_variance(trait_variance, LagClock::SystemTime), + Err(psychometric_core::PsychometricError::EventTimeRequired) + ); + assert_eq!( + refuse_unstandardised_trait_variance_as_standardised_trait_variance( + trait_variance, + recovered + ), + Err( + psychometric_core::PsychometricError::UnstandardisedTraitVarianceIsNotStandardisedTraitVariance + ) + ); + assert_eq!( + refuse_standardised_initial_latent_variance_as_standardised_trait_variance( + t0var_std, + recovered + ), + Err( + psychometric_core::PsychometricError::StandardisedInitialLatentVarianceIsNotStandardisedTraitVariance + ) + ); + assert_eq!( + refuse_initial_time_independent_variance_as_standardised_trait_variance(extra, recovered), + Err( + psychometric_core::PsychometricError::InitialTimeIndependentVarianceIsNotStandardisedTraitVariance + ) + ); +} + #[test] fn standardised_discrete_continuous_intercept_is_not_unstandardised_continuous_or_asymptotic() { let intercept = 0.4_f64; diff --git a/docs/adr/0005-posterior-esem-dsem.md b/docs/adr/0005-posterior-esem-dsem.md index 9fc0cb36..b0a49918 100644 --- a/docs/adr/0005-posterior-esem-dsem.md +++ b/docs/adr/0005-posterior-esem-dsem.md @@ -34,6 +34,7 @@ The executable standardised-measurement slice recovers Driver et al. (2017, p. 1 The executable standardised-asymptotic-intercept slice recovers Driver et al. (2017, p. 16 `asymCINTstd`) as `(-κ / a) / √p` after strictly positive `asymDIFFUSION` `p = −q / (2 a)` (footnote 4; Eq. 3; Table 2; 2017-era `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-26T00:20Z). Unstandardised `-κ / a` is defined for a zero process and is not that map. `κ / √p` is `CINTstd` and is not this total-change map. `A^{-1}[e^{A Δt} − I] κ / √p` is `discreteCINTstd` and is not this `Δt → ∞` map. This is not ctsem estimation. The executable standardised-initial-mean slice recovers Driver et al. (2017, p. 16 `T0MEANSstd`) as `μ_0 / √p_0` after strictly positive free `T0VAR` (footnote 4; JSS PDF re-opened 2026-08-26T04:09Z). Unstandardised `μ_0` is defined for a zero first-occasion variance and is not that map. `p_0 / p_0 = 1` is the named `T0VARstd` correlation form and is not `T0MEANSstd` even when `μ_0 = √p_0`. `μ_0 / √asymDIFFUSION` uses process-dynamics variance and is not the first-occasion map. Free `T0MEANS` does not require `a < 0`. This is not ctsem estimation. The executable standardised-initial-variance slice recovers Driver et al. (2017, p. 16 `T0VARstd`) as `p_0 / p_0 = 1` after strictly positive free `T0VAR` (footnote 4; 2017-era `summary.ctsemFit.R` `solve(sqrt(diag(T0VAR))) %&% T0VAR`; JSS PDF re-opened 2026-08-26T07:17Z). Unstandardised `p_0` is defined for a zero first-occasion variance and is not that map. `μ_0 / √p_0` is the named `T0MEANSstd` first-occasion mean and is not `T0VARstd` even when `μ_0 = √p_0`. `p / p = 1` is the named `asymDIFFUSIONstd` correlation form and is not `T0VARstd` even when both equal 1. Free `T0VAR` 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. Input/process/intervention/outcome paths obey event-time order. Temporal precedence, document linkage, event tracking, or model prediction alone do not justify causal language. diff --git a/docs/research/multilevel-event-time-recovery.md b/docs/research/multilevel-event-time-recovery.md index 0ac29769..901532b1 100644 --- a/docs/research/multilevel-event-time-recovery.md +++ b/docs/research/multilevel-event-time-recovery.md @@ -84,7 +84,7 @@ This slice stays inside `psychometric_core`. It does not add a second invariance 78. refuse treating unstandardised `T0TDPREDEFFECT` `t0_m` as `T0TDPREDEFFECTstd`, refuse treating `TDPREDEFFECTstd` `m · √v / √(-q / (2 a))` as `T0TDPREDEFFECTstd`, refuse treating `T0TIPREDEFFECTstd` `t0_b · √v / √p_0` as `T0TDPREDEFFECTstd` even when `t0_m = t0_b`, refuse treating `t0_m · √v / √(trait + p_0 + added)` as `T0TDPREDEFFECTstd`, and refuse treating `TRAITVAR` as the footnote 4 standardisation variance; 79. recover the exact scalar p. 16 `T0VARstd` as `solve(sqrt(diag(T0VAR))) %&% T0VAR` after forming strictly positive free `T0VAR` `p_0` (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:06Z; OpenMx `%&%` is `t(A) %*% B %*% A`; the default ridge is 0; the scalar map is `p_0 / p_0 = 1`; `p_0 = 0` fails closed; a non-event clock fails closed; free `T0VAR` does not require `a < 0`); 80. refuse treating unstandardised `T0VAR` as `T0VARstd`, refuse treating `T0TDPREDEFFECTstd` `t0_m · √v / √p_0` as `T0VARstd`, refuse treating `addedT0TIPREDVAR` `t0_b² v` as `T0VARstd`, and refuse treating `TRAITVAR` as the footnote 4 standardisation variance; -81. recover the exact scalar p. 16 `TRAITVARstd` as `solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR` after forming strictly positive `TRAITVAR` (Driver et al., 2017, Table 2, p. 12; §7.1, pp. 18–19; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T22:21Z; OpenMx `%&%` is `t(A) %*% B %*% A`; unlike `T0VARstd` there is no ridge addend; the scalar map is `trait / trait = 1`; `TRAITVAR = 0` fails closed; a non-event clock fails closed; `TRAITVAR` does not require `a < 0`); +81. recover the exact scalar p. 16 `TRAITVARstd` as `solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR` after forming strictly positive `TRAITVAR` (Driver et al., 2017, Table 2, p. 12; §7.1, pp. 18–19; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-26T17:45Z; OpenMx `%&%` is `t(A) %*% B %*% A`; unlike `T0VARstd` there is no ridge addend; the scalar map is `trait / trait = 1`; `TRAITVAR = 0` fails closed; a non-event clock fails closed; `TRAITVAR` does not require `a < 0`); 82. refuse treating unstandardised `TRAITVAR` as `TRAITVARstd`, refuse treating `T0VARstd` as `TRAITVARstd` even when both equal 1, and refuse treating `addedT0TIPREDVAR` `t0_b² v` as `TRAITVARstd`; 83. recover the exact scalar p. 16 `MANIFESTTRAITVARstd` as `solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR` after forming strictly positive `MANIFESTTRAITVAR` (Driver et al., 2017, Table 2, p. 12; §7.1, p. 19; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T22:28Z; OpenMx `%&%` is `t(A) %*% B %*% A`; unlike `TRAITVARstd` the 2017-era source adds ridging; the default ridge is 0; the scalar map is `ψ / ψ = 1`; `MANIFESTTRAITVAR = 0` fails closed; a non-event clock fails closed; `MANIFESTTRAITVAR` does not require `a < 0`); 84. refuse treating unstandardised `MANIFESTTRAITVAR` as `MANIFESTTRAITVARstd`, refuse treating `TRAITVARstd` as `MANIFESTTRAITVARstd` even when both equal 1, and refuse treating `MANIFESTVAR` `θ` as `MANIFESTTRAITVARstd`; @@ -251,7 +251,7 @@ The Voelkle et al. (2012) ZORA accepted manuscript was re-opened 2026-08-18T21:0 - Driver et al. (2017, p. 16 `TDPREDEFFECTstd`; Table 2; Eq. 3; footnote 4; JSS PDF re-opened 2026-08-23T21:10Z) recovers a known standardised continuous TD effect \(m\cdot\sqrt{v}/\sqrt{-q/(2a)}\) at machine-scale RMSE, and that RMSE is smaller than treating unstandardised \(M\), intercept-style \(A^{-1}[e^{A\Delta t}-I]M\cdot\sqrt{v}/\sqrt{p}\), or \(m\cdot\sqrt{v}/\sqrt{\mathrm{trait}+p+\mathrm{added}}\) as `TDPREDEFFECTstd`; equal numbers with `TIPREDEFFECTstd` when \(M=B\) remain distinct named quantities; a larger positive \(q\) yields a smaller \(|\mathrm{std}|\); a zero coefficient with positive \(v\) and \(p\) is exactly zero; \(q=0\), \(v=0\), and \(a\ge 0\) fail closed; a non-event clock and an overflowing product fail closed. - Driver et al. (2017, Table 3 / p. 16 `T0TDPREDEFFECTstd`; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T21:34Z) recovers a known standardised first-occasion TD effect \(t0_m\cdot\sqrt{v}/\sqrt{p_0}\) at machine-scale RMSE, and that RMSE is smaller than treating unstandardised \(t0_m\), continuous \(m\cdot\sqrt{v}/\sqrt{-q/(2a)}\), or \(t0_m\cdot\sqrt{v}/\sqrt{\mathrm{trait}+p_0+\mathrm{added}}\) as `T0TDPREDEFFECTstd`; equal numbers with `T0TIPREDEFFECTstd` when \(t0_m=t0_b\) remain distinct named quantities; a larger positive \(p_0\) yields a smaller \(|\mathrm{std}|\); a zero coefficient with positive \(v\) and \(p_0\) is exactly zero; \(p_0=0\) and \(v=0\) fail closed; a non-event clock and an overflowing product fail closed; free `T0VAR` does not require \(a<0\). - Driver et al. (2017, Table 2 / p. 16 `T0VARstd`; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-26T07:17Z) recovers the scalar correlation \(p_0/p_0=1\) at machine-scale RMSE after strictly positive free `T0VAR`, and that RMSE is smaller than treating unstandardised \(p_0\), `T0MEANSstd` \(\mu_0/\sqrt{p_0}\), or `asymDIFFUSIONstd` \(p/p=1\) as `T0VARstd`; distinct positive \(p_0\) recover the same 1; equal 1 with `T0MEANSstd` when \(\mu_0=\sqrt{p_0}\) remains a distinct named quantity; equal 1 with `asymDIFFUSIONstd` remains a distinct named quantity; \(p_0=0\) fails closed; a non-event clock fails closed; free `T0VAR` does not require \(a<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-23T22:21Z) 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 `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\).