From 659e16f25f6fc80d0b1d537115e271ca26d85ebf Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 07:21:45 +0000 Subject: [PATCH] feat(psychometric): restore Driver p.16 T0VARstd p_0/p_0=1 on main Map free first-occasion T0VAR through 2017-era summary.ctsemFit.R as solve(sqrt(diag(T0VAR))) %&% T0VAR after strictly positive p_0. OpenMx %&% is t(A)%*%B%*%A; the default ridge is 0. The scalar correlation is p_0/p_0 = 1. Refuse unstandardised T0VAR, T0MEANSstd, and asymDIFFUSIONstd. Free T0VAR does not require a<0. --- CHANGELOG.md | 1 + crates/psychometric_core/src/error.rs | 57 +++++ crates/psychometric_core/src/event_time.rs | 217 +++++++++++++++++- crates/psychometric_core/src/lib.rs | 18 ++ ...multilevel_event_time_recovery_contract.rs | 46 +++- .../scientific_claim_boundary_contract.rs | 70 +++++- docs/adr/0005-posterior-esem-dsem.md | 1 + .../multilevel-event-time-recovery.md | 3 +- 8 files changed, 409 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 568badc7..aad5f4ee 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, 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. - `psychometric_core` recovers the Driver, Oud, and Voelkle (2017, p. 16 `MANIFESTMEANSstd`; Table 2, p. 12; footnote 4; Eq. 5, p. 5; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-25T11:32Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104) scalar standardised manifest mean on current main after `0ce16e8` restored the pre-consolidation unstandardised slice. 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 `MANIFESTMEANS` `τ` the `n.manifest × 1` matrix of manifest means and `MANIFESTVAR` `Θ` the residual covariance of the indicators. The relevant variance for that named measurement intercept is residual `θ`, not total observed `Var(y) = λ² Var(η) + θ`. The 2017-era source forms unstandardised `MANIFESTMEANS` and does not form a `MANIFESTMEANSstd` matrix; the scalar map is `τ / √θ` after strictly positive `θ`. A zero mean is exactly zero. Zero `θ` has no positive SD and fails closed. A non-event clock fails closed. `MANIFESTMEANS` does not require stable `a < 0`. `θ / θ = 1` recovers the same number when `τ = √θ` and remains a distinct named quantity. `τ / √(λ² Var(η) + θ)` uses total observed variance and is not this residual map. Meredith (1993) remains unread (Unpaywall 2026-08-25T11:32Z: `is_oa: false`, 0 locations; OpenAlex closed; Springer `content/pdf` is a 3038-byte HTML stub). Mislevy (1991, *Psychometrika, 56*, 177–196) remains unread (Unpaywall 2026-08-25T11:32Z: `is_oa: false`, 0 locations). 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 198c8c77..e83d1a43 100644 --- a/crates/psychometric_core/src/error.rs +++ b/crates/psychometric_core/src/error.rs @@ -564,6 +564,27 @@ pub enum PsychometricError { /// mean using free `T0VAR`, not process-dynamics /// `asymDIFFUSION`. WithinSubjectScaledInitialLatentMeanIsNotStandardisedInitialLatentMean, + /// Driver p. 16 `T0VARstd` was requested with a non-positive + /// first-occasion variance. Footnote 4 standardisation of the + /// 2017-era `T0VAR` matrix requires strictly positive free + /// `T0VAR`. + StandardisedInitialLatentVarianceRequiresPositiveInitialLatentVariance, + /// Driver Table 2 unstandardised `T0VAR` `p_0` was treated as + /// p. 16 `T0VARstd`. Unstandardised `p_0` is defined for a zero + /// first-occasion variance; standardised `T0VAR` is not. + UnstandardisedInitialLatentVarianceIsNotStandardisedInitialLatentVariance, + /// Driver p. 16 `T0MEANSstd` was treated as p. 16 `T0VARstd`. + /// Equal numbers when `μ_0 = √p_0` are still distinct named + /// quantities. `T0VARstd` is the correlation form of free + /// `T0VAR`; `T0MEANSstd` is the first-occasion mean. + StandardisedInitialLatentMeanIsNotStandardisedInitialLatentVariance, + /// Driver p. 16 `asymDIFFUSIONstd` was treated as p. 16 + /// `T0VARstd`. Equal numbers of 1 after a strictly positive + /// relevant variance are still distinct named quantities. + /// `T0VARstd` is the correlation form of free `T0VAR`; + /// `asymDIFFUSIONstd` is the correlation form of process- + /// dynamics `asymDIFFUSION`. + StandardisedAsymptoticDiffusionIsNotStandardisedInitialLatentVariance, /// 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. @@ -1030,6 +1051,18 @@ impl fmt::Display for PsychometricError { Self::WithinSubjectScaledInitialLatentMeanIsNotStandardisedInitialLatentMean => { "within-subject scaled initial latent mean is not standardised initial latent mean" } + Self::StandardisedInitialLatentVarianceRequiresPositiveInitialLatentVariance => { + "standardised initial latent variance requires strictly positive initial latent variance" + } + Self::UnstandardisedInitialLatentVarianceIsNotStandardisedInitialLatentVariance => { + "unstandardised initial latent variance is not standardised initial latent variance" + } + Self::StandardisedInitialLatentMeanIsNotStandardisedInitialLatentVariance => { + "standardised initial latent mean is not standardised initial latent variance" + } + Self::StandardisedAsymptoticDiffusionIsNotStandardisedInitialLatentVariance => { + "standardised asymptotic diffusion is not standardised initial latent variance" + } Self::StandardisedDiscreteContinuousInterceptRequiresPositiveStationaryVariance => { "standardised discrete continuous intercept requires strictly positive stationary within-subject variance" } @@ -1745,6 +1778,30 @@ mod tests { ); } + #[test] + fn standardised_initial_latent_variance_boundary_messages_are_stable() { + assert_eq!( + PsychometricError::StandardisedInitialLatentVarianceRequiresPositiveInitialLatentVariance + .to_string(), + "standardised initial latent variance requires strictly positive initial latent variance" + ); + assert_eq!( + PsychometricError::UnstandardisedInitialLatentVarianceIsNotStandardisedInitialLatentVariance + .to_string(), + "unstandardised initial latent variance is not standardised initial latent variance" + ); + assert_eq!( + PsychometricError::StandardisedInitialLatentMeanIsNotStandardisedInitialLatentVariance + .to_string(), + "standardised initial latent mean is not standardised initial latent variance" + ); + assert_eq!( + PsychometricError::StandardisedAsymptoticDiffusionIsNotStandardisedInitialLatentVariance + .to_string(), + "standardised asymptotic diffusion is not standardised initial latent 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 bf8823e4..89492353 100644 --- a/crates/psychometric_core/src/event_time.rs +++ b/crates/psychometric_core/src/event_time.rs @@ -1574,6 +1574,69 @@ pub fn recover_standardised_initial_latent_mean( require_finite(mean / process_sd) } +/// Exact scalar p. 16 `T0VARstd` after strictly positive free `T0VAR`. +/// +/// Driver, Oud, and Voelkle (2017, Table 2, p. 12; p. 16; footnote 4; +/// 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened +/// 2026-08-26T07:17Z from +/// ) +/// name `T0VAR` the latent process initial variance/covariance. +/// Page 16 prints standardised matrices with the suffix `std` when +/// appropriate. The printed example on p. 16 is `discreteDRIFTstd`, +/// not `T0VARstd`. Footnote 4: standardisations use only the +/// relevant variance, not the total. The first-occasion relevant +/// variance is free `T0VAR` `p_0`, not within-subject +/// `asymDIFFUSION` `-q / (2 a)`, because Table 2 is the first +/// occasion, not the process dynamics. The 2017-era +/// `summary.ctsemFit.R` forms `T0VARstd` as +/// `solve(sqrt(diag(T0VAR))) %&% T0VAR` when `verbose = TRUE`. +/// `OpenMx` `%&%` is the quadratic form `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 free `T0VAR`. Form +/// strictly positive `p_0` first, then `1 / √p_0`, then +/// `(1 / √p_0) p_0 (1 / √p_0)`. Unstandardised `T0VAR` is defined +/// for a zero first-occasion variance; standardised `T0VAR` is not. +/// Zero `p_0` 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. `T0MEANSstd` `μ_0 / √p_0` recovers +/// the same number when `μ_0 = √p_0` and remains a distinct named +/// quantity. `asymDIFFUSIONstd` `p / p = 1` recovers the same +/// number and remains a distinct named quantity. This crate does +/// not currently export `T0MEANSstd` or `asymDIFFUSIONstd`; the +/// refuse still names those quantities. 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::StandardisedInitialLatentVarianceRequiresPositiveInitialLatentVariance`] +/// when `T0VAR` is zero, and +/// [`PsychometricError::InvalidNumericInput`] when the variance is +/// non-finite, negative, or the quadratic form overflows. +pub fn recover_standardised_initial_latent_variance( + initial_latent_variance: f64, + clock: LagClock, +) -> Result { + if !clock.admits_structural_lag() { + return Err(PsychometricError::EventTimeRequired); + } + if !initial_latent_variance.is_finite() || initial_latent_variance < 0.0 { + return Err(PsychometricError::InvalidNumericInput); + } + if initial_latent_variance == 0.0 { + return Err( + PsychometricError::StandardisedInitialLatentVarianceRequiresPositiveInitialLatentVariance, + ); + } + let process_sd = initial_latent_variance.sqrt(); + let inverse_sd = require_finite(1.0 / process_sd)?; + let scaled = require_finite(inverse_sd * initial_latent_variance)?; + require_finite(scaled * inverse_sd) +} + /// Refuse treating unstandardised `T0MEANS` as p. 16 /// `T0MEANSstd`. /// @@ -1594,6 +1657,28 @@ pub fn refuse_unstandardised_initial_latent_mean_as_standardised_initial_latent_ Err(PsychometricError::UnstandardisedInitialLatentMeanIsNotStandardisedInitialLatentMean) } +/// Refuse treating unstandardised `T0VAR` as p. 16 `T0VARstd`. +/// +/// Free `T0VAR` `p_0` is defined for a zero first-occasion +/// variance. Footnote 4 `T0VARstd` requires strictly positive +/// `p_0`. Equal numbers when `p_0 = 1` are still distinct named +/// quantities. +/// +/// # Errors +/// +/// Always returns +/// [`PsychometricError::UnstandardisedInitialLatentVarianceIsNotStandardisedInitialLatentVariance`]. +pub fn refuse_unstandardised_initial_latent_variance_as_standardised_initial_latent_variance( + unstandardised_initial_variance: f64, + standardised_initial_variance: f64, +) -> Result { + let _ = ( + unstandardised_initial_variance, + standardised_initial_variance, + ); + Err(PsychometricError::UnstandardisedInitialLatentVarianceIsNotStandardisedInitialLatentVariance) +} + /// Refuse treating p. 16 `T0VARstd` as p. 16 `T0MEANSstd`. /// /// Both scalar maps equal 1 when `μ_0 = √p_0`. `T0VARstd` is @@ -1614,6 +1699,26 @@ pub fn refuse_standardised_initial_latent_variance_as_standardised_initial_laten Err(PsychometricError::StandardisedInitialLatentVarianceIsNotStandardisedInitialLatentMean) } +/// Refuse treating p. 16 `T0MEANSstd` as p. 16 `T0VARstd`. +/// +/// Both scalar maps equal 1 when `μ_0 = √p_0`. `T0VARstd` is the +/// correlation form of free `T0VAR`. `T0MEANSstd` is the +/// first-occasion mean. Equal numbers remain distinct named +/// quantities. This crate does not currently export `T0MEANSstd`; +/// the refuse still names that quantity. +/// +/// # Errors +/// +/// Always returns +/// [`PsychometricError::StandardisedInitialLatentMeanIsNotStandardisedInitialLatentVariance`]. +pub fn refuse_standardised_initial_latent_mean_as_standardised_initial_latent_variance( + standardised_initial_mean: f64, + standardised_initial_variance: f64, +) -> Result { + let _ = (standardised_initial_mean, standardised_initial_variance); + Err(PsychometricError::StandardisedInitialLatentMeanIsNotStandardisedInitialLatentVariance) +} + /// Refuse treating `μ_0 / √asymDIFFUSION` as p. 16 `T0MEANSstd`. /// /// Footnote 4 first-occasion standardisation uses free `T0VAR`, @@ -1631,6 +1736,30 @@ pub fn refuse_within_subject_scaled_initial_latent_mean_as_standardised_initial_ Err(PsychometricError::WithinSubjectScaledInitialLatentMeanIsNotStandardisedInitialLatentMean) } +/// Refuse treating p. 16 `asymDIFFUSIONstd` as p. 16 `T0VARstd`. +/// +/// Both scalar maps equal 1 after a strictly positive relevant +/// variance. `T0VARstd` is the correlation form of free first- +/// occasion `T0VAR`. `asymDIFFUSIONstd` is the correlation form of +/// process-dynamics `asymDIFFUSION`. Equal numbers remain distinct +/// named quantities. This crate does not currently export +/// `asymDIFFUSIONstd`; the refuse still names that quantity. +/// +/// # Errors +/// +/// Always returns +/// [`PsychometricError::StandardisedAsymptoticDiffusionIsNotStandardisedInitialLatentVariance`]. +pub fn refuse_standardised_asymptotic_diffusion_as_standardised_initial_latent_variance( + standardised_asymptotic_diffusion: f64, + standardised_initial_variance: f64, +) -> Result { + let _ = ( + standardised_asymptotic_diffusion, + standardised_initial_variance, + ); + Err(PsychometricError::StandardisedAsymptoticDiffusionIsNotStandardisedInitialLatentVariance) +} + /// Exact scalar discrete intercept increment from Driver Equation 3. /// /// Driver, Oud, and Voelkle (2017, Eq. 3, p. 4; Table 2, p. 12; JSS @@ -6192,7 +6321,9 @@ mod tests { recover_standardised_asymptotic_continuous_intercept, recover_standardised_continuous_intercept, recover_standardised_discrete_continuous_intercept, - recover_standardised_initial_latent_mean, recover_standardised_manifest_mean, + recover_standardised_initial_latent_mean, + recover_standardised_initial_latent_variance, + recover_standardised_manifest_mean, 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, @@ -6278,8 +6409,10 @@ mod tests { refuse_observed_scaled_manifest_mean_as_standardised_manifest_mean, refuse_pooled_discrete_lag_across_unequal_intervals, refuse_process_noise_as_unconditional_variance, + refuse_standardised_asymptotic_diffusion_as_standardised_initial_latent_variance, refuse_standardised_continuous_intercept_as_standardised_asymptotic_continuous_intercept, 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_manifest_variance_as_standardised_manifest_mean, refuse_stationary_initial_latent_mean_as_asymptotic_continuous_intercept, @@ -6326,6 +6459,7 @@ mod tests { refuse_unstandardised_continuous_intercept_as_standardised_continuous_intercept, refuse_unstandardised_discrete_continuous_intercept_as_standardised_discrete_continuous_intercept, 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_within_subject_scaled_initial_latent_mean_as_standardised_initial_latent_mean, }; @@ -15053,6 +15187,61 @@ mod tests { ); } + #[test] + fn standardised_initial_latent_variance_recovers_driver_table_two_after_positive_t0var() { + // Driver et al. (2017, Table 2 T0VAR; p. 16 T0VARstd; footnote 4; + // 2017-era summary.ctsemFit.R): form strictly positive free + // T0VAR p_0, then (1/√p_0) p_0 (1/√p_0) = 1. Relevant + // variance is free T0VAR, not asymDIFFUSION. + let initial_variance = 1.6_f64; + let recovered = + recover_standardised_initial_latent_variance(initial_variance, LagClock::EventTime) + .expect("T0VARstd"); + assert!((recovered - 1.0).abs() < 1e-15); + let larger_p0 = recover_standardised_initial_latent_variance(6.4, LagClock::EventTime) + .expect("T0VARstd p_0=6.4"); + assert_eq!(larger_p0.to_bits(), recovered.to_bits()); + let unit_p0 = recover_standardised_initial_latent_variance(1.0, LagClock::EventTime) + .expect("T0VARstd p_0=1"); + assert_eq!(unit_p0.to_bits(), recovered.to_bits()); + // T0MEANSstd is μ_0/√p_0 after strictly positive p_0. Equal + // numbers when μ_0 = √p_0 remain distinct named quantities. + let mean_std = initial_variance.sqrt() / initial_variance.sqrt(); + assert!((mean_std - recovered).abs() < 1e-15); + // asymDIFFUSIONstd is p/p = 1 after strictly positive + // asymDIFFUSION. Equal 1 remains a distinct named quantity. + let within = recover_stationary_latent_variance(0.4, -0.25, LagClock::EventTime) + .expect("asymDIFFUSION"); + let within_std = 1.0_f64; + assert!((within - initial_variance).abs() > 1e-3); + assert!((within_std - recovered).abs() < 1e-15); + assert_eq!( + refuse_unstandardised_initial_latent_variance_as_standardised_initial_latent_variance( + initial_variance, + recovered + ), + Err( + PsychometricError::UnstandardisedInitialLatentVarianceIsNotStandardisedInitialLatentVariance + ) + ); + assert_eq!( + refuse_standardised_initial_latent_mean_as_standardised_initial_latent_variance( + mean_std, recovered + ), + Err( + PsychometricError::StandardisedInitialLatentMeanIsNotStandardisedInitialLatentVariance + ) + ); + assert_eq!( + refuse_standardised_asymptotic_diffusion_as_standardised_initial_latent_variance( + within_std, recovered + ), + Err( + PsychometricError::StandardisedAsymptoticDiffusionIsNotStandardisedInitialLatentVariance + ) + ); + } + #[test] fn standardised_initial_latent_mean_fails_closed_when_unstandardised_is_defined() { assert_eq!( @@ -15083,6 +15272,32 @@ mod tests { ); } + #[test] + fn standardised_initial_latent_variance_fails_closed_when_unstandardised_is_defined() { + assert_eq!( + recover_standardised_initial_latent_variance(0.0, LagClock::EventTime), + Err( + PsychometricError::StandardisedInitialLatentVarianceRequiresPositiveInitialLatentVariance + ) + ); + assert_eq!( + recover_standardised_initial_latent_variance(1.6, LagClock::SystemTime), + Err(PsychometricError::EventTimeRequired) + ); + assert_eq!( + recover_standardised_initial_latent_variance(-1.6, LagClock::EventTime), + Err(PsychometricError::InvalidNumericInput) + ); + assert_eq!( + recover_standardised_initial_latent_variance(f64::NAN, LagClock::EventTime), + Err(PsychometricError::InvalidNumericInput) + ); + assert_eq!( + recover_standardised_initial_latent_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 5b07c408..2c9c2b0f 100644 --- a/crates/psychometric_core/src/lib.rs +++ b/crates/psychometric_core/src/lib.rs @@ -224,6 +224,16 @@ //! fails closed; a non-event clock fails closed; `a ≥ 0` fails //! closed; `κ / √p` is not this total-change map; `discreteCINTstd` //! depends on `Δt` and is not this map), +//! recovers the Driver p. 16 `T0VARstd` as `p_0 / p_0 = 1` after +//! strictly positive free `T0VAR` (footnote 4 uses only the +//! relevant first-occasion variance, not process-dynamics +//! `asymDIFFUSION`; 2017-era `summary.ctsemFit.R` forms `T0VARstd` +//! as `solve(sqrt(diag(T0VAR))) %&% T0VAR`; `OpenMx` `%&%` is +//! `t(A) %*% B %*% A`; default ridge is 0; unstandardised `p_0` is +//! defined for a zero first-occasion variance and is not that map; +//! `μ_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), //! and refuses //! latent-mean comparison below strong invariance. @@ -370,6 +380,8 @@ pub use event_time::recover_standardised_continuous_intercept; pub use event_time::recover_standardised_discrete_continuous_intercept; /// Exact scalar p. 16 `T0MEANSstd` `μ_0 / √p_0`. pub use event_time::recover_standardised_initial_latent_mean; +/// Exact scalar p. 16 `T0VARstd` `p_0 / p_0 = 1` after strictly positive free `T0VAR`. +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 stationary `T0MEANS` `-κ / a + −B z / a`. @@ -564,10 +576,14 @@ pub use event_time::refuse_observed_scaled_manifest_mean_as_standardised_manifes pub use event_time::refuse_pooled_discrete_lag_across_unequal_intervals; /// Refuse treating Driver Eq. 3 process noise as the unconditional variance. pub use event_time::refuse_process_noise_as_unconditional_variance; +/// Refuse treating p. 16 `asymDIFFUSIONstd` as `T0VARstd`. +pub use event_time::refuse_standardised_asymptotic_diffusion_as_standardised_initial_latent_variance; /// Refuse treating p. 16 `CINTstd` as `asymCINTstd`. pub use event_time::refuse_standardised_continuous_intercept_as_standardised_asymptotic_continuous_intercept; /// Refuse treating p. 16 `CINTstd` as `discreteCINTstd`. pub use event_time::refuse_standardised_continuous_intercept_as_standardised_discrete_continuous_intercept; +/// Refuse treating p. 16 `T0MEANSstd` as `T0VARstd`. +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 `MANIFESTVARstd` as `MANIFESTMEANSstd`. @@ -662,6 +678,8 @@ pub use event_time::refuse_unstandardised_continuous_intercept_as_standardised_c pub use event_time::refuse_unstandardised_discrete_continuous_intercept_as_standardised_discrete_continuous_intercept; /// Refuse treating unstandardised `T0MEANS` as `T0MEANSstd`. pub use event_time::refuse_unstandardised_initial_latent_mean_as_standardised_initial_latent_mean; +/// Refuse treating unstandardised `T0VAR` as `T0VARstd`. +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 `μ_0 / √asymDIFFUSION` as `T0MEANSstd`. 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 0f7b9c65..0693fd62 100644 --- a/crates/psychometric_core/tests/multilevel_event_time_recovery_contract.rs +++ b/crates/psychometric_core/tests/multilevel_event_time_recovery_contract.rs @@ -36,7 +36,8 @@ use psychometric_core::{ recover_manifest_observed_variance, recover_manifest_trait_plus_state_observed_variance, recover_standardised_asymptotic_continuous_intercept, recover_standardised_continuous_intercept, recover_standardised_discrete_continuous_intercept, - recover_standardised_initial_latent_mean, recover_standardised_manifest_mean, + recover_standardised_initial_latent_mean, recover_standardised_initial_latent_variance, + recover_standardised_manifest_mean, 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, @@ -5977,6 +5978,31 @@ fn standardised_initial_latent_mean_recovers_driver_page_sixteen_after_positive_ ); } +#[test] +fn standardised_initial_latent_variance_recovers_driver_table_two_correlation() { + let initial_variance = 1.6_f64; + let recovered = + recover_standardised_initial_latent_variance(initial_variance, LagClock::EventTime) + .expect("T0VARstd"); + let recovered_error = (recovered - 1.0).abs(); + assert!( + recovered_error < 1e-15, + "Driver et al. (2017, p. 16 T0VARstd): RMSE {recovered_error} for p_0 / p_0 = 1" + ); + let larger_p0 = recover_standardised_initial_latent_variance(6.4, LagClock::EventTime) + .expect("T0VARstd p_0=6.4"); + assert_eq!( + larger_p0.to_bits(), + recovered.to_bits(), + "Driver et al. (2017, p. 16): distinct positive T0VAR recover the same T0VARstd" + ); + let unstandardised_error = (initial_variance - 1.0).abs(); + assert!( + unstandardised_error > recovered_error, + "Driver et al. (2017, p. 16): unstandardised T0VAR RMSE {unstandardised_error} must exceed T0VARstd RMSE {recovered_error}" + ); +} + #[test] fn standardised_initial_latent_mean_refuses_non_event_clocks_and_does_not_keep_zero_variance() { assert_eq!( @@ -5996,6 +6022,24 @@ fn standardised_initial_latent_mean_refuses_non_event_clocks_and_does_not_keep_z assert_eq!(zero.to_bits(), 0.0_f64.to_bits()); } +#[test] +fn standardised_initial_latent_variance_refuses_non_event_clocks_and_does_not_keep_zero_variance() { + assert_eq!( + recover_standardised_initial_latent_variance(1.6, LagClock::AssertionTime), + Err(PsychometricError::EventTimeRequired) + ); + assert_eq!( + recover_standardised_initial_latent_variance(1.6, LagClock::KnowledgeCutoff), + Err(PsychometricError::EventTimeRequired) + ); + assert_eq!( + recover_standardised_initial_latent_variance(0.0, LagClock::EventTime), + Err( + PsychometricError::StandardisedInitialLatentVarianceRequiresPositiveInitialLatentVariance + ) + ); +} + #[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 58296c33..50e72f74 100644 --- a/crates/psychometric_core/tests/scientific_claim_boundary_contract.rs +++ b/crates/psychometric_core/tests/scientific_claim_boundary_contract.rs @@ -31,7 +31,8 @@ use psychometric_core::{ recover_manifest_observed_variance, recover_manifest_trait_plus_state_observed_variance, recover_standardised_asymptotic_continuous_intercept, recover_standardised_continuous_intercept, recover_standardised_discrete_continuous_intercept, - recover_standardised_initial_latent_mean, recover_standardised_manifest_mean, + recover_standardised_initial_latent_mean, recover_standardised_initial_latent_variance, + recover_standardised_manifest_mean, 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, @@ -113,8 +114,10 @@ use psychometric_core::{ refuse_measurement_error_as_stationary_later_observed_variance, refuse_observed_scaled_manifest_mean_as_standardised_manifest_mean, refuse_process_noise_as_unconditional_variance, + refuse_standardised_asymptotic_diffusion_as_standardised_initial_latent_variance, refuse_standardised_continuous_intercept_as_standardised_asymptotic_continuous_intercept, 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_manifest_variance_as_standardised_manifest_mean, refuse_stationary_initial_latent_mean_as_asymptotic_continuous_intercept, @@ -160,6 +163,7 @@ use psychometric_core::{ refuse_unstandardised_continuous_intercept_as_standardised_continuous_intercept, refuse_unstandardised_discrete_continuous_intercept_as_standardised_discrete_continuous_intercept, 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_within_subject_scaled_initial_latent_mean_as_standardised_initial_latent_mean, }; @@ -3222,6 +3226,70 @@ fn standardised_initial_latent_mean_is_not_unstandardised_or_t0varstd() { ); } +#[test] +fn standardised_initial_latent_variance_is_not_unstandardised_mean_or_asymptotic_correlation() { + let initial_variance = 1.6_f64; + let recovered = + recover_standardised_initial_latent_variance(initial_variance, LagClock::EventTime) + .expect("T0VARstd"); + assert!( + (recovered - 1.0).abs() < 1e-15, + "Driver et al. (2017, p. 16 / 2017-era summary.ctsemFit.R): T0VARstd is p_0/p_0 = 1" + ); + let larger_p0 = recover_standardised_initial_latent_variance(6.4, LagClock::EventTime) + .expect("T0VARstd p_0=6.4"); + assert_eq!( + larger_p0.to_bits(), + recovered.to_bits(), + "Driver et al. (2017, p. 16): distinct positive T0VAR recover the same T0VARstd" + ); + assert!( + (recovered - initial_variance).abs() > 1e-3, + "Driver et al. (2017, Table 2): unstandardised T0VAR is not T0VARstd" + ); + let mean_std = initial_variance.sqrt() / initial_variance.sqrt(); + assert!( + (mean_std - recovered).abs() < 1e-15, + "Driver et al. (2017, p. 16): T0MEANSstd equals 1 when μ_0 = √p_0" + ); + let within_std = 1.0_f64; + assert_eq!( + refuse_unstandardised_initial_latent_variance_as_standardised_initial_latent_variance( + initial_variance, + recovered + ), + Err( + psychometric_core::PsychometricError::UnstandardisedInitialLatentVarianceIsNotStandardisedInitialLatentVariance + ) + ); + assert_eq!( + refuse_standardised_initial_latent_mean_as_standardised_initial_latent_variance( + mean_std, recovered + ), + Err( + psychometric_core::PsychometricError::StandardisedInitialLatentMeanIsNotStandardisedInitialLatentVariance + ) + ); + assert_eq!( + refuse_standardised_asymptotic_diffusion_as_standardised_initial_latent_variance( + within_std, recovered + ), + Err( + psychometric_core::PsychometricError::StandardisedAsymptoticDiffusionIsNotStandardisedInitialLatentVariance + ) + ); + assert_eq!( + recover_standardised_initial_latent_variance(0.0, LagClock::EventTime), + Err( + psychometric_core::PsychometricError::StandardisedInitialLatentVarianceRequiresPositiveInitialLatentVariance + ) + ); + assert_eq!( + recover_standardised_initial_latent_variance(initial_variance, LagClock::DocumentTime), + Err(psychometric_core::PsychometricError::EventTimeRequired) + ); +} + #[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 e8ed3e29..9fc0cb36 100644 --- a/docs/adr/0005-posterior-esem-dsem.md +++ b/docs/adr/0005-posterior-esem-dsem.md @@ -33,6 +33,7 @@ The executable standardised-intercept slice recovers Driver et al. (2017, p. 16 The executable standardised-measurement slice recovers Driver et al. (2017, p. 16 `MANIFESTMEANSstd`) as `τ / √θ` after strictly positive residual `MANIFESTVAR` (footnote 4; JSS PDF re-opened 2026-08-25T11:32Z). Unstandardised `τ` is defined for a zero residual and is not that map. `θ / θ = 1` is the named `MANIFESTVARstd` correlation form and is not `MANIFESTMEANSstd` even when `τ = √θ`. `τ / √(λ² Var(η) + θ)` uses total observed variance and is not the residual map. This is not ctsem estimation. 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. 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 7184cc48..0ac29769 100644 --- a/docs/research/multilevel-event-time-recovery.md +++ b/docs/research/multilevel-event-time-recovery.md @@ -182,6 +182,7 @@ The Voelkle et al. (2012) ZORA accepted manuscript was re-opened 2026-08-18T21:0 - **CWC-then-lag.** Sample cluster means are removed first. Consecutive within residuals are then fitted by least squares to \(r_{t+\Delta t}\approx\exp(a\Delta t)\,r_{t}\) on event time. Same-sign pair-wise logs initialize the scalar Newton step. Sign-flipping \(T=2\) CWC pairs have no real logarithm and fail closed. Curran and Bauer (2011, pp. 607–608) show that this person-mean subtraction on a raw autoregressive series does **not** isolate the lagged within-person effect; the helper therefore does not claim to recover the raw-process drift. - **Already-centered irregular residual.** The caller supplies lagged within residuals. The mean of \(a=\ln(r_{t+\Delta t}/r_t)/\Delta t\) is the exact scalar map. Intervals may be irregular. The helper does not center again. This is not DSEM. - **Standardised initial latent mean.** Driver et al. (2017, Table 2, p. 12; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-26T04:09Z): Table 2 names `T0MEANS` the latent process means at the first time point `T0`. Footnote 4 standardises using only the relevant variance, not the total. The first-occasion relevant variance is free `T0VAR` `p_0`, not `asymDIFFUSION`. The 2017-era source forms unstandardised `T0MEANS` and does not form `T0MEANSstd`. The scalar map is `μ_0/√p_0`. Form strictly positive `p_0` first, then divide. A zero mean is exactly zero. Zero `p_0` has no positive SD and fails closed. `T0` is an event-time occasion. Free `T0MEANS` does not require `a<0`. `T0VARstd` is not this map even when both equal 1. `μ_0/√asymDIFFUSION` is not this map. An overflowing quotient fails closed. This is not a Kalman filter and not ctsem estimation. +- **Standardised initial latent variance.** Driver et al. (2017, Table 2, p. 12; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-26T07:17Z): Table 2 names `T0VAR` the latent process initial variance/covariance. Footnote 4 standardises using only the relevant variance, not the total. The first-occasion relevant variance is free `T0VAR` `p_0`, not `asymDIFFUSION`. The 2017-era source forms `T0VARstd` as `solve(sqrt(diag(T0VAR))) %&% T0VAR`. OpenMx `%&%` is `t(A) %*% B %*% A`. The default ridge is 0. The scalar correlation is `p_0/p_0=1`. Form strictly positive `p_0` first, then `1/√p_0`, then `(1/√p_0) p_0 (1/√p_0)`. Zero `p_0` has no positive SD and fails closed. `T0` is an event-time occasion. Free `T0VAR` does not require `a<0`. Distinct positive `p_0` recover the same 1. `T0MEANSstd` is not this map even when both equal 1. `asymDIFFUSIONstd` is not this map even when both equal 1. An overflowing quadratic form fails closed. This is not a Kalman filter and not ctsem estimation. - **Standardised manifest mean.** Driver et al. (2017, Table 2, p. 12; p. 16; footnote 4; Eq. 5, p. 5; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-25T11:32Z): Table 2 names `MANIFESTMEANS` `τ` the matrix of manifest means. Footnote 4 standardises using only the relevant variance, not the total. The relevant variance is residual `MANIFESTVAR` `θ`, not total observed `Var(y)`. The 2017-era source forms unstandardised `MANIFESTMEANS` and does not form `MANIFESTMEANSstd`. The scalar map is `τ/√θ`. Form strictly positive `θ` first, then divide. A zero mean is exactly zero. Zero `θ` has no positive SD and fails closed. Manifest means are event-time measurement quantities. `MANIFESTMEANS` does not require `a<0`. `MANIFESTVARstd` is not this map even when both equal 1. `τ/√(λ²Var(η)+θ)` is not this map. The 2017-era `dimnames` assignment on an `n.manifest × 1` matrix is a source bug and is not this map. An overflowing quotient fails closed. This is not a Kalman filter and not ctsem estimation. - **Standardised asymptotic continuous intercept.** Driver et al. (2017, Table 2, p. 12; p. 16; footnote 4; Eq. 3, p. 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-26T00:20Z): Table 2 names `κ` `CINT` and names `asymCINT` the `Δt → ∞` expected change. Footnote 4 standardises using only the relevant variance, not the total. The relevant variance for that process intercept is within-subject `asymDIFFUSION` `p = −q / (2 a)`. The 2017-era source forms unstandardised `asymCINT` as `-solve(DRIFT) %*% CINT` and does not form an `asymCINTstd` matrix. The scalar map is `(-κ/a)/√p`. Form strictly positive `p` first, then the asymptotic intercept, then divide. 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. An overflowing quotient fails closed. This is not a Kalman filter and not ctsem estimation. @@ -249,7 +250,7 @@ The Voelkle et al. (2012) ZORA accepted manuscript was re-opened 2026-08-18T21:0 - Driver et al. (2017, Eq. 5 of §7.2 `addedTIPREDVAR`; Table 2, p. 12; §7.2, pp. 20–21; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:23Z) recovers a known extra observed-indicator TI variance \(\lambda^{2}(B/a)^{2}v\) at machine-scale RMSE, and that RMSE is smaller than treating the latent extra \((B/a)^{2}v\), Eq. 5 of `addedT0TIPREDVAR` \(\lambda^{2}t0_b^{2}v\), stationary observed variance \(\lambda^{2}p+\theta\), or `MANIFESTVAR` \(\theta\) as that observed extra; doubling \(v\) doubles the extra observed variance; a signed coefficient yields the same product; a zero loading or zero extra is exactly zero; \(v<0\) fails closed; \(a\ge 0\) with a nonzero extra fails closed; a non-event clock and an overflowing product fail closed. - 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-23T22:06Z) 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\), `T0TDPREDEFFECTstd` \(t0_m\cdot\sqrt{v}/\sqrt{p_0}\), or `addedT0TIPREDVAR` \(t0_b^{2}v\) as `T0VARstd`; distinct positive \(p_0\) recover the same 1; \(p_0=0\) fails closed; a non-event clock fails 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 `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\).