From 07c0f324d575833af3469d7fed704d6d54892284 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 30 Aug 2026 08:19:58 -0700 Subject: [PATCH 001/296] fix(psychometric): rebase discreteDRIFTstd onto main after #279 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 062a69412..50ac00ae4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,8 @@ 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 `discreteDRIFTstd`; Eq. 3, p. 5; footnote 4; Table 2, p. 12; §7.1, pp. 18–19; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-27T17:25Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104) scalar standardised discrete `DRIFT` on current main after `0ce16e8` dropped the pre-consolidation code while research notes already named the map (register items 55–56). Page 16 prints discrete-time transformations for a chosen event interval (`discreteDRIFT` as `expm(DRIFT Δt)`) and, when appropriate, standardised matrices with the suffix `std`. The printed example on p. 16 is `discreteDRIFTstd`. Footnote 4 standardises using only the relevant variance, not the total. For `DRIFT` that relevant variance is within-subject `asymDIFFUSION` `p = −q / (2 a)`, because `DRIFT` is intended to represent individual, or average individual, temporal dynamics. The 2017-era source forms `discreteDRIFTstd` whenever `verbose = TRUE`, as `discreteDRIFT * standardiser` after `standardiser <- rep(sqrt(diag(asymDIFFUSION)), each = n.latent) / rep(diag(sqrt(asymDIFFUSION)), times = n.latent)` (comment: "std dev of affecting latent divided by std dev of affected latent"). In the scalar case that SD ratio is 1 after strictly positive `p`, so the standardised auto-effect equals `φ = exp(a Δt)` numerically; those remain distinct named quantities. Form strictly positive `p` first, then `φ`. Unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion; standardised `DRIFT` is not. Zero `q` has no positive SD and fails closed. Lasting `p` requires stable `a < 0`. A non-event clock fails closed. A non-positive event interval fails closed. Section 7.1 warns that omitting trait variance confounds between- and within-person information. The trait-plus-state autocorrelation `(trait + e^{a Δt} p + added) / (trait + p + added)` uses the total, not `asymDIFFUSION`, and is not this map when `TRAITVAR` is nonzero. `TRAITVAR` is not the standardisation variance. Meredith (1993) remains unread (web search historically Unpaywall `is_oa: false`; Springer `content/pdf` is an HTML stub). Mislevy (1991, *Psychometrika, 56*, 177–196) remains unread on the same terms (DOI `10.1007/bf02294457`). Still not a Kalman filter, not a matrix `expm`, not ESEM estimation, not DSEM, and not ctsem estimation. Do not convert open `#271` `MANIFESTVARstd` or `#272` `TIPREDVARstd`. + - `event_core` adds bounded Allen interval-consistency classification, atomic path-consistency closure, contradiction/resource refusals, and an explicit dependency-error fallback without claiming unrestricted global satisfiability. - `psychometric_core` recovers the Driver, Oud, and Voelkle (2017, Table 2, p. 12 `MANIFESTTRAITVAR`; §7.1, p. 19; p. 16 `MANIFESTTRAITVARstd`; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-27T14:20Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104) scalar standardised manifest-trait variance on current main after `0ce16e8` dropped the pre-consolidation code while research notes already named the map (register items 83–84). Table 2 names `MANIFESTTRAITVAR` `Ψ_τ` the additional time-invariant variance-covariance on the measurement level and sets it `NULL` when there is no manifest trait. Equation 5 writes `Γ ~ N(τ, Ψ)` and names that covariance the manifest traits. Section 7.1 names manifest traits stable individual differences in indicator levels, distinct from process-level `TRAITVAR` `φ_ξ`. Page 16 prints standardised matrices with the suffix `std` when appropriate. The printed example on p. 16 is `discreteDRIFTstd`, not `MANIFESTTRAITVARstd`. Footnote 4 standardises using only the relevant variance, not the total. The relevant variance for that named indicator-level correlation is `MANIFESTTRAITVAR`, not process-level `TRAITVAR` and not residual `MANIFESTVAR` `θ`. The 2017-era source forms `MANIFESTTRAITVARstd` only when `MANIFESTTRAITVAR != 0`, as `solve(sqrt(diag(MANIFESTTRAITVAR) + ridging)) %&% MANIFESTTRAITVAR` when `verbose = TRUE`. OpenMx `%&%` is `t(A) %*% B %*% A`. Unlike `TRAITVARstd`, that formation adds `diag(c(ridging), n.manifest)`. The default `ridging = FALSE` adds 0, not `0.0001`; that ridge is a numerical hack and is not this exact map. The scalar correlation is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR`. Form strictly positive `ψ` first, then `1 / √ψ`, then `(1 / √ψ) ψ (1 / √ψ)`. Unstandardised `MANIFESTTRAITVAR` is defined for a zero trait; standardised `MANIFESTTRAITVAR` is not. Zero `MANIFESTTRAITVAR` skips forming `MANIFESTTRAITVARstd` in the 2017-era source and fails closed here. Indicator-level trait variance is an event-time structural quantity, so a non-event clock fails closed. `MANIFESTTRAITVAR` does not require stable `a < 0`. Distinct positive `ψ` recover the same 1. `trait / trait = 1` is `TRAITVARstd` and recovers the same number and remains a distinct named quantity. `θ` is `MANIFESTVAR` and is measurement error, not this correlation. Meredith (1993) remains unread (web search 2026-08-27T14:20Z: Springer/Cambridge Core paywalled; Unpaywall historically `is_oa: false`; Springer `content/pdf` is an HTML stub). Mislevy (1991, *Psychometrika, 56*, 177–196) remains unread on the same terms (DOI `10.1007/bf02294457`). Still not a Kalman filter, not a matrix `expm`, not ESEM estimation, not DSEM, and not ctsem estimation. From da4c6f4f563729819c77b33b40845c7e495638e8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 30 Aug 2026 08:21:57 -0700 Subject: [PATCH 002/296] chore(psychometric): add discreteDRIFTstd src files --- crates/psychometric_core/src/error.rs | 53 ++++- crates/psychometric_core/src/event_time.rs | 246 ++++++++++++++++++++- crates/psychometric_core/src/lib.rs | 21 ++ 3 files changed, 316 insertions(+), 4 deletions(-) diff --git a/crates/psychometric_core/src/error.rs b/crates/psychometric_core/src/error.rs index 4ab2695e0..9475aecb1 100644 --- a/crates/psychometric_core/src/error.rs +++ b/crates/psychometric_core/src/error.rs @@ -687,7 +687,22 @@ pub enum PsychometricError { /// `MANIFESTTRAITVARstd`. Measurement error is not the /// correlation form of indicator-level trait variance. MeasurementErrorIsNotStandardisedManifestTraitVariance, - + /// Driver p. 16 `discreteDRIFTstd` was requested with a non-positive + /// within-subject variance. Footnote 4 standardises `DRIFT` using + /// only strictly positive `asymDIFFUSION`. + StandardisedDiscreteDriftRequiresPositiveWithinSubjectVariance, + /// Driver p. 16 unstandardised `discreteDRIFT` `e^{a Δt}` was treated + /// as `discreteDRIFTstd`. Unstandardised `e^{a Δt}` is defined for + /// growing or zero-diffusion processes; standardised `DRIFT` is not. + UnstandardisedDiscreteDriftIsNotStandardisedDiscreteDrift, + /// Driver §7.1 trait-plus-state autocorrelation was treated as + /// p. 16 `discreteDRIFTstd`. Footnote 4 uses only `asymDIFFUSION`, + /// not `TRAITVAR`. + TraitPlusStateAutocorrelationIsNotStandardisedDiscreteDrift, + /// Driver §4.3 / §7.1 trait variance was treated as the p. 16 + /// footnote 4 standardisation variance. `TRAITVAR` is not + /// `asymDIFFUSION`. + TraitVarianceIsNotStandardisationVariance, /// Driver p. 16 `MANIFESTVARstd` was requested with a /// non-positive residual `MANIFESTVAR`. Unlike `TRAITVAR` / /// `MANIFESTTRAITVAR`, the 2017-era source still forms @@ -1222,7 +1237,18 @@ impl fmt::Display for PsychometricError { Self::MeasurementErrorIsNotStandardisedManifestTraitVariance => { "measurement error is not standardised manifest-trait variance" } - + Self::StandardisedDiscreteDriftRequiresPositiveWithinSubjectVariance => { + "standardised discrete DRIFT requires strictly positive within-subject variance" + } + Self::UnstandardisedDiscreteDriftIsNotStandardisedDiscreteDrift => { + "unstandardised discrete DRIFT is not standardised discrete DRIFT" + } + Self::TraitPlusStateAutocorrelationIsNotStandardisedDiscreteDrift => { + "trait-plus-state autocorrelation is not standardised discrete DRIFT" + } + Self::TraitVarianceIsNotStandardisationVariance => { + "trait variance is not the standardisation variance" + } Self::StandardisedManifestVarianceRequiresPositiveManifestVariance => { "standardised measurement-error variance requires strictly positive measurement-error variance" } @@ -2073,4 +2099,27 @@ mod tests { "measurement error is not standardised manifest-trait variance" ); } + + #[test] + fn standardised_discrete_drift_boundary_messages_are_stable() { + assert_eq!( + PsychometricError::StandardisedDiscreteDriftRequiresPositiveWithinSubjectVariance + .to_string(), + "standardised discrete DRIFT requires strictly positive within-subject variance" + ); + assert_eq!( + PsychometricError::UnstandardisedDiscreteDriftIsNotStandardisedDiscreteDrift + .to_string(), + "unstandardised discrete DRIFT is not standardised discrete DRIFT" + ); + assert_eq!( + PsychometricError::TraitPlusStateAutocorrelationIsNotStandardisedDiscreteDrift + .to_string(), + "trait-plus-state autocorrelation is not standardised discrete DRIFT" + ); + assert_eq!( + PsychometricError::TraitVarianceIsNotStandardisationVariance.to_string(), + "trait variance is not the standardisation variance" + ); + } } diff --git a/crates/psychometric_core/src/event_time.rs b/crates/psychometric_core/src/event_time.rs index a29bc5c18..9639b2c2d 100644 --- a/crates/psychometric_core/src/event_time.rs +++ b/crates/psychometric_core/src/event_time.rs @@ -357,6 +357,131 @@ pub fn recover_discrete_lag_from_log_rate( Ok(discrete_lag) } +/// Exact scalar p. 16 `discreteDRIFTstd` after strictly positive +/// `asymDIFFUSION`. +/// +/// Driver, Oud, and Voelkle (2017, p. 16; Eq. 3, p. 5; footnote 4; +/// Table 2, p. 12; §7.1, pp. 18–19; 2017-era ctsem +/// `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-27T17:25Z from +/// ) +/// print `discreteDRIFT` as the unstandardised discrete-time equivalent +/// of `DRIFT` for a chosen event interval, `expm(DRIFT Δt)`. Equation 3 +/// writes that map. Page 16 then prints standardised matrices with the +/// suffix `std` when appropriate. The printed example on p. 16 is +/// `discreteDRIFTstd`. Footnote 4: standardisations use only the +/// relevant variance, not the total. For `DRIFT`, that relevant +/// variance is within-subject `asymDIFFUSION` `p = −q / (2 a)`, because +/// `DRIFT` is intended to represent individual, or average individual, +/// temporal dynamics. The 2017-era `summary.ctsemFit.R` forms +/// `discreteDRIFTstd` whenever `verbose = TRUE`, as +/// `discreteDRIFT * standardiser` after +/// `standardiser <- rep(sqrt(diag(asymDIFFUSION)), each = n.latent) / +/// rep(diag(sqrt(asymDIFFUSION)), times = n.latent)` (comment: "std +/// dev of affecting latent divided by std dev of affected latent"). +/// In the scalar case that SD ratio is 1 after strictly positive `p`, +/// so the standardised auto-effect equals `φ = exp(a Δt)` numerically; +/// those remain distinct named quantities. Form strictly positive `p` +/// first, then `φ`. Unstandardised `e^{a Δt}` is defined for growing +/// `a ≥ 0` and for zero diffusion; standardised `DRIFT` is not. Zero +/// `q` has no positive SD and fails closed. Lasting `asymDIFFUSION` +/// requires stable `a < 0`. A non-event clock fails closed. A +/// non-positive event interval fails closed. Section 7.1 warns that +/// omitting trait variance confounds between- and within-person +/// information. The trait-plus-state autocorrelation +/// `(trait + e^{a Δt} p + added) / (trait + p + added)` uses the +/// total, not `asymDIFFUSION`, and is not this map when `TRAITVAR` +/// is nonzero. `TRAITVAR` is not the standardisation variance. This +/// is not a Kalman filter, not a matrix `expm`, not DSEM, and not +/// ctsem estimation. +/// +/// # Errors +/// +/// Propagates [`recover_stationary_latent_variance`] and +/// [`recover_discrete_lag_from_log_rate`]. Returns +/// [`PsychometricError::EventTimeRequired`] for any non-event clock, +/// [`PsychometricError::NonPositiveInterval`] when `event_delta` is +/// not strictly positive, +/// [`PsychometricError::StationaryVarianceRequiresStableDrift`] when +/// the log-rate is not strictly negative, +/// [`PsychometricError::StandardisedDiscreteDriftRequiresPositiveWithinSubjectVariance`] +/// when `asymDIFFUSION` is zero, and +/// [`PsychometricError::InvalidNumericInput`] when an input is +/// non-finite or the exponential overflows or underflows to zero. +pub fn recover_standardised_discrete_drift( + continuous_diffusion: f64, + log_rate: f64, + event_delta: f64, + clock: LagClock, +) -> Result { + let within = recover_stationary_latent_variance(continuous_diffusion, log_rate, clock)?; + if within == 0.0 { + return Err( + PsychometricError::StandardisedDiscreteDriftRequiresPositiveWithinSubjectVariance, + ); + } + recover_discrete_lag_from_log_rate(log_rate, event_delta, clock) +} + +/// Refuse treating unstandardised `discreteDRIFT` as p. 16 +/// `discreteDRIFTstd`. +/// +/// `e^{a Δt}` is defined for growing and zero-diffusion processes. +/// Footnote 4 `discreteDRIFTstd` requires strictly positive +/// `asymDIFFUSION`. Equal numbers in the scalar stationary case are +/// still distinct named quantities. +/// +/// # Errors +/// +/// Always returns +/// [`PsychometricError::UnstandardisedDiscreteDriftIsNotStandardisedDiscreteDrift`]. +pub fn refuse_unstandardised_discrete_drift_as_standardised_discrete_drift( + unstandardised_discrete_drift: f64, + standardised_discrete_drift: f64, +) -> Result { + let _ = (unstandardised_discrete_drift, standardised_discrete_drift); + Err(PsychometricError::UnstandardisedDiscreteDriftIsNotStandardisedDiscreteDrift) +} + +/// Refuse treating Driver §7.1 trait-plus-state autocorrelation as +/// p. 16 `discreteDRIFTstd`. +/// +/// `(trait + e^{a Δt} p + added) / (trait + p + added)` mixes +/// between-subject `TRAITVAR` into the auto-effect. Footnote 4 +/// standardises `DRIFT` using only `asymDIFFUSION`. +/// +/// # Errors +/// +/// Always returns +/// [`PsychometricError::TraitPlusStateAutocorrelationIsNotStandardisedDiscreteDrift`]. +pub fn refuse_trait_plus_state_autocorrelation_as_standardised_discrete_drift( + trait_plus_state_autocorrelation: f64, + standardised_discrete_drift: f64, +) -> Result { + let _ = ( + trait_plus_state_autocorrelation, + standardised_discrete_drift, + ); + Err(PsychometricError::TraitPlusStateAutocorrelationIsNotStandardisedDiscreteDrift) +} + +/// Refuse treating Driver §4.3 trait variance as the p. 16 footnote 4 +/// standardisation variance. +/// +/// `TRAITVAR` is time-invariant between-subject variance. Footnote 4 +/// uses only within-subject `asymDIFFUSION`. +/// +/// # Errors +/// +/// Always returns +/// [`PsychometricError::TraitVarianceIsNotStandardisationVariance`]. +pub fn refuse_trait_variance_as_standardisation_variance( + trait_variance: f64, + within_subject_variance: f64, +) -> Result { + let _ = (trait_variance, within_subject_variance); + Err(PsychometricError::TraitVarianceIsNotStandardisationVariance) +} + /// Recover the exact scalar pair `(φ, a)` on event time. /// /// # Errors @@ -6888,7 +7013,7 @@ mod tests { recover_manifest_observed_variance, recover_manifest_trait_plus_state_observed_variance, recover_standardised_asymptotic_continuous_intercept, recover_standardised_asymptotic_diffusion, recover_standardised_continuous_intercept, - recover_standardised_discrete_continuous_intercept, + recover_standardised_discrete_continuous_intercept, recover_standardised_discrete_drift, recover_standardised_initial_latent_mean, recover_standardised_initial_latent_variance, recover_standardised_manifest_mean, recover_standardised_manifest_trait_variance, recover_standardised_trait_variance, recover_stationary_initial_latent_mean, @@ -7026,14 +7151,17 @@ mod tests { refuse_time_independent_effect_as_time_varying_discrete_effect, refuse_time_independent_observed_mean_as_initial_time_dependent_observed_mean, refuse_time_independent_observed_mean_as_initial_time_independent_observed_mean, + refuse_trait_plus_state_autocorrelation_as_standardised_discrete_drift, refuse_trait_plus_state_lagged_covariance_as_stationary_lagged_latent_covariance, refuse_trait_scaled_continuous_intercept_as_standardised_continuous_intercept, - refuse_trait_variance_as_process_noise, refuse_trait_variance_as_stationary_within_subject, + refuse_trait_variance_as_process_noise, refuse_trait_variance_as_standardisation_variance, + refuse_trait_variance_as_stationary_within_subject, refuse_unmatched_time_varying_predictor_interval, refuse_unstandardised_asymptotic_continuous_intercept_as_standardised_asymptotic_continuous_intercept, refuse_unstandardised_asymptotic_diffusion_as_standardised_asymptotic_diffusion, refuse_unstandardised_continuous_intercept_as_standardised_continuous_intercept, refuse_unstandardised_discrete_continuous_intercept_as_standardised_discrete_continuous_intercept, + refuse_unstandardised_discrete_drift_as_standardised_discrete_drift, 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, @@ -16353,4 +16481,118 @@ mod tests { Err(PsychometricError::InvalidNumericInput) ); } + + #[test] + fn standardised_discrete_drift_recovers_driver_page_sixteen_after_positive_asymdiffusion() { + // Driver et al. (2017, p. 16 discreteDRIFTstd; footnote 4; + // 2017-era summary.ctsemFit.R): form strictly positive + // asymDIFFUSION = −q / (2 a), then φ = exp(a Δt). Scalar SD + // ratio is 1. JSS PDF re-opened 2026-08-27T17:25Z. + let diffusion = 0.4_f64; + let log_rate = -0.5_f64; + let event_delta = 1.0_f64; + let recovered = recover_standardised_discrete_drift( + diffusion, + log_rate, + event_delta, + LagClock::EventTime, + ) + .expect("discreteDRIFTstd"); + let unstandardised = + recover_discrete_lag_from_log_rate(log_rate, event_delta, LagClock::EventTime) + .expect("discreteDRIFT"); + let within = recover_stationary_latent_variance(diffusion, log_rate, LagClock::EventTime) + .expect("asymDIFFUSION"); + assert!(within > 0.0); + assert!((recovered - unstandardised).abs() < 1e-15); + assert!((recovered - (log_rate * event_delta).exp()).abs() < 1e-15); + let two_and_a_half = + recover_standardised_discrete_drift(diffusion, log_rate, 2.5, LagClock::EventTime) + .expect("discreteDRIFTstd Δt=2.5"); + assert!((two_and_a_half - (log_rate * 2.5).exp()).abs() < 1e-15); + assert!((recovered - two_and_a_half).abs() > 1e-9); + let trait_variance = 1.0_f64; + let lagged = recover_trait_plus_state_lagged_covariance( + trait_variance, + within, + log_rate, + event_delta, + LagClock::EventTime, + ) + .expect("trait+state lag"); + let total = recover_trait_plus_state_latent_variance(trait_variance, within) + .expect("trait+state var"); + let contaminated = lagged / total; + assert!((contaminated - recovered).abs() > 1e-3); + assert_eq!( + refuse_unstandardised_discrete_drift_as_standardised_discrete_drift( + unstandardised, + recovered + ), + Err(PsychometricError::UnstandardisedDiscreteDriftIsNotStandardisedDiscreteDrift) + ); + assert_eq!( + refuse_trait_plus_state_autocorrelation_as_standardised_discrete_drift( + contaminated, + recovered + ), + Err(PsychometricError::TraitPlusStateAutocorrelationIsNotStandardisedDiscreteDrift) + ); + assert_eq!( + refuse_trait_variance_as_standardisation_variance(trait_variance, within), + Err(PsychometricError::TraitVarianceIsNotStandardisationVariance) + ); + } + + #[test] + fn standardised_discrete_drift_fails_closed_when_unstandardised_is_defined() { + let log_rate = -0.5_f64; + let event_delta = 1.0_f64; + let unstandardised_zero_q = + recover_discrete_lag_from_log_rate(log_rate, event_delta, LagClock::EventTime) + .expect("e^{aΔt} at q=0"); + assert!((unstandardised_zero_q - log_rate.exp()).abs() < 1e-15); + assert_eq!( + recover_standardised_discrete_drift(0.0, log_rate, event_delta, LagClock::EventTime), + Err(PsychometricError::StandardisedDiscreteDriftRequiresPositiveWithinSubjectVariance) + ); + let growing = recover_discrete_lag_from_log_rate(0.5, event_delta, LagClock::EventTime) + .expect("growing a>0"); + assert_eq!(growing.to_bits(), 0.5_f64.exp().to_bits()); + assert_eq!( + recover_standardised_discrete_drift(0.4, 0.5, event_delta, LagClock::EventTime), + Err(PsychometricError::StationaryVarianceRequiresStableDrift) + ); + let unit = + recover_discrete_lag_from_log_rate(0.0, event_delta, LagClock::EventTime).expect("a=0"); + assert!((unit - 1.0).abs() < 1e-15); + assert_eq!( + recover_standardised_discrete_drift(0.4, 0.0, event_delta, LagClock::EventTime), + Err(PsychometricError::StationaryVarianceRequiresStableDrift) + ); + assert_eq!( + recover_standardised_discrete_drift(0.4, log_rate, event_delta, LagClock::SystemTime), + Err(PsychometricError::EventTimeRequired) + ); + assert_eq!( + recover_standardised_discrete_drift(0.4, log_rate, 0.0, LagClock::EventTime), + Err(PsychometricError::NonPositiveInterval) + ); + assert_eq!( + recover_standardised_discrete_drift(0.4, log_rate, f64::NAN, LagClock::EventTime), + Err(PsychometricError::NonPositiveInterval) + ); + assert_eq!( + recover_standardised_discrete_drift(-0.1, log_rate, event_delta, LagClock::EventTime), + Err(PsychometricError::InvalidNumericInput) + ); + assert_eq!( + recover_standardised_discrete_drift(0.4, f64::NAN, event_delta, LagClock::EventTime), + Err(PsychometricError::InvalidNumericInput) + ); + assert_eq!( + recover_standardised_discrete_drift(0.4, -800.0, 1.0, LagClock::EventTime), + Err(PsychometricError::InvalidNumericInput) + ); + } } diff --git a/crates/psychometric_core/src/lib.rs b/crates/psychometric_core/src/lib.rs index c081a63f6..e66a3d48d 100644 --- a/crates/psychometric_core/src/lib.rs +++ b/crates/psychometric_core/src/lib.rs @@ -261,6 +261,19 @@ //! correlation; zero `MANIFESTTRAITVAR` fails closed; a non-event //! clock fails closed; `MANIFESTTRAITVAR` does not require `a < 0`; //! JSS PDF re-opened 2026-08-27T14:20Z), +//! recovers the Driver p. 16 `discreteDRIFTstd` as `e^{a Δt}` after +//! strictly positive `asymDIFFUSION` `p = −q / (2 a)` (the printed +//! example on p. 16; footnote 4 standardises `DRIFT` using only +//! within-subject variance, not the total; 2017-era +//! `summary.ctsemFit.R` forms `discreteDRIFTstd` as +//! `discreteDRIFT * standardiser` where the scalar SD ratio is 1 +//! after strictly positive `p`; unstandardised `e^{a Δt}` is defined +//! for growing `a ≥ 0` and for zero diffusion and is not that map; +//! zero `q` fails closed; a non-event clock fails closed; `a ≥ 0` +//! fails closed; a non-positive event interval fails closed; the +//! §7.1 trait-plus-state autocorrelation uses `TRAITVAR` and is not +//! that map; `TRAITVAR` is not the standardisation variance; JSS PDF +//! re-opened 2026-08-27T17:25Z), //! and refuses //! latent-mean comparison below strong invariance. @@ -407,6 +420,8 @@ pub use event_time::recover_standardised_asymptotic_diffusion; pub use event_time::recover_standardised_continuous_intercept; /// Exact scalar p. 16 `discreteCINTstd` `A^{-1}[e^{A Δt} − I] κ / √p`. pub use event_time::recover_standardised_discrete_continuous_intercept; +/// Exact scalar p. 16 `discreteDRIFTstd` `e^{a Δt}` after strictly positive `asymDIFFUSION`. +pub use event_time::recover_standardised_discrete_drift; /// 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`. @@ -715,12 +730,16 @@ pub use event_time::refuse_time_independent_effect_as_time_varying_discrete_effe pub use event_time::refuse_time_independent_observed_mean_as_initial_time_dependent_observed_mean; /// Refuse treating process-increment `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` as the first-occasion TI-predictor observed mean. pub use event_time::refuse_time_independent_observed_mean_as_initial_time_independent_observed_mean; +/// Refuse treating Driver §7.1 trait-plus-state autocorrelation as p. 16 `discreteDRIFTstd`. +pub use event_time::refuse_trait_plus_state_autocorrelation_as_standardised_discrete_drift; /// Refuse treating §4.3 trait-plus-state lagged covariance as lagged stationary `T0VAR`. pub use event_time::refuse_trait_plus_state_lagged_covariance_as_stationary_lagged_latent_covariance; /// Refuse treating `κ / √(trait + p + added)` as `CINTstd`. pub use event_time::refuse_trait_scaled_continuous_intercept_as_standardised_continuous_intercept; /// Refuse treating Driver §4.3 trait variance as process noise. pub use event_time::refuse_trait_variance_as_process_noise; +/// Refuse treating Driver §4.3 trait variance as the p. 16 footnote 4 standardisation variance. +pub use event_time::refuse_trait_variance_as_standardisation_variance; /// Refuse treating Driver §4.3 trait variance as `asymDIFFUSION`. pub use event_time::refuse_trait_variance_as_stationary_within_subject; /// Refuse a time-varying predictor whose sampling and constancy intervals differ. @@ -733,6 +752,8 @@ pub use event_time::refuse_unstandardised_asymptotic_diffusion_as_standardised_a pub use event_time::refuse_unstandardised_continuous_intercept_as_standardised_continuous_intercept; /// Refuse treating unstandardised `discreteCINT` as `discreteCINTstd`. pub use event_time::refuse_unstandardised_discrete_continuous_intercept_as_standardised_discrete_continuous_intercept; +/// Refuse treating unstandardised `discreteDRIFT` as p. 16 `discreteDRIFTstd`. +pub use event_time::refuse_unstandardised_discrete_drift_as_standardised_discrete_drift; /// 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`. From 96444faca9dd4831acd740ce2f3a8e7aac32b6eb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 30 Aug 2026 08:22:51 -0700 Subject: [PATCH 003/296] chore(psychometric): add discreteDRIFTstd contract tests --- ...multilevel_event_time_recovery_contract.rs | 106 ++++++++++++++++-- .../scientific_claim_boundary_contract.rs | 98 +++++++++++++--- 2 files changed, 178 insertions(+), 26 deletions(-) diff --git a/crates/psychometric_core/tests/multilevel_event_time_recovery_contract.rs b/crates/psychometric_core/tests/multilevel_event_time_recovery_contract.rs index 1c0027f44..e65e9d196 100644 --- a/crates/psychometric_core/tests/multilevel_event_time_recovery_contract.rs +++ b/crates/psychometric_core/tests/multilevel_event_time_recovery_contract.rs @@ -36,17 +36,17 @@ use psychometric_core::{ recover_manifest_observed_variance, recover_manifest_trait_plus_state_observed_variance, recover_standardised_asymptotic_continuous_intercept, recover_standardised_asymptotic_diffusion, 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_trait_variance, recover_standardised_manifest_variance, - recover_standardised_trait_variance, recover_stationary_initial_latent_mean, - recover_stationary_initial_latent_variance, recover_stationary_initial_observed_mean, - recover_stationary_initial_observed_variance, recover_stationary_lagged_latent_covariance, - recover_stationary_lagged_observed_covariance, recover_stationary_latent_variance, - recover_stationary_later_latent_variance, recover_stationary_later_observed_variance, - recover_time_dependent_predictor_impulse, recover_time_dependent_predictor_impulse_carry, - recover_trait_plus_state_lagged_covariance, recover_trait_plus_state_latent_variance, - recover_within_residual_event_time_log_rate, + recover_standardised_discrete_continuous_intercept, recover_standardised_discrete_drift, + recover_standardised_initial_latent_mean, recover_standardised_initial_latent_variance, + recover_standardised_manifest_mean, recover_standardised_manifest_trait_variance, + recover_standardised_manifest_variance, recover_standardised_trait_variance, + recover_stationary_initial_latent_mean, recover_stationary_initial_latent_variance, + recover_stationary_initial_observed_mean, recover_stationary_initial_observed_variance, + recover_stationary_lagged_latent_covariance, recover_stationary_lagged_observed_covariance, + recover_stationary_latent_variance, recover_stationary_later_latent_variance, + recover_stationary_later_observed_variance, recover_time_dependent_predictor_impulse, + recover_time_dependent_predictor_impulse_carry, recover_trait_plus_state_lagged_covariance, + recover_trait_plus_state_latent_variance, recover_within_residual_event_time_log_rate, refuse_after_extra_process_contribution_as_observed_mean, refuse_after_extra_process_latent_mean_as_observed_mean, refuse_asymptotic_continuous_intercept_as_asymptotic_time_independent_effect, @@ -159,9 +159,12 @@ use psychometric_core::{ refuse_time_independent_effect_as_time_varying_discrete_effect, refuse_time_independent_observed_mean_as_initial_time_dependent_observed_mean, refuse_time_independent_observed_mean_as_initial_time_independent_observed_mean, + refuse_trait_plus_state_autocorrelation_as_standardised_discrete_drift, 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_trait_variance_as_process_noise, refuse_trait_variance_as_standardisation_variance, + refuse_trait_variance_as_stationary_within_subject, refuse_unmatched_time_varying_predictor_interval, + refuse_unstandardised_discrete_drift_as_standardised_discrete_drift, refuse_unstandardised_manifest_trait_variance_as_standardised_manifest_trait_variance, refuse_unstandardised_manifest_variance_as_standardised_manifest_variance, refuse_unstandardised_trait_variance_as_standardised_trait_variance, @@ -6251,7 +6254,86 @@ fn standardised_manifest_trait_variance_refuses_non_event_clocks_and_does_not_ke } #[test] +fn standardised_discrete_drift_recovers_driver_page_sixteen_footnote_four() { + let diffusion = 0.4_f64; + let log_rate = -0.5_f64; + let event_delta = 1.0_f64; + let recovered = + recover_standardised_discrete_drift(diffusion, log_rate, event_delta, LagClock::EventTime) + .expect("discreteDRIFTstd"); + let unstandardised = + recover_discrete_lag_from_log_rate(log_rate, event_delta, LagClock::EventTime) + .expect("discreteDRIFT"); + let within = recover_stationary_latent_variance(diffusion, log_rate, LagClock::EventTime) + .expect("asymDIFFUSION"); + assert!(within > 0.0); + assert!((recovered - (log_rate * event_delta).exp()).abs() < 1e-15); + assert!((recovered - unstandardised).abs() < 1e-15); + let two_and_a_half = + recover_standardised_discrete_drift(diffusion, log_rate, 2.5, LagClock::EventTime) + .expect("discreteDRIFTstd Δt=2.5"); + assert!((two_and_a_half - (log_rate * 2.5).exp()).abs() < 1e-15); + let trait_variance = 1.0_f64; + let lagged = recover_trait_plus_state_lagged_covariance( + trait_variance, + within, + log_rate, + event_delta, + LagClock::EventTime, + ) + .expect("trait+state lag"); + let total = + recover_trait_plus_state_latent_variance(trait_variance, within).expect("trait+state var"); + let contaminated = lagged / total; + assert!((contaminated - recovered).abs() > 1e-3); + assert_eq!( + refuse_unstandardised_discrete_drift_as_standardised_discrete_drift( + unstandardised, + recovered + ), + Err(PsychometricError::UnstandardisedDiscreteDriftIsNotStandardisedDiscreteDrift) + ); + assert_eq!( + refuse_trait_plus_state_autocorrelation_as_standardised_discrete_drift( + contaminated, + recovered + ), + Err(PsychometricError::TraitPlusStateAutocorrelationIsNotStandardisedDiscreteDrift) + ); + assert_eq!( + refuse_trait_variance_as_standardisation_variance(trait_variance, within), + Err(PsychometricError::TraitVarianceIsNotStandardisationVariance) + ); +} +#[test] +fn standardised_discrete_drift_refuses_non_event_clocks_and_does_not_keep_growing_or_zero_diffusion() + { + assert_eq!( + recover_standardised_discrete_drift(0.4, -0.5, 1.0, LagClock::SystemTime), + Err(PsychometricError::EventTimeRequired) + ); + assert_eq!( + recover_standardised_discrete_drift(0.4, -0.5, 0.0, LagClock::EventTime), + Err(PsychometricError::NonPositiveInterval) + ); + let growing = + recover_discrete_lag_from_log_rate(0.5, 1.0, LagClock::EventTime).expect("growing a>0"); + assert!(growing.is_finite() && growing > 1.0); + assert_eq!( + recover_standardised_discrete_drift(0.4, 0.5, 1.0, LagClock::EventTime), + Err(PsychometricError::StationaryVarianceRequiresStableDrift) + ); + let zero_q = + recover_discrete_lag_from_log_rate(-0.5, 1.0, LagClock::EventTime).expect("e^{aΔt} at q=0"); + assert!(zero_q.is_finite() && zero_q > 0.0); + assert_eq!( + recover_standardised_discrete_drift(0.0, -0.5, 1.0, LagClock::EventTime), + Err(PsychometricError::StandardisedDiscreteDriftRequiresPositiveWithinSubjectVariance) + ); +} + +#[test] fn standardised_discrete_continuous_intercept_recovers_driver_page_sixteen_after_positive_p() { let intercept = 0.4_f64; let diffusion = 0.8_f64; diff --git a/crates/psychometric_core/tests/scientific_claim_boundary_contract.rs b/crates/psychometric_core/tests/scientific_claim_boundary_contract.rs index 6ccf7f38b..8d7cc4ed1 100644 --- a/crates/psychometric_core/tests/scientific_claim_boundary_contract.rs +++ b/crates/psychometric_core/tests/scientific_claim_boundary_contract.rs @@ -6,8 +6,9 @@ use psychometric_core::{ recover_asymptotic_continuous_intercept, recover_asymptotic_time_independent_predictor_effect, recover_asymptotic_time_independent_predictor_variance, recover_cluster_mean_within_between_slopes, recover_discrete_constant_predictor_effect, - recover_discrete_continuous_intercept_effect, recover_discrete_lagged_latent_covariance, - recover_discrete_latent_mean, recover_discrete_latent_mean_with_extra_process, + recover_discrete_continuous_intercept_effect, recover_discrete_lag_from_log_rate, + recover_discrete_lagged_latent_covariance, recover_discrete_latent_mean, + recover_discrete_latent_mean_with_extra_process, recover_discrete_latent_mean_with_extra_process_after, recover_discrete_latent_mean_with_impulse, recover_discrete_latent_mean_with_impulse_carry, recover_discrete_latent_mean_with_initial_time_dependent_predictor, @@ -31,17 +32,17 @@ use psychometric_core::{ recover_manifest_observed_variance, recover_manifest_trait_plus_state_observed_variance, recover_standardised_asymptotic_continuous_intercept, recover_standardised_asymptotic_diffusion, 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_trait_variance, recover_standardised_manifest_variance, - recover_standardised_trait_variance, recover_stationary_initial_latent_mean, - recover_stationary_initial_latent_variance, recover_stationary_initial_observed_mean, - recover_stationary_initial_observed_variance, recover_stationary_lagged_latent_covariance, - recover_stationary_lagged_observed_covariance, recover_stationary_latent_variance, - recover_stationary_later_latent_variance, recover_stationary_later_observed_variance, - recover_time_dependent_predictor_impulse, recover_time_dependent_predictor_impulse_carry, - recover_trait_plus_state_lagged_covariance, recover_trait_plus_state_latent_variance, - recover_within_residual_event_time_log_rate, + recover_standardised_discrete_continuous_intercept, recover_standardised_discrete_drift, + recover_standardised_initial_latent_mean, recover_standardised_initial_latent_variance, + recover_standardised_manifest_mean, recover_standardised_manifest_trait_variance, + recover_standardised_manifest_variance, recover_standardised_trait_variance, + recover_stationary_initial_latent_mean, recover_stationary_initial_latent_variance, + recover_stationary_initial_observed_mean, recover_stationary_initial_observed_variance, + recover_stationary_lagged_latent_covariance, recover_stationary_lagged_observed_covariance, + recover_stationary_latent_variance, recover_stationary_later_latent_variance, + recover_stationary_later_observed_variance, recover_time_dependent_predictor_impulse, + recover_time_dependent_predictor_impulse_carry, recover_trait_plus_state_lagged_covariance, + recover_trait_plus_state_latent_variance, recover_within_residual_event_time_log_rate, refuse_after_extra_process_contribution_as_observed_mean, refuse_after_extra_process_latent_mean_as_observed_mean, refuse_asymptotic_continuous_intercept_as_asymptotic_time_independent_effect, @@ -167,13 +168,16 @@ use psychometric_core::{ refuse_time_independent_effect_as_time_varying_discrete_effect, refuse_time_independent_observed_mean_as_initial_time_dependent_observed_mean, refuse_time_independent_observed_mean_as_initial_time_independent_observed_mean, + refuse_trait_plus_state_autocorrelation_as_standardised_discrete_drift, refuse_trait_plus_state_lagged_covariance_as_stationary_lagged_latent_covariance, refuse_trait_scaled_continuous_intercept_as_standardised_continuous_intercept, - refuse_trait_variance_as_process_noise, refuse_trait_variance_as_stationary_within_subject, + refuse_trait_variance_as_process_noise, refuse_trait_variance_as_standardisation_variance, + refuse_trait_variance_as_stationary_within_subject, refuse_unstandardised_asymptotic_continuous_intercept_as_standardised_asymptotic_continuous_intercept, refuse_unstandardised_asymptotic_diffusion_as_standardised_asymptotic_diffusion, refuse_unstandardised_continuous_intercept_as_standardised_continuous_intercept, refuse_unstandardised_discrete_continuous_intercept_as_standardised_discrete_continuous_intercept, + refuse_unstandardised_discrete_drift_as_standardised_discrete_drift, 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, @@ -3455,7 +3459,73 @@ fn standardised_manifest_trait_variance_is_not_unstandardised_traitstd_or_measur } #[test] +fn standardised_discrete_drift_is_not_unstandardised_or_trait_contaminated() { + let diffusion = 0.4_f64; + let log_rate = -0.5_f64; + let event_delta = 1.0_f64; + let recovered = + recover_standardised_discrete_drift(diffusion, log_rate, event_delta, LagClock::EventTime) + .expect("discreteDRIFTstd"); + let unstandardised = + recover_discrete_lag_from_log_rate(log_rate, event_delta, LagClock::EventTime) + .expect("discreteDRIFT"); + let within = recover_stationary_latent_variance(diffusion, log_rate, LagClock::EventTime) + .expect("asymDIFFUSION"); + assert!( + (recovered - unstandardised).abs() < 1e-15, + "Driver et al. (2017, p. 16): scalar discreteDRIFTstd equals exp(a Δt) after positive asymDIFFUSION" + ); + let trait_variance = 1.0_f64; + let lagged = recover_trait_plus_state_lagged_covariance( + trait_variance, + within, + log_rate, + event_delta, + LagClock::EventTime, + ) + .expect("trait+state lag"); + let total = + recover_trait_plus_state_latent_variance(trait_variance, within).expect("trait+state var"); + let contaminated = lagged / total; + assert!( + (contaminated - recovered).abs() > 1e-3, + "Driver et al. (2017, footnote 4 / §7.1): TRAITVAR contaminates the auto-effect" + ); + assert_eq!( + recover_standardised_discrete_drift(0.0, log_rate, event_delta, LagClock::EventTime), + Err( + psychometric_core::PsychometricError::StandardisedDiscreteDriftRequiresPositiveWithinSubjectVariance + ) + ); + assert_eq!( + recover_standardised_discrete_drift(diffusion, 0.5, event_delta, LagClock::EventTime), + Err(psychometric_core::PsychometricError::StationaryVarianceRequiresStableDrift) + ); + assert_eq!( + refuse_unstandardised_discrete_drift_as_standardised_discrete_drift( + unstandardised, + recovered + ), + Err( + psychometric_core::PsychometricError::UnstandardisedDiscreteDriftIsNotStandardisedDiscreteDrift + ) + ); + assert_eq!( + refuse_trait_plus_state_autocorrelation_as_standardised_discrete_drift( + contaminated, + recovered + ), + Err( + psychometric_core::PsychometricError::TraitPlusStateAutocorrelationIsNotStandardisedDiscreteDrift + ) + ); + assert_eq!( + refuse_trait_variance_as_standardisation_variance(trait_variance, within), + Err(psychometric_core::PsychometricError::TraitVarianceIsNotStandardisationVariance) + ); +} +#[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) From ccb1688740a6cc50e0f1e5c1195a597579ba7e7a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 30 Aug 2026 08:23:45 -0700 Subject: [PATCH 004/296] docs(adr): add 0005 posterior esem dsem --- docs/adr/0005-posterior-esem-dsem.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/adr/0005-posterior-esem-dsem.md b/docs/adr/0005-posterior-esem-dsem.md index ee1e6cf0d..36dae717c 100644 --- a/docs/adr/0005-posterior-esem-dsem.md +++ b/docs/adr/0005-posterior-esem-dsem.md @@ -1,8 +1,8 @@ # ADR 0005 — Posterior-aware ESEM/DSEM and structural interpretation **Decision status:** Accepted -**Implementation maturity:** partial — construct classification, valid log-ratio/logistic-normal indicator gates, CPU `f64` OLS and posterior-draw loading point-estimate averaging, Rubin `T_m = Ū_m + (1+1/m) B_m` on draw-level OLS loadings, cluster-mean within/between OLS with the CWC contextual effect and Kish ESS WLS, event-time discrete lag-1 and exact scalar local log-rate, exact scalar forward map and unequal-interval remapping, exact scalar discrete effect of a constant predictor, first-order discrete effect of a time-varying predictor with matched sampling and constancy intervals (Voelkle et al., 2012, Eq. 14), exact scalar discrete process noise (Driver, Oud, & Voelkle, 2017, Eq. 3), exact scalar lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), exact scalar stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; §4.3; p. 16 `asymDIFFUSION`), exact scalar trait-plus-state variance and lagged covariance (Driver et al., 2017, §4.3 `TRAITVAR`; not process noise and not `asymDIFFUSION`), exact scalar observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5, p. 5; Table 2, p. 12; `λ² Var(η) + θ` when `MANIFESTTRAITVAR` is zero, else `λ² Var(η) + θ + ψ`; lagged `λ² cov(η_t, η_{t-1}) + ψ`; `MANIFESTVAR` is `Θ`, not `Var(y)`; `Θ` does not enter lagged observed covariance; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; observed-indicator mean is `τ + λ μ` (`MANIFESTMEANS` is `τ`, not `E(y)`; `CINT` is not `MANIFESTMEANS`; `T0MEANS` is not `E(y)`; Equation 1 is the SDE; not a Kalman filter), exact scalar discrete latent mean `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ` (Driver et al., 2017, Eq. 3; `T0MEANS` is not `μ_t`; `CINT` is not the discrete increment), exact scalar evolved observed-indicator mean `τ + λ μ_t` (Driver et al., 2017, Eq. 5 of that Eq. 3 map; the first-occasion map `τ + λ μ_0` is not `E(y_t)`), exact scalar contemporaneous `TDPREDEFFECT` impulse `m x` (Driver et al., 2017, Eq. 3 fourth summand; Table 2 `TDPREDEFFECT` is `M`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; the §7.2 level-change form is not that impulse), exact scalar observed mean of that contemporaneous impulse `τ + λ(μ_t + m x)` (Driver et al., 2017, Eq. 5 of that Eq. 3 composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`), exact scalar time-independent `TIPREDEFFECT` increment `A^{-1}[e^{A Δt} − I] B z` (Driver et al., 2017, Eq. 3 second summand; Table 2 `TIPREDEFFECT` is `B`, not `κ`, not `M`, and not Voelkle Eq. 14; `B` is not that discrete increment), exact scalar observed mean of that increment `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` (Driver et al., 2017, Eq. 5 of that Eq. 3 printed addend after the `T0MEANS` carry and the `CINT` increment; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`), exact scalar within-interval `TDPREDEFFECT` carry `e^{A(t−u)} M x` for `t0 < u < t` (Driver et al., 2017, Eq. 1–2 Green-function integral of Eq. 2; §7.2 dissipation; not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14), exact scalar observed mean of that carry `τ + λ(μ_t + e^{a(t−u)} m x)` (Driver et al., 2017, Eq. 5 of that carried latent mean; `τ + λ μ_t` is not that observed mean), exact scalar first-occasion `T0TIPREDEFFECT` shift `t0_b z` and Eq. 3 first-summand carry `e^{A Δt} t0_b z` (Driver et al., 2017, Table 3, p. 13; Eq. 3, p. 5; `T0TIPREDEFFECT` is not `TIPREDEFFECT` `B`; `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`; `e^{A Δt} t0_b z` is not `t0_b z`), exact scalar observed mean of that first-occasion carry `τ + λ(μ_t + e^{a Δt} t0_b z)` (Driver et al., 2017, Eq. 5 of that Table 3 / Eq. 3 first-summand composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean), exact scalar first-occasion `T0TDPREDEFFECT` shift `t0_m x0` and Eq. 3 first-summand carry `e^{A Δt} t0_m x0` (Driver et al., 2017, Table 3, p. 13; Eq. 3, p. 5; JSS PDF re-opened 2026-08-20T19:10Z; `T0TDPREDEFFECT` is not `TDPREDEFFECT` `M`; `t0_m x0` is not `M x`; `e^{A Δt} t0_m x0` is not `t0_m x0`; `e^{A Δt} t0_m x0` is not `e^{A(t−u)} M x` for `t0 < u < t`; `t0_m x0` is not `t0_b z`; an impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`), exact scalar observed mean of that first-occasion TD carry `τ + λ(μ_t + e^{a Δt} t0_m x0)` (Driver et al., 2017, Eq. 5 of that Table 3 / Eq. 3 first-summand TD composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean; `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean), exact scalar §7.2 level-change `CINT` `κ = −a m x` (Driver et al., 2017, §7.2, pp. 20–21; `a < 0` so `−κ / a = m x`; not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`, and not the extra near-zero-drift latent process also named in §7.2), exact scalar Eq. 3 increment of that setting `(1 − e^{a Δt}) m x` (not `m x`, not `κ`, and not `TIPREDEFFECT`), exact scalar §7.2 extra near-zero-drift latent process contribution `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-20T23:10Z; identification `TDPREDEFFECT` on the extra process is 1; extra `DRIFT` printed as `−0.000001`; precisely 0 causes computational problems; `ε = a` is `a_{ηξ} x Δt e^{a Δt}`; not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`; `ε ≥ 0` fails closed), exact scalar observed mean of that extra-process contribution `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a))` (Driver et al., 2017, Eq. 5, p. 5; §7.2, pp. 22–23; JSS PDF re-opened 2026-08-21T06:12Z; the extra process has `LAMBDA` 0 and is not an observed indicator; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; the contribution is not `E(y_t)`; the evolved-plus-contribution latent mean is not `E(y_t)`), exact scalar after-t0 extra-process contribution `a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)` for `t0 < u < t` and Eq. 5 observed mean `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a))` (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-21T06:32Z; `T0TDPREDEFFECT` uses `Δt` for both the evolution and the extra drive; `TDPREDEFFECT` after `t0` uses `t − u`; `e^{a(t−u)} m x` is a Dirac on the original process, not this `DRIFT` drive), exact scalar §7.2 `asymTIPREDEFFECT` `-B z / a` (Driver et al., 2017, §7.2, pp. 20–21; JSS PDF opened 2026-08-21T13:08Z; expected total change in process means given a time-independent predictor; `a < 0`; not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; `a ≥ 0` fails closed), exact scalar §7.2 `addedTIPREDVAR` `(B / a)² v` (Driver et al., 2017, §7.2, pp. 20–21; stable between-subject variance accounted for by a time-independent predictor; not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`), exact scalar Table 2 `asymCINT` `-κ / a` (Driver et al., 2017, Table 2, p. 12; Eq. 3 as `Δt → ∞`; `a < 0`; not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; `a ≥ 0` fails closed), exact scalar p. 16 stationary `T0MEANS` `-κ / a + −B z / a` (Driver et al., 2017, p. 16; constrained first-occasion mean using `T0MEANSbase` / `T0MEANSfree`; not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean), exact scalar Eq. 5 of that constrained mean `τ + λ(−κ / a + −B z / a)` (Driver et al., 2017, §4.3, pp. 9–10; Eq. 5, p. 5; JSS PDF re-opened 2026-08-21T20:07Z; form the stationary latent mean first, then `τ + λ` of that mean; `τ + λ μ_0` is not that observed mean; `τ + λ(−κ / a)` is not that observed mean when `B z ≠ 0`; `τ + λ μ_t` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`), exact scalar §4.3 / p. 16 stationary `T0VAR` `trait + −q / (2 a) + (B / a)² v` (Driver et al., 2017, §4.3, pp. 9–10; p. 16; JSS PDF re-opened 2026-08-22T03:07Z; not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`), exact scalar lagged covariance of that constrained process `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T19:13Z; trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map), exact scalar Eq. 5 of that lagged covariance `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ` (`Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance), exact scalar later-occasion variance of that constrained process `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T23:12Z; trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt` and is not that later map; `Q_Δt` is not that later map), exact scalar Eq. 5 of that later-occasion variance `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ` (the lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not that later observed variance; the later-occasion latent variance is not that observed variance)), exact scalar later-occasion variance of §4.3 predetermined `T0VAR` `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T05:12Z; trait and `addedTIPREDVAR` do not enter `Q_Δt`; free `T0VAR` `p_0` is not that later map; setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map; stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map; as `Δt → ∞` with stable `a < 0` the composition approaches contemporaneous stationary `T0VAR`; as `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`; nonzero diffusion with `a ≥ 0` is a growing process and is kept. Eq. 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ` (`MANIFESTVAR` is not that later observed variance; the predetermined later-occasion latent variance is not that observed variance; stationary later observed variance is not that observed variance when `p_0` is free)), exact scalar lagged covariance of §4.3 predetermined `T0VAR` `trait + e^{a Δt} p_0 + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T09:04Z; trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`; free `T0VAR` `p_0` is not that lagged map; setting `p_0 = −q / (2 a)` recovers the stationary lagged map; stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map; later-occasion variance includes `Q_Δt` and is not that lagged map. Eq. 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ` (`MANIFESTVAR` does not enter; the predetermined lagged latent covariance is not that observed covariance; predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance; stationary lagged observed covariance is not that observed covariance when `p_0` is free)), exact scalar first-occasion variance of §4.3 predetermined `T0VAR` `trait + p_0 + (B / a)² v` (free `p_0` is not that map; stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free; lagged covariance decays the state and is not that map; later-occasion variance includes `Q_Δt` and is not that map), exact scalar Eq. 5 of that predetermined first-occasion variance `λ²(trait + p_0 + (B / a)² v) + θ + ψ` (`MANIFESTVAR` is not that first-occasion observed variance; the predetermined first-occasion latent variance is not that observed variance; stationary first-occasion observed variance is not that observed variance when `p_0` is free; predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance; later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z; first-occasion lagged omits `e^{a s} Q_u`; later-occasion variance does not lag; stationary lagged uses `−q / (2 a)`; decaying the later total is not that map; Eq. 5 of that later-start lagged covariance is `λ²` of it plus `ψ`; `Θ` does not enter; first-occasion lagged observed omits `e^{a s} Q_u`; later observed variance includes `Q_u` and `θ`; later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z; later-occasion variance at `u` omits `Q_s`; later-start lagged covariance omits `Q_s`; stationary later uses `−q / (2 a)`; evolving the later total as if it were all state is not that map; ignoring `startoffset` omits `e^{2 a s} Q_u`; Eq. 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`; `MANIFESTVAR` is not that observed variance; p. 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; footnote 4; §7.1; JSS PDF re-opened 2026-08-23T11:40Z; unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`; the trait-plus-state autocorrelation uses `TRAITVAR` and is not `discreteDRIFTstd`; p. 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:06Z; unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`; the continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`; `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z; unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`; the discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`; `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z); unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`; the discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`; `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z; unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`; `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z; unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`; the asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`; `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z; the affected variance is free `T0VAR`, not `asymDIFFUSION`; unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`; `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`; `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`; `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); 2017-era `addedT0TIPREDVAR` is `t0_b² v` (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z; `T0TIPREDEFFECT %*% TIPREDVAR %*% t(T0TIPREDEFFECT)` immediately after `T0TIPREDEFFECTstd`; form `t0_b` first, then square, then multiply by `v`; a zero coefficient or zero predictor variance is exactly zero; free `T0TIPREDEFFECT` does not require `a < 0`; `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map; `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance; free `T0VAR` is not this extra TI variance; `TRAITVAR` is not this extra TI variance; Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z; form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; `t0_b² v` is the latent extra, not the observed extra; `λ² p_0 + θ` is first-occasion observed variance, not this extra; `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra; `MANIFESTVAR` `θ` is not this extra; Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v`; form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; lasting asymptotic extra requires `a < 0`; `(B / a)² v` is the latent extra, not the observed extra; `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra; `λ² p + θ` is stationary observed variance, not this extra; `MANIFESTVAR` `θ` is not this extra; p. 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance; unstandardised `M` is not `TDPREDEFFECTstd`; `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`; intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`; `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`; Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance; unstandardised `t0_m` is not `T0TDPREDEFFECTstd`; `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`; `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`; `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`; free `T0VAR` does not require `a < 0`; p. 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; default ridge is 0); unstandardised `T0VAR` is not `T0VARstd`; `T0TDPREDEFFECTstd` is not `T0VARstd`; `addedT0TIPREDVAR` is not `T0VARstd`; p. 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend); unstandardised `TRAITVAR` is not `TRAITVARstd`; `T0VARstd` is not `TRAITVARstd` even when both equal 1; `addedT0TIPREDVAR` is not `TRAITVARstd`; p. 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0); unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`; `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1; `MANIFESTVAR` is not `MANIFESTTRAITVARstd`; p. 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug); unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`; `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1; Equation 5 `Var(y)` is not `MANIFESTVARstd`; p. 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`); unstandardised `TIPREDVAR` is not `TIPREDVARstd`; `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1; §7.2 `addedTIPREDVAR` is not `TIPREDVARstd`; p. 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`); unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`; `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1; `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`; p. 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `discreteCINT` is not `discreteCINTstd`; `κ / √p` is not `discreteCINTstd`; `(-κ / a) / √p` is not `discreteCINTstd`; `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`; unstandardised `asymCINT` is not `asymCINTstd`; `κ / √p` is not `asymCINTstd`; `discreteCINTstd` is not `asymCINTstd`; `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`; unstandardised `T0MEANS` is not `T0MEANSstd`; `T0VARstd` is not `T0MEANSstd`; `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`; `MANIFESTMEANSstd` is `τ / √θ` after strictly positive `MANIFESTVAR`; unstandardised `MANIFESTMEANS` is not `MANIFESTMEANSstd`; `MANIFESTVARstd` is not `MANIFESTMEANSstd`; `τ / √(λ² Var(η) + θ)` is not `MANIFESTMEANSstd`;))))), CWC-then-event-time residual lag, irregular already-centered residual log-rate, and strong/strict-gated two-group OLS latent-mean difference are implemented on the stacked psychometric PR and are not implemented-main until exact-head checks, review, and protected-main integration complete; full ESEM/set-ESEM, formative composites, DSEM, and matrix continuous-time dynamics remain accepted-target -**Implementation maturity:** partial — construct classification, valid log-ratio/logistic-normal indicator gates, CPU `f64` OLS and posterior-draw loading point-estimate averaging, Rubin `T_m = Ū_m + (1+1/m) B_m` on draw-level OLS loadings, cluster-mean within/between OLS with the CWC contextual effect and Kish ESS WLS, event-time discrete lag-1 and exact scalar local log-rate, exact scalar forward map and unequal-interval remapping, exact scalar discrete effect of a constant predictor, first-order discrete effect of a time-varying predictor with matched sampling and constancy intervals (Voelkle et al., 2012, Eq. 14), exact scalar discrete process noise (Driver, Oud, & Voelkle, 2017, Eq. 3), exact scalar lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), exact scalar stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; §4.3; p. 16 `asymDIFFUSION`), exact scalar trait-plus-state variance and lagged covariance (Driver et al., 2017, §4.3 `TRAITVAR`; not process noise and not `asymDIFFUSION`), exact scalar observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5, p. 5; Table 2, p. 12; `λ² Var(η) + θ` when `MANIFESTTRAITVAR` is zero, else `λ² Var(η) + θ + ψ`; lagged `λ² cov(η_t, η_{t-1}) + ψ`; `MANIFESTVAR` is `Θ`, not `Var(y)`; `Θ` does not enter lagged observed covariance; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; observed-indicator mean is `τ + λ μ` (`MANIFESTMEANS` is `τ`, not `E(y)`; `CINT` is not `MANIFESTMEANS`; `T0MEANS` is not `E(y)`; Equation 1 is the SDE; not a Kalman filter), exact scalar discrete latent mean `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ` (Driver et al., 2017, Eq. 3; `T0MEANS` is not `μ_t`; `CINT` is not the discrete increment), exact scalar evolved observed-indicator mean `τ + λ μ_t` (Driver et al., 2017, Eq. 5 of that Eq. 3 map; the first-occasion map `τ + λ μ_0` is not `E(y_t)`), exact scalar contemporaneous `TDPREDEFFECT` impulse `m x` (Driver et al., 2017, Eq. 3 fourth summand; Table 2 `TDPREDEFFECT` is `M`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; the §7.2 level-change form is not that impulse), exact scalar observed mean of that contemporaneous impulse `τ + λ(μ_t + m x)` (Driver et al., 2017, Eq. 5 of that Eq. 3 composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`), exact scalar time-independent `TIPREDEFFECT` increment `A^{-1}[e^{A Δt} − I] B z` (Driver et al., 2017, Eq. 3 second summand; Table 2 `TIPREDEFFECT` is `B`, not `κ`, not `M`, and not Voelkle Eq. 14; `B` is not that discrete increment), exact scalar observed mean of that increment `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` (Driver et al., 2017, Eq. 5 of that Eq. 3 printed addend after the `T0MEANS` carry and the `CINT` increment; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`), exact scalar within-interval `TDPREDEFFECT` carry `e^{A(t−u)} M x` for `t0 < u < t` (Driver et al., 2017, Eq. 1–2 Green-function integral of Eq. 2; §7.2 dissipation; not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14), exact scalar observed mean of that carry `τ + λ(μ_t + e^{a(t−u)} m x)` (Driver et al., 2017, Eq. 5 of that carried latent mean; `τ + λ μ_t` is not that observed mean), exact scalar first-occasion `T0TIPREDEFFECT` shift `t0_b z` and Eq. 3 first-summand carry `e^{A Δt} t0_b z` (Driver et al., 2017, Table 3, p. 13; Eq. 3, p. 5; `T0TIPREDEFFECT` is not `TIPREDEFFECT` `B`; `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`; `e^{A Δt} t0_b z` is not `t0_b z`), exact scalar observed mean of that first-occasion carry `τ + λ(μ_t + e^{a Δt} t0_b z)` (Driver et al., 2017, Eq. 5 of that Table 3 / Eq. 3 first-summand composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean), exact scalar first-occasion `T0TDPREDEFFECT` shift `t0_m x0` and Eq. 3 first-summand carry `e^{A Δt} t0_m x0` (Driver et al., 2017, Table 3, p. 13; Eq. 3, p. 5; JSS PDF re-opened 2026-08-20T19:10Z; `T0TDPREDEFFECT` is not `TDPREDEFFECT` `M`; `t0_m x0` is not `M x`; `e^{A Δt} t0_m x0` is not `t0_m x0`; `e^{A Δt} t0_m x0` is not `e^{A(t−u)} M x` for `t0 < u < t`; `t0_m x0` is not `t0_b z`; an impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`), exact scalar observed mean of that first-occasion TD carry `τ + λ(μ_t + e^{a Δt} t0_m x0)` (Driver et al., 2017, Eq. 5 of that Table 3 / Eq. 3 first-summand TD composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean; `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean), exact scalar §7.2 level-change `CINT` `κ = −a m x` (Driver et al., 2017, §7.2, pp. 20–21; `a < 0` so `−κ / a = m x`; not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`, and not the extra near-zero-drift latent process also named in §7.2), exact scalar Eq. 3 increment of that setting `(1 − e^{a Δt}) m x` (not `m x`, not `κ`, and not `TIPREDEFFECT`), exact scalar §7.2 extra near-zero-drift latent process contribution `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-20T23:10Z; identification `TDPREDEFFECT` on the extra process is 1; extra `DRIFT` printed as `−0.000001`; precisely 0 causes computational problems; `ε = a` is `a_{ηξ} x Δt e^{a Δt}`; not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`; `ε ≥ 0` fails closed), exact scalar observed mean of that extra-process contribution `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a))` (Driver et al., 2017, Eq. 5, p. 5; §7.2, pp. 22–23; JSS PDF re-opened 2026-08-21T06:12Z; the extra process has `LAMBDA` 0 and is not an observed indicator; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; the contribution is not `E(y_t)`; the evolved-plus-contribution latent mean is not `E(y_t)`), exact scalar after-t0 extra-process contribution `a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)` for `t0 < u < t` and Eq. 5 observed mean `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a))` (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-21T06:32Z; `T0TDPREDEFFECT` uses `Δt` for both the evolution and the extra drive; `TDPREDEFFECT` after `t0` uses `t − u`; `e^{a(t−u)} m x` is a Dirac on the original process, not this `DRIFT` drive), exact scalar §7.2 `asymTIPREDEFFECT` `-B z / a` (Driver et al., 2017, §7.2, pp. 20–21; JSS PDF opened 2026-08-21T13:08Z; expected total change in process means given a time-independent predictor; `a < 0`; not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; `a ≥ 0` fails closed), exact scalar §7.2 `addedTIPREDVAR` `(B / a)² v` (Driver et al., 2017, §7.2, pp. 20–21; stable between-subject variance accounted for by a time-independent predictor; not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`), exact scalar Table 2 `asymCINT` `-κ / a` (Driver et al., 2017, Table 2, p. 12; Eq. 3 as `Δt → ∞`; `a < 0`; not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; `a ≥ 0` fails closed), exact scalar p. 16 stationary `T0MEANS` `-κ / a + −B z / a` (Driver et al., 2017, p. 16; constrained first-occasion mean using `T0MEANSbase` / `T0MEANSfree`; not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean), exact scalar Eq. 5 of that constrained mean `τ + λ(−κ / a + −B z / a)` (Driver et al., 2017, §4.3, pp. 9–10; Eq. 5, p. 5; JSS PDF re-opened 2026-08-21T20:07Z; form the stationary latent mean first, then `τ + λ` of that mean; `τ + λ μ_0` is not that observed mean; `τ + λ(−κ / a)` is not that observed mean when `B z ≠ 0`; `τ + λ μ_t` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`), exact scalar §4.3 / p. 16 stationary `T0VAR` `trait + −q / (2 a) + (B / a)² v` (Driver et al., 2017, §4.3, pp. 9–10; p. 16; JSS PDF re-opened 2026-08-22T03:07Z; not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`), exact scalar lagged covariance of that constrained process `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T19:13Z; trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map), exact scalar Eq. 5 of that lagged covariance `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ` (`Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance), exact scalar later-occasion variance of that constrained process `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T23:12Z; trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt` and is not that later map; `Q_Δt` is not that later map), exact scalar Eq. 5 of that later-occasion variance `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ` (the lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not that later observed variance; the later-occasion latent variance is not that observed variance)), exact scalar later-occasion variance of §4.3 predetermined `T0VAR` `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T05:12Z; trait and `addedTIPREDVAR` do not enter `Q_Δt`; free `T0VAR` `p_0` is not that later map; setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map; stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map; as `Δt → ∞` with stable `a < 0` the composition approaches contemporaneous stationary `T0VAR`; as `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`; nonzero diffusion with `a ≥ 0` is a growing process and is kept. Eq. 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ` (`MANIFESTVAR` is not that later observed variance; the predetermined later-occasion latent variance is not that observed variance; stationary later observed variance is not that observed variance when `p_0` is free)), exact scalar lagged covariance of §4.3 predetermined `T0VAR` `trait + e^{a Δt} p_0 + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T09:04Z; trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`; free `T0VAR` `p_0` is not that lagged map; setting `p_0 = −q / (2 a)` recovers the stationary lagged map; stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map; later-occasion variance includes `Q_Δt` and is not that lagged map. Eq. 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ` (`MANIFESTVAR` does not enter; the predetermined lagged latent covariance is not that observed covariance; predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance; stationary lagged observed covariance is not that observed covariance when `p_0` is free)), exact scalar first-occasion variance of §4.3 predetermined `T0VAR` `trait + p_0 + (B / a)² v` (free `p_0` is not that map; stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free; lagged covariance decays the state and is not that map; later-occasion variance includes `Q_Δt` and is not that map), exact scalar Eq. 5 of that predetermined first-occasion variance `λ²(trait + p_0 + (B / a)² v) + θ + ψ` (`MANIFESTVAR` is not that first-occasion observed variance; the predetermined first-occasion latent variance is not that observed variance; stationary first-occasion observed variance is not that observed variance when `p_0` is free; predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance; later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z; first-occasion lagged omits `e^{a s} Q_u`; later-occasion variance does not lag; stationary lagged uses `−q / (2 a)`; decaying the later total is not that map; Eq. 5 of that later-start lagged covariance is `λ²` of it plus `ψ`; `Θ` does not enter; first-occasion lagged observed omits `e^{a s} Q_u`; later observed variance includes `Q_u` and `θ`; later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z; later-occasion variance at `u` omits `Q_s`; later-start lagged covariance omits `Q_s`; stationary later uses `−q / (2 a)`; evolving the later total as if it were all state is not that map; ignoring `startoffset` omits `e^{2 a s} Q_u`; Eq. 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`; `MANIFESTVAR` is not that observed variance; p. 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; footnote 4; §7.1; JSS PDF re-opened 2026-08-23T11:40Z; unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`; the trait-plus-state autocorrelation uses `TRAITVAR` and is not `discreteDRIFTstd`; p. 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:06Z; unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`; the continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`; `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z; unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`; the discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`; `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z); unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`; the discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`; `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z; unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`; `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z; unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`; the asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`; `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z; the affected variance is free `T0VAR`, not `asymDIFFUSION`; unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`; `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`; `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`; `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); 2017-era `addedT0TIPREDVAR` is `t0_b² v` (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z; `T0TIPREDEFFECT %*% TIPREDVAR %*% t(T0TIPREDEFFECT)` immediately after `T0TIPREDEFFECTstd`; form `t0_b` first, then square, then multiply by `v`; a zero coefficient or zero predictor variance is exactly zero; free `T0TIPREDEFFECT` does not require `a < 0`; `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map; `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance; free `T0VAR` is not this extra TI variance; `TRAITVAR` is not this extra TI variance; Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z; form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; `t0_b² v` is the latent extra, not the observed extra; `λ² p_0 + θ` is first-occasion observed variance, not this extra; `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra; `MANIFESTVAR` `θ` is not this extra; Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v`; form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; lasting asymptotic extra requires `a < 0`; `(B / a)² v` is the latent extra, not the observed extra; `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra; `λ² p + θ` is stationary observed variance, not this extra; `MANIFESTVAR` `θ` is not this extra; p. 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance; unstandardised `M` is not `TDPREDEFFECTstd`; `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`; intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`; `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`; Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance; unstandardised `t0_m` is not `T0TDPREDEFFECTstd`; `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`; `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`; `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`; free `T0VAR` does not require `a < 0`; p. 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; default ridge is 0); unstandardised `T0VAR` is not `T0VARstd`; `T0TDPREDEFFECTstd` is not `T0VARstd`; `addedT0TIPREDVAR` is not `T0VARstd`; p. 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend); unstandardised `TRAITVAR` is not `TRAITVARstd`; `T0VARstd` is not `TRAITVARstd` even when both equal 1; `addedT0TIPREDVAR` is not `TRAITVARstd`; p. 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0); unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`; `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1; `MANIFESTVAR` is not `MANIFESTTRAITVARstd`; p. 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug); unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`; `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1; Equation 5 `Var(y)` is not `MANIFESTVARstd`; p. 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`); unstandardised `TIPREDVAR` is not `TIPREDVARstd`; `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1; §7.2 `addedTIPREDVAR` is not `TIPREDVARstd`; p. 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`); unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`; `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1; `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`; p. 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `discreteCINT` is not `discreteCINTstd`; `κ / √p` is not `discreteCINTstd`; `(-κ / a) / √p` is not `discreteCINTstd`; `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`; unstandardised `asymCINT` is not `asymCINTstd`; `κ / √p` is not `asymCINTstd`; `discreteCINTstd` is not `asymCINTstd`; `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`; unstandardised `T0MEANS` is not `T0MEANSstd`; `T0VARstd` is not `T0MEANSstd`; `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`;))))), CWC-then-event-time residual lag, irregular already-centered residual log-rate, and strong/strict-gated two-group OLS latent-mean difference are implemented on the consolidation vehicle PR `integration/psychometric-standardisation` (folding draft stack #181–#218) and are not implemented-main until exact-head checks, review, and protected-main integration complete; full ESEM/set-ESEM, formative composites, DSEM, and matrix continuous-time dynamics remain accepted-target +**Implementation maturity:** partial — construct classification, valid log-ratio/logistic-normal indicator gates, CPU `f64` OLS and posterior-draw loading point-estimate averaging, Rubin `T_m = Ū_m + (1+1/m) B_m` on draw-level OLS loadings, cluster-mean within/between OLS with the CWC contextual effect and Kish ESS WLS, event-time discrete lag-1 and exact scalar local log-rate, exact scalar forward map and unequal-interval remapping, exact scalar discrete effect of a constant predictor, first-order discrete effect of a time-varying predictor with matched sampling and constancy intervals (Voelkle et al., 2012, Eq. 14), exact scalar discrete process noise (Driver, Oud, & Voelkle, 2017, Eq. 3), exact scalar lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), exact scalar stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; §4.3; p. 16 `asymDIFFUSION`), exact scalar trait-plus-state variance and lagged covariance (Driver et al., 2017, §4.3 `TRAITVAR`; not process noise and not `asymDIFFUSION`), exact scalar observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5, p. 5; Table 2, p. 12; `λ² Var(η) + θ` when `MANIFESTTRAITVAR` is zero, else `λ² Var(η) + θ + ψ`; lagged `λ² cov(η_t, η_{t-1}) + ψ`; `MANIFESTVAR` is `Θ`, not `Var(y)`; `Θ` does not enter lagged observed covariance; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; observed-indicator mean is `τ + λ μ` (`MANIFESTMEANS` is `τ`, not `E(y)`; `CINT` is not `MANIFESTMEANS`; `T0MEANS` is not `E(y)`; Equation 1 is the SDE; not a Kalman filter), exact scalar discrete latent mean `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ` (Driver et al., 2017, Eq. 3; `T0MEANS` is not `μ_t`; `CINT` is not the discrete increment), exact scalar evolved observed-indicator mean `τ + λ μ_t` (Driver et al., 2017, Eq. 5 of that Eq. 3 map; the first-occasion map `τ + λ μ_0` is not `E(y_t)`), exact scalar contemporaneous `TDPREDEFFECT` impulse `m x` (Driver et al., 2017, Eq. 3 fourth summand; Table 2 `TDPREDEFFECT` is `M`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; the §7.2 level-change form is not that impulse), exact scalar observed mean of that contemporaneous impulse `τ + λ(μ_t + m x)` (Driver et al., 2017, Eq. 5 of that Eq. 3 composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`), exact scalar time-independent `TIPREDEFFECT` increment `A^{-1}[e^{A Δt} − I] B z` (Driver et al., 2017, Eq. 3 second summand; Table 2 `TIPREDEFFECT` is `B`, not `κ`, not `M`, and not Voelkle Eq. 14; `B` is not that discrete increment), exact scalar observed mean of that increment `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` (Driver et al., 2017, Eq. 5 of that Eq. 3 printed addend after the `T0MEANS` carry and the `CINT` increment; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`), exact scalar within-interval `TDPREDEFFECT` carry `e^{A(t−u)} M x` for `t0 < u < t` (Driver et al., 2017, Eq. 1–2 Green-function integral of Eq. 2; §7.2 dissipation; not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14), exact scalar observed mean of that carry `τ + λ(μ_t + e^{a(t−u)} m x)` (Driver et al., 2017, Eq. 5 of that carried latent mean; `τ + λ μ_t` is not that observed mean), exact scalar first-occasion `T0TIPREDEFFECT` shift `t0_b z` and Eq. 3 first-summand carry `e^{A Δt} t0_b z` (Driver et al., 2017, Table 3, p. 13; Eq. 3, p. 5; `T0TIPREDEFFECT` is not `TIPREDEFFECT` `B`; `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`; `e^{A Δt} t0_b z` is not `t0_b z`), exact scalar observed mean of that first-occasion carry `τ + λ(μ_t + e^{a Δt} t0_b z)` (Driver et al., 2017, Eq. 5 of that Table 3 / Eq. 3 first-summand composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean), exact scalar first-occasion `T0TDPREDEFFECT` shift `t0_m x0` and Eq. 3 first-summand carry `e^{A Δt} t0_m x0` (Driver et al., 2017, Table 3, p. 13; Eq. 3, p. 5; JSS PDF re-opened 2026-08-20T19:10Z; `T0TDPREDEFFECT` is not `TDPREDEFFECT` `M`; `t0_m x0` is not `M x`; `e^{A Δt} t0_m x0` is not `t0_m x0`; `e^{A Δt} t0_m x0` is not `e^{A(t−u)} M x` for `t0 < u < t`; `t0_m x0` is not `t0_b z`; an impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`), exact scalar observed mean of that first-occasion TD carry `τ + λ(μ_t + e^{a Δt} t0_m x0)` (Driver et al., 2017, Eq. 5 of that Table 3 / Eq. 3 first-summand TD composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean; `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean), exact scalar §7.2 level-change `CINT` `κ = −a m x` (Driver et al., 2017, §7.2, pp. 20–21; `a < 0` so `−κ / a = m x`; not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`, and not the extra near-zero-drift latent process also named in §7.2), exact scalar Eq. 3 increment of that setting `(1 − e^{a Δt}) m x` (not `m x`, not `κ`, and not `TIPREDEFFECT`), exact scalar §7.2 extra near-zero-drift latent process contribution `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-20T23:10Z; identification `TDPREDEFFECT` on the extra process is 1; extra `DRIFT` printed as `−0.000001`; precisely 0 causes computational problems; `ε = a` is `a_{ηξ} x Δt e^{a Δt}`; not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`; `ε ≥ 0` fails closed), exact scalar observed mean of that extra-process contribution `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a))` (Driver et al., 2017, Eq. 5, p. 5; §7.2, pp. 22–23; JSS PDF re-opened 2026-08-21T06:12Z; the extra process has `LAMBDA` 0 and is not an observed indicator; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; the contribution is not `E(y_t)`; the evolved-plus-contribution latent mean is not `E(y_t)`), exact scalar after-t0 extra-process contribution `a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)` for `t0 < u < t` and Eq. 5 observed mean `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a))` (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-21T06:32Z; `T0TDPREDEFFECT` uses `Δt` for both the evolution and the extra drive; `TDPREDEFFECT` after `t0` uses `t − u`; `e^{a(t−u)} m x` is a Dirac on the original process, not this `DRIFT` drive), exact scalar §7.2 `asymTIPREDEFFECT` `-B z / a` (Driver et al., 2017, §7.2, pp. 20–21; JSS PDF opened 2026-08-21T13:08Z; expected total change in process means given a time-independent predictor; `a < 0`; not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; `a ≥ 0` fails closed), exact scalar §7.2 `addedTIPREDVAR` `(B / a)² v` (Driver et al., 2017, §7.2, pp. 20–21; stable between-subject variance accounted for by a time-independent predictor; not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`), exact scalar Table 2 `asymCINT` `-κ / a` (Driver et al., 2017, Table 2, p. 12; Eq. 3 as `Δt → ∞`; `a < 0`; not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; `a ≥ 0` fails closed), exact scalar p. 16 stationary `T0MEANS` `-κ / a + −B z / a` (Driver et al., 2017, p. 16; constrained first-occasion mean using `T0MEANSbase` / `T0MEANSfree`; not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean), exact scalar Eq. 5 of that constrained mean `τ + λ(−κ / a + −B z / a)` (Driver et al., 2017, §4.3, pp. 9–10; Eq. 5, p. 5; JSS PDF re-opened 2026-08-21T20:07Z; form the stationary latent mean first, then `τ + λ` of that mean; `τ + λ μ_0` is not that observed mean; `τ + λ(−κ / a)` is not that observed mean when `B z ≠ 0`; `τ + λ μ_t` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`), exact scalar §4.3 / p. 16 stationary `T0VAR` `trait + −q / (2 a) + (B / a)² v` (Driver et al., 2017, §4.3, pp. 9–10; p. 16; JSS PDF re-opened 2026-08-22T03:07Z; not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`), exact scalar lagged covariance of that constrained process `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T19:13Z; trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map), exact scalar Eq. 5 of that lagged covariance `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ` (`Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance), exact scalar later-occasion variance of that constrained process `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T23:12Z; trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt` and is not that later map; `Q_Δt` is not that later map), exact scalar Eq. 5 of that later-occasion variance `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ` (the lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not that later observed variance; the later-occasion latent variance is not that observed variance)), exact scalar later-occasion variance of §4.3 predetermined `T0VAR` `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T05:12Z; trait and `addedTIPREDVAR` do not enter `Q_Δt`; free `T0VAR` `p_0` is not that later map; setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map; stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map; as `Δt → ∞` with stable `a < 0` the composition approaches contemporaneous stationary `T0VAR`; as `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`; nonzero diffusion with `a ≥ 0` is a growing process and is kept. Eq. 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ` (`MANIFESTVAR` is not that later observed variance; the predetermined later-occasion latent variance is not that observed variance; stationary later observed variance is not that observed variance when `p_0` is free)), exact scalar lagged covariance of §4.3 predetermined `T0VAR` `trait + e^{a Δt} p_0 + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T09:04Z; trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`; free `T0VAR` `p_0` is not that lagged map; setting `p_0 = −q / (2 a)` recovers the stationary lagged map; stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map; later-occasion variance includes `Q_Δt` and is not that lagged map. Eq. 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ` (`MANIFESTVAR` does not enter; the predetermined lagged latent covariance is not that observed covariance; predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance; stationary lagged observed covariance is not that observed covariance when `p_0` is free)), exact scalar first-occasion variance of §4.3 predetermined `T0VAR` `trait + p_0 + (B / a)² v` (free `p_0` is not that map; stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free; lagged covariance decays the state and is not that map; later-occasion variance includes `Q_Δt` and is not that map), exact scalar Eq. 5 of that predetermined first-occasion variance `λ²(trait + p_0 + (B / a)² v) + θ + ψ` (`MANIFESTVAR` is not that first-occasion observed variance; the predetermined first-occasion latent variance is not that observed variance; stationary first-occasion observed variance is not that observed variance when `p_0` is free; predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance; later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z; first-occasion lagged omits `e^{a s} Q_u`; later-occasion variance does not lag; stationary lagged uses `−q / (2 a)`; decaying the later total is not that map; Eq. 5 of that later-start lagged covariance is `λ²` of it plus `ψ`; `Θ` does not enter; first-occasion lagged observed omits `e^{a s} Q_u`; later observed variance includes `Q_u` and `θ`; later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z; later-occasion variance at `u` omits `Q_s`; later-start lagged covariance omits `Q_s`; stationary later uses `−q / (2 a)`; evolving the later total as if it were all state is not that map; ignoring `startoffset` omits `e^{2 a s} Q_u`; Eq. 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`; `MANIFESTVAR` is not that observed variance; p. 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; footnote 4; §7.1; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-27T17:25Z; unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`; the trait-plus-state autocorrelation uses `TRAITVAR` and is not `discreteDRIFTstd`; p. 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:06Z; unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`; the continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`; `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z; unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`; the discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`; `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z); unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`; the discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`; `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z; unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`; `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z; unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`; the asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`; `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z; the affected variance is free `T0VAR`, not `asymDIFFUSION`; unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`; `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`; `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`; `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); 2017-era `addedT0TIPREDVAR` is `t0_b² v` (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z; `T0TIPREDEFFECT %*% TIPREDVAR %*% t(T0TIPREDEFFECT)` immediately after `T0TIPREDEFFECTstd`; form `t0_b` first, then square, then multiply by `v`; a zero coefficient or zero predictor variance is exactly zero; free `T0TIPREDEFFECT` does not require `a < 0`; `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map; `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance; free `T0VAR` is not this extra TI variance; `TRAITVAR` is not this extra TI variance; Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z; form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; `t0_b² v` is the latent extra, not the observed extra; `λ² p_0 + θ` is first-occasion observed variance, not this extra; `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra; `MANIFESTVAR` `θ` is not this extra; Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v`; form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; lasting asymptotic extra requires `a < 0`; `(B / a)² v` is the latent extra, not the observed extra; `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra; `λ² p + θ` is stationary observed variance, not this extra; `MANIFESTVAR` `θ` is not this extra; p. 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance; unstandardised `M` is not `TDPREDEFFECTstd`; `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`; intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`; `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`; Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance; unstandardised `t0_m` is not `T0TDPREDEFFECTstd`; `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`; `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`; `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`; free `T0VAR` does not require `a < 0`; p. 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; default ridge is 0); unstandardised `T0VAR` is not `T0VARstd`; `T0TDPREDEFFECTstd` is not `T0VARstd`; `addedT0TIPREDVAR` is not `T0VARstd`; p. 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend); unstandardised `TRAITVAR` is not `TRAITVARstd`; `T0VARstd` is not `TRAITVARstd` even when both equal 1; `addedT0TIPREDVAR` is not `TRAITVARstd`; p. 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0); unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`; `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1; `MANIFESTVAR` is not `MANIFESTTRAITVARstd`; p. 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug); unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`; `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1; Equation 5 `Var(y)` is not `MANIFESTVARstd`; p. 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`); unstandardised `TIPREDVAR` is not `TIPREDVARstd`; `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1; §7.2 `addedTIPREDVAR` is not `TIPREDVARstd`; p. 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`); unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`; `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1; `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`; p. 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `discreteCINT` is not `discreteCINTstd`; `κ / √p` is not `discreteCINTstd`; `(-κ / a) / √p` is not `discreteCINTstd`; `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`; unstandardised `asymCINT` is not `asymCINTstd`; `κ / √p` is not `asymCINTstd`; `discreteCINTstd` is not `asymCINTstd`; `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`; unstandardised `T0MEANS` is not `T0MEANSstd`; `T0VARstd` is not `T0MEANSstd`; `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`; `MANIFESTMEANSstd` is `τ / √θ` after strictly positive `MANIFESTVAR`; unstandardised `MANIFESTMEANS` is not `MANIFESTMEANSstd`; `MANIFESTVARstd` is not `MANIFESTMEANSstd`; `τ / √(λ² Var(η) + θ)` is not `MANIFESTMEANSstd`;))))), CWC-then-event-time residual lag, irregular already-centered residual log-rate, and strong/strict-gated two-group OLS latent-mean difference are implemented on the stacked psychometric PR and are not implemented-main until exact-head checks, review, and protected-main integration complete; full ESEM/set-ESEM, formative composites, DSEM, and matrix continuous-time dynamics remain accepted-target +**Implementation maturity:** partial — construct classification, valid log-ratio/logistic-normal indicator gates, CPU `f64` OLS and posterior-draw loading point-estimate averaging, Rubin `T_m = Ū_m + (1+1/m) B_m` on draw-level OLS loadings, cluster-mean within/between OLS with the CWC contextual effect and Kish ESS WLS, event-time discrete lag-1 and exact scalar local log-rate, exact scalar forward map and unequal-interval remapping, exact scalar discrete effect of a constant predictor, first-order discrete effect of a time-varying predictor with matched sampling and constancy intervals (Voelkle et al., 2012, Eq. 14), exact scalar discrete process noise (Driver, Oud, & Voelkle, 2017, Eq. 3), exact scalar lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), exact scalar stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; §4.3; p. 16 `asymDIFFUSION`), exact scalar trait-plus-state variance and lagged covariance (Driver et al., 2017, §4.3 `TRAITVAR`; not process noise and not `asymDIFFUSION`), exact scalar observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5, p. 5; Table 2, p. 12; `λ² Var(η) + θ` when `MANIFESTTRAITVAR` is zero, else `λ² Var(η) + θ + ψ`; lagged `λ² cov(η_t, η_{t-1}) + ψ`; `MANIFESTVAR` is `Θ`, not `Var(y)`; `Θ` does not enter lagged observed covariance; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; observed-indicator mean is `τ + λ μ` (`MANIFESTMEANS` is `τ`, not `E(y)`; `CINT` is not `MANIFESTMEANS`; `T0MEANS` is not `E(y)`; Equation 1 is the SDE; not a Kalman filter), exact scalar discrete latent mean `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ` (Driver et al., 2017, Eq. 3; `T0MEANS` is not `μ_t`; `CINT` is not the discrete increment), exact scalar evolved observed-indicator mean `τ + λ μ_t` (Driver et al., 2017, Eq. 5 of that Eq. 3 map; the first-occasion map `τ + λ μ_0` is not `E(y_t)`), exact scalar contemporaneous `TDPREDEFFECT` impulse `m x` (Driver et al., 2017, Eq. 3 fourth summand; Table 2 `TDPREDEFFECT` is `M`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; the §7.2 level-change form is not that impulse), exact scalar observed mean of that contemporaneous impulse `τ + λ(μ_t + m x)` (Driver et al., 2017, Eq. 5 of that Eq. 3 composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`), exact scalar time-independent `TIPREDEFFECT` increment `A^{-1}[e^{A Δt} − I] B z` (Driver et al., 2017, Eq. 3 second summand; Table 2 `TIPREDEFFECT` is `B`, not `κ`, not `M`, and not Voelkle Eq. 14; `B` is not that discrete increment), exact scalar observed mean of that increment `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` (Driver et al., 2017, Eq. 5 of that Eq. 3 printed addend after the `T0MEANS` carry and the `CINT` increment; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`), exact scalar within-interval `TDPREDEFFECT` carry `e^{A(t−u)} M x` for `t0 < u < t` (Driver et al., 2017, Eq. 1–2 Green-function integral of Eq. 2; §7.2 dissipation; not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14), exact scalar observed mean of that carry `τ + λ(μ_t + e^{a(t−u)} m x)` (Driver et al., 2017, Eq. 5 of that carried latent mean; `τ + λ μ_t` is not that observed mean), exact scalar first-occasion `T0TIPREDEFFECT` shift `t0_b z` and Eq. 3 first-summand carry `e^{A Δt} t0_b z` (Driver et al., 2017, Table 3, p. 13; Eq. 3, p. 5; `T0TIPREDEFFECT` is not `TIPREDEFFECT` `B`; `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`; `e^{A Δt} t0_b z` is not `t0_b z`), exact scalar observed mean of that first-occasion carry `τ + λ(μ_t + e^{a Δt} t0_b z)` (Driver et al., 2017, Eq. 5 of that Table 3 / Eq. 3 first-summand composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean), exact scalar first-occasion `T0TDPREDEFFECT` shift `t0_m x0` and Eq. 3 first-summand carry `e^{A Δt} t0_m x0` (Driver et al., 2017, Table 3, p. 13; Eq. 3, p. 5; JSS PDF re-opened 2026-08-20T19:10Z; `T0TDPREDEFFECT` is not `TDPREDEFFECT` `M`; `t0_m x0` is not `M x`; `e^{A Δt} t0_m x0` is not `t0_m x0`; `e^{A Δt} t0_m x0` is not `e^{A(t−u)} M x` for `t0 < u < t`; `t0_m x0` is not `t0_b z`; an impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`), exact scalar observed mean of that first-occasion TD carry `τ + λ(μ_t + e^{a Δt} t0_m x0)` (Driver et al., 2017, Eq. 5 of that Table 3 / Eq. 3 first-summand TD composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean; `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean), exact scalar §7.2 level-change `CINT` `κ = −a m x` (Driver et al., 2017, §7.2, pp. 20–21; `a < 0` so `−κ / a = m x`; not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`, and not the extra near-zero-drift latent process also named in §7.2), exact scalar Eq. 3 increment of that setting `(1 − e^{a Δt}) m x` (not `m x`, not `κ`, and not `TIPREDEFFECT`), exact scalar §7.2 extra near-zero-drift latent process contribution `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-20T23:10Z; identification `TDPREDEFFECT` on the extra process is 1; extra `DRIFT` printed as `−0.000001`; precisely 0 causes computational problems; `ε = a` is `a_{ηξ} x Δt e^{a Δt}`; not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`; `ε ≥ 0` fails closed), exact scalar observed mean of that extra-process contribution `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a))` (Driver et al., 2017, Eq. 5, p. 5; §7.2, pp. 22–23; JSS PDF re-opened 2026-08-21T06:12Z; the extra process has `LAMBDA` 0 and is not an observed indicator; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; the contribution is not `E(y_t)`; the evolved-plus-contribution latent mean is not `E(y_t)`), exact scalar after-t0 extra-process contribution `a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)` for `t0 < u < t` and Eq. 5 observed mean `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a))` (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-21T06:32Z; `T0TDPREDEFFECT` uses `Δt` for both the evolution and the extra drive; `TDPREDEFFECT` after `t0` uses `t − u`; `e^{a(t−u)} m x` is a Dirac on the original process, not this `DRIFT` drive), exact scalar §7.2 `asymTIPREDEFFECT` `-B z / a` (Driver et al., 2017, §7.2, pp. 20–21; JSS PDF opened 2026-08-21T13:08Z; expected total change in process means given a time-independent predictor; `a < 0`; not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; `a ≥ 0` fails closed), exact scalar §7.2 `addedTIPREDVAR` `(B / a)² v` (Driver et al., 2017, §7.2, pp. 20–21; stable between-subject variance accounted for by a time-independent predictor; not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`), exact scalar Table 2 `asymCINT` `-κ / a` (Driver et al., 2017, Table 2, p. 12; Eq. 3 as `Δt → ∞`; `a < 0`; not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; `a ≥ 0` fails closed), exact scalar p. 16 stationary `T0MEANS` `-κ / a + −B z / a` (Driver et al., 2017, p. 16; constrained first-occasion mean using `T0MEANSbase` / `T0MEANSfree`; not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean), exact scalar Eq. 5 of that constrained mean `τ + λ(−κ / a + −B z / a)` (Driver et al., 2017, §4.3, pp. 9–10; Eq. 5, p. 5; JSS PDF re-opened 2026-08-21T20:07Z; form the stationary latent mean first, then `τ + λ` of that mean; `τ + λ μ_0` is not that observed mean; `τ + λ(−κ / a)` is not that observed mean when `B z ≠ 0`; `τ + λ μ_t` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`), exact scalar §4.3 / p. 16 stationary `T0VAR` `trait + −q / (2 a) + (B / a)² v` (Driver et al., 2017, §4.3, pp. 9–10; p. 16; JSS PDF re-opened 2026-08-22T03:07Z; not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`), exact scalar lagged covariance of that constrained process `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T19:13Z; trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map), exact scalar Eq. 5 of that lagged covariance `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ` (`Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance), exact scalar later-occasion variance of that constrained process `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T23:12Z; trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt` and is not that later map; `Q_Δt` is not that later map), exact scalar Eq. 5 of that later-occasion variance `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ` (the lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not that later observed variance; the later-occasion latent variance is not that observed variance)), exact scalar later-occasion variance of §4.3 predetermined `T0VAR` `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T05:12Z; trait and `addedTIPREDVAR` do not enter `Q_Δt`; free `T0VAR` `p_0` is not that later map; setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map; stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map; as `Δt → ∞` with stable `a < 0` the composition approaches contemporaneous stationary `T0VAR`; as `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`; nonzero diffusion with `a ≥ 0` is a growing process and is kept. Eq. 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ` (`MANIFESTVAR` is not that later observed variance; the predetermined later-occasion latent variance is not that observed variance; stationary later observed variance is not that observed variance when `p_0` is free)), exact scalar lagged covariance of §4.3 predetermined `T0VAR` `trait + e^{a Δt} p_0 + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T09:04Z; trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`; free `T0VAR` `p_0` is not that lagged map; setting `p_0 = −q / (2 a)` recovers the stationary lagged map; stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map; later-occasion variance includes `Q_Δt` and is not that lagged map. Eq. 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ` (`MANIFESTVAR` does not enter; the predetermined lagged latent covariance is not that observed covariance; predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance; stationary lagged observed covariance is not that observed covariance when `p_0` is free)), exact scalar first-occasion variance of §4.3 predetermined `T0VAR` `trait + p_0 + (B / a)² v` (free `p_0` is not that map; stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free; lagged covariance decays the state and is not that map; later-occasion variance includes `Q_Δt` and is not that map), exact scalar Eq. 5 of that predetermined first-occasion variance `λ²(trait + p_0 + (B / a)² v) + θ + ψ` (`MANIFESTVAR` is not that first-occasion observed variance; the predetermined first-occasion latent variance is not that observed variance; stationary first-occasion observed variance is not that observed variance when `p_0` is free; predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance; later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z; first-occasion lagged omits `e^{a s} Q_u`; later-occasion variance does not lag; stationary lagged uses `−q / (2 a)`; decaying the later total is not that map; Eq. 5 of that later-start lagged covariance is `λ²` of it plus `ψ`; `Θ` does not enter; first-occasion lagged observed omits `e^{a s} Q_u`; later observed variance includes `Q_u` and `θ`; later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z; later-occasion variance at `u` omits `Q_s`; later-start lagged covariance omits `Q_s`; stationary later uses `−q / (2 a)`; evolving the later total as if it were all state is not that map; ignoring `startoffset` omits `e^{2 a s} Q_u`; Eq. 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`; `MANIFESTVAR` is not that observed variance; p. 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; footnote 4; §7.1; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-27T17:25Z; unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`; the trait-plus-state autocorrelation uses `TRAITVAR` and is not `discreteDRIFTstd`; p. 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:06Z; unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`; the continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`; `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z; unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`; the discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`; `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z); unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`; the discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`; `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z; unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`; `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z; unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`; the asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`; `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z; the affected variance is free `T0VAR`, not `asymDIFFUSION`; unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`; `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`; `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`; `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); 2017-era `addedT0TIPREDVAR` is `t0_b² v` (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z; `T0TIPREDEFFECT %*% TIPREDVAR %*% t(T0TIPREDEFFECT)` immediately after `T0TIPREDEFFECTstd`; form `t0_b` first, then square, then multiply by `v`; a zero coefficient or zero predictor variance is exactly zero; free `T0TIPREDEFFECT` does not require `a < 0`; `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map; `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance; free `T0VAR` is not this extra TI variance; `TRAITVAR` is not this extra TI variance; Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z; form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; `t0_b² v` is the latent extra, not the observed extra; `λ² p_0 + θ` is first-occasion observed variance, not this extra; `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra; `MANIFESTVAR` `θ` is not this extra; Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v`; form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; lasting asymptotic extra requires `a < 0`; `(B / a)² v` is the latent extra, not the observed extra; `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra; `λ² p + θ` is stationary observed variance, not this extra; `MANIFESTVAR` `θ` is not this extra; p. 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance; unstandardised `M` is not `TDPREDEFFECTstd`; `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`; intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`; `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`; Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance; unstandardised `t0_m` is not `T0TDPREDEFFECTstd`; `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`; `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`; `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`; free `T0VAR` does not require `a < 0`; p. 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; default ridge is 0); unstandardised `T0VAR` is not `T0VARstd`; `T0TDPREDEFFECTstd` is not `T0VARstd`; `addedT0TIPREDVAR` is not `T0VARstd`; p. 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend); unstandardised `TRAITVAR` is not `TRAITVARstd`; `T0VARstd` is not `TRAITVARstd` even when both equal 1; `addedT0TIPREDVAR` is not `TRAITVARstd`; p. 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0); unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`; `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1; `MANIFESTVAR` is not `MANIFESTTRAITVARstd`; p. 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug); unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`; `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1; Equation 5 `Var(y)` is not `MANIFESTVARstd`; p. 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`); unstandardised `TIPREDVAR` is not `TIPREDVARstd`; `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1; §7.2 `addedTIPREDVAR` is not `TIPREDVARstd`; p. 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`); unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`; `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1; `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`; p. 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `discreteCINT` is not `discreteCINTstd`; `κ / √p` is not `discreteCINTstd`; `(-κ / a) / √p` is not `discreteCINTstd`; `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`; unstandardised `asymCINT` is not `asymCINTstd`; `κ / √p` is not `asymCINTstd`; `discreteCINTstd` is not `asymCINTstd`; `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`; unstandardised `T0MEANS` is not `T0MEANSstd`; `T0VARstd` is not `T0MEANSstd`; `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`;))))), CWC-then-event-time residual lag, irregular already-centered residual log-rate, and strong/strict-gated two-group OLS latent-mean difference are implemented on the consolidation vehicle PR `integration/psychometric-standardisation` (folding draft stack #181–#218) and are not implemented-main until exact-head checks, review, and protected-main integration complete; full ESEM/set-ESEM, formative composites, DSEM, and matrix continuous-time dynamics remain accepted-target **Date:** 2026-08-05 **Supersedes:** None. ADR 0012 governs upstream topic measurement/network coordinates; this ADR governs higher-order psychometric structure and longitudinal interpretation. From 93dc530e4792b40d30181e4ade6cfe8140f0bf47 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 30 Aug 2026 08:24:40 -0700 Subject: [PATCH 005/296] docs(research): add multilevel event-time recovery --- docs/research/multilevel-event-time-recovery.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/research/multilevel-event-time-recovery.md b/docs/research/multilevel-event-time-recovery.md index 3701dcb4b..8d1ca42d9 100644 --- a/docs/research/multilevel-event-time-recovery.md +++ b/docs/research/multilevel-event-time-recovery.md @@ -58,7 +58,7 @@ This slice stays inside `psychometric_core`. It does not add a second invariance 52. recover the exact scalar Eq. 5 of later-start lagged §4.3 predetermined `T0VAR` `λ²(trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v) + ψ` (Driver et al., 2017, Eq. 5, p. 5; Eq. 3–4, pp. 4–5; Table 2, p. 12; JSS PDF re-opened 2026-08-23T10:27Z; form the later-start lagged latent covariance first, then `λ² c + ψ`; a zero loading is exactly `ψ`; independent `ε_t` does not enter) and refuse treating `θ`, the later-start lagged latent covariance, first-occasion lagged observed covariance, predetermined later observed variance, or stationary lagged observed covariance as `cov(y_{t0+u+s}, y_{t0+u})` when `p_0` is free; 53. recover the exact scalar later-start later-occasion variance of §4.3 predetermined `T0VAR` `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3, pp. 9–10; Eq. 3–4, pp. 4–5; Table 2, p. 12; p. 16; §7.2, pp. 20–21; JSS PDF re-opened 2026-08-23T11:05Z; Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; form the later-start within-subject variance first, then evolve that state, then include the trait, then include the TI extra variance, then add; trait and `addedTIPREDVAR` do not enter `Q_s`; setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map; as `u → 0+` the composition approaches later-occasion variance over `s`; as `s → 0+` the composition approaches later-occasion variance at `u`) and refuse treating that composition as later-occasion variance at `u`, as later-start lagged covariance, as stationary later-occasion variance, as `e^{2 a s}` of the later total plus `Q_s`, or as later-occasion variance over the lag interval alone; 54. recover the exact scalar Eq. 5 of later-start later-occasion §4.3 predetermined `T0VAR` `λ²(trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v) + θ + ψ` (Driver et al., 2017, Eq. 5, p. 5; Eq. 3–4, pp. 4–5; Table 2, p. 12; JSS PDF re-opened 2026-08-23T11:05Z; form the later-start later-occasion latent variance first, then `λ² p + θ + ψ`; a zero loading is exactly `θ + ψ`) and refuse treating `θ`, the later-start later-occasion latent variance, predetermined later observed variance, later-start lagged observed covariance, or stationary later-occasion observed variance as `Var(y_{t0+u+s})` when `p_0` is free; -55. recover the exact scalar p. 16 `discreteDRIFTstd` `e^{a Δt}` after forming strictly positive `asymDIFFUSION` `−q / (2 a)` (Driver et al., 2017, p. 16; Eq. 3, p. 5; footnote 4; §7.1, pp. 18–19; JSS PDF re-opened 2026-08-23T11:40Z; form the within-subject variance first, then `φ = exp(a Δt)`; scalar stationary SD ratio is 1; `a ≥ 0` and `q = 0` fail closed); +55. recover the exact scalar p. 16 `discreteDRIFTstd` `e^{a Δt}` after forming strictly positive `asymDIFFUSION` `−q / (2 a)` (Driver et al., 2017, p. 16; Eq. 3, p. 5; footnote 4; Table 2, p. 12; §7.1, pp. 18–19; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-27T17:25Z; the printed example on p. 16; form the within-subject variance first, then `φ = exp(a Δt)`; 2017-era source forms `discreteDRIFT * standardiser` where the scalar stationary SD ratio is 1; `a ≥ 0` and `q = 0` fail closed; executable on current main); 56. refuse treating unstandardised `discreteDRIFT` `e^{a Δt}` as `discreteDRIFTstd`, refuse treating the §7.1 trait-plus-state autocorrelation `(trait + e^{a Δt} p + added) / (trait + p + added)` as `discreteDRIFTstd`, and refuse treating `TRAITVAR` as the footnote 4 standardisation variance; 57. recover the exact scalar p. 16 `discreteDIFFUSIONstd` `Q_Δt / (−q / (2 a))` after forming strictly positive `asymDIFFUSION` `−q / (2 a)` (Driver et al., 2017, p. 16; Eq. 3–4, pp. 4–5; footnote 4; §7.1, pp. 18–19; JSS PDF re-opened 2026-08-23T13:06Z; form the within-subject variance first, then `Q_Δt`, then the ratio; scalar stationary map is `1 − exp(2 a Δt)`; `a ≥ 0` and `q = 0` fail closed); 58. refuse treating unstandardised `discreteDIFFUSION` `Q_Δt` as `discreteDIFFUSIONstd`, refuse treating the continuous standardisation `q / (−q / (2 a)) = −2 a` as `discreteDIFFUSIONstd`, refuse treating `Q_Δt / (trait + p + added)` as `discreteDIFFUSIONstd`, and refuse treating `TRAITVAR` as the footnote 4 standardisation variance; From c89a503689c03cda244562cad36811ea99da75ee Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 30 Aug 2026 16:20:00 +0000 Subject: [PATCH 006/296] docs(psychometric): record discreteDRIFTstd as PR-only until main CodeRabbit on #310 @ 93dc530e: CHANGELOG and research notes said the map was on current main; it lives on this PR until protected-main integration. ADR 0005 keeps a single Implementation maturity line. --- CHANGELOG.md | 2 +- docs/adr/0005-posterior-esem-dsem.md | 1 - docs/research/multilevel-event-time-recovery.md | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50ac00ae4..18c14c461 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,7 +38,7 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang ## [Unreleased] -- `psychometric_core` recovers the Driver, Oud, and Voelkle (2017, p. 16 `discreteDRIFTstd`; Eq. 3, p. 5; footnote 4; Table 2, p. 12; §7.1, pp. 18–19; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-27T17:25Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104) scalar standardised discrete `DRIFT` on current main after `0ce16e8` dropped the pre-consolidation code while research notes already named the map (register items 55–56). Page 16 prints discrete-time transformations for a chosen event interval (`discreteDRIFT` as `expm(DRIFT Δt)`) and, when appropriate, standardised matrices with the suffix `std`. The printed example on p. 16 is `discreteDRIFTstd`. Footnote 4 standardises using only the relevant variance, not the total. For `DRIFT` that relevant variance is within-subject `asymDIFFUSION` `p = −q / (2 a)`, because `DRIFT` is intended to represent individual, or average individual, temporal dynamics. The 2017-era source forms `discreteDRIFTstd` whenever `verbose = TRUE`, as `discreteDRIFT * standardiser` after `standardiser <- rep(sqrt(diag(asymDIFFUSION)), each = n.latent) / rep(diag(sqrt(asymDIFFUSION)), times = n.latent)` (comment: "std dev of affecting latent divided by std dev of affected latent"). In the scalar case that SD ratio is 1 after strictly positive `p`, so the standardised auto-effect equals `φ = exp(a Δt)` numerically; those remain distinct named quantities. Form strictly positive `p` first, then `φ`. Unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion; standardised `DRIFT` is not. Zero `q` has no positive SD and fails closed. Lasting `p` requires stable `a < 0`. A non-event clock fails closed. A non-positive event interval fails closed. Section 7.1 warns that omitting trait variance confounds between- and within-person information. The trait-plus-state autocorrelation `(trait + e^{a Δt} p + added) / (trait + p + added)` uses the total, not `asymDIFFUSION`, and is not this map when `TRAITVAR` is nonzero. `TRAITVAR` is not the standardisation variance. Meredith (1993) remains unread (web search historically Unpaywall `is_oa: false`; Springer `content/pdf` is an HTML stub). Mislevy (1991, *Psychometrika, 56*, 177–196) remains unread on the same terms (DOI `10.1007/bf02294457`). Still not a Kalman filter, not a matrix `expm`, not ESEM estimation, not DSEM, and not ctsem estimation. Do not convert open `#271` `MANIFESTVARstd` or `#272` `TIPREDVARstd`. +- `psychometric_core` recovers the Driver, Oud, and Voelkle (2017, p. 16 `discreteDRIFTstd`; Eq. 3, p. 5; footnote 4; Table 2, p. 12; §7.1, pp. 18–19; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-27T17:25Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104) scalar standardised discrete `DRIFT` on this PR (`#310` / `agent/psychometric-discrete-drift-std-clean`) until protected-main integration after `0ce16e8` dropped the pre-consolidation code while research notes already named the map (register items 55–56). Page 16 prints discrete-time transformations for a chosen event interval (`discreteDRIFT` as `expm(DRIFT Δt)`) and, when appropriate, standardised matrices with the suffix `std`. The printed example on p. 16 is `discreteDRIFTstd`. Footnote 4 standardises using only the relevant variance, not the total. For `DRIFT` that relevant variance is within-subject `asymDIFFUSION` `p = −q / (2 a)`, because `DRIFT` is intended to represent individual, or average individual, temporal dynamics. The 2017-era source forms `discreteDRIFTstd` whenever `verbose = TRUE`, as `discreteDRIFT * standardiser` after `standardiser <- rep(sqrt(diag(asymDIFFUSION)), each = n.latent) / rep(diag(sqrt(asymDIFFUSION)), times = n.latent)` (comment: "std dev of affecting latent divided by std dev of affected latent"). In the scalar case that SD ratio is 1 after strictly positive `p`, so the standardised auto-effect equals `φ = exp(a Δt)` numerically; those remain distinct named quantities. Form strictly positive `p` first, then `φ`. Unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion; standardised `DRIFT` is not. Zero `q` has no positive SD and fails closed. Lasting `p` requires stable `a < 0`. A non-event clock fails closed. A non-positive event interval fails closed. Section 7.1 warns that omitting trait variance confounds between- and within-person information. The trait-plus-state autocorrelation `(trait + e^{a Δt} p + added) / (trait + p + added)` uses the total, not `asymDIFFUSION`, and is not this map when `TRAITVAR` is nonzero. `TRAITVAR` is not the standardisation variance. Meredith (1993) remains unread (web search historically Unpaywall `is_oa: false`; Springer `content/pdf` is an HTML stub). Mislevy (1991, *Psychometrika, 56*, 177–196) remains unread on the same terms (DOI `10.1007/bf02294457`). Still not a Kalman filter, not a matrix `expm`, not ESEM estimation, not DSEM, and not ctsem estimation. Do not convert open `#271` `MANIFESTVARstd` or `#272` `TIPREDVARstd`. - `event_core` adds bounded Allen interval-consistency classification, atomic path-consistency closure, contradiction/resource refusals, and an explicit dependency-error fallback without claiming unrestricted global satisfiability. diff --git a/docs/adr/0005-posterior-esem-dsem.md b/docs/adr/0005-posterior-esem-dsem.md index 36dae717c..9e7c98838 100644 --- a/docs/adr/0005-posterior-esem-dsem.md +++ b/docs/adr/0005-posterior-esem-dsem.md @@ -1,7 +1,6 @@ # ADR 0005 — Posterior-aware ESEM/DSEM and structural interpretation **Decision status:** Accepted -**Implementation maturity:** partial — construct classification, valid log-ratio/logistic-normal indicator gates, CPU `f64` OLS and posterior-draw loading point-estimate averaging, Rubin `T_m = Ū_m + (1+1/m) B_m` on draw-level OLS loadings, cluster-mean within/between OLS with the CWC contextual effect and Kish ESS WLS, event-time discrete lag-1 and exact scalar local log-rate, exact scalar forward map and unequal-interval remapping, exact scalar discrete effect of a constant predictor, first-order discrete effect of a time-varying predictor with matched sampling and constancy intervals (Voelkle et al., 2012, Eq. 14), exact scalar discrete process noise (Driver, Oud, & Voelkle, 2017, Eq. 3), exact scalar lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), exact scalar stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; §4.3; p. 16 `asymDIFFUSION`), exact scalar trait-plus-state variance and lagged covariance (Driver et al., 2017, §4.3 `TRAITVAR`; not process noise and not `asymDIFFUSION`), exact scalar observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5, p. 5; Table 2, p. 12; `λ² Var(η) + θ` when `MANIFESTTRAITVAR` is zero, else `λ² Var(η) + θ + ψ`; lagged `λ² cov(η_t, η_{t-1}) + ψ`; `MANIFESTVAR` is `Θ`, not `Var(y)`; `Θ` does not enter lagged observed covariance; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; observed-indicator mean is `τ + λ μ` (`MANIFESTMEANS` is `τ`, not `E(y)`; `CINT` is not `MANIFESTMEANS`; `T0MEANS` is not `E(y)`; Equation 1 is the SDE; not a Kalman filter), exact scalar discrete latent mean `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ` (Driver et al., 2017, Eq. 3; `T0MEANS` is not `μ_t`; `CINT` is not the discrete increment), exact scalar evolved observed-indicator mean `τ + λ μ_t` (Driver et al., 2017, Eq. 5 of that Eq. 3 map; the first-occasion map `τ + λ μ_0` is not `E(y_t)`), exact scalar contemporaneous `TDPREDEFFECT` impulse `m x` (Driver et al., 2017, Eq. 3 fourth summand; Table 2 `TDPREDEFFECT` is `M`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; the §7.2 level-change form is not that impulse), exact scalar observed mean of that contemporaneous impulse `τ + λ(μ_t + m x)` (Driver et al., 2017, Eq. 5 of that Eq. 3 composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`), exact scalar time-independent `TIPREDEFFECT` increment `A^{-1}[e^{A Δt} − I] B z` (Driver et al., 2017, Eq. 3 second summand; Table 2 `TIPREDEFFECT` is `B`, not `κ`, not `M`, and not Voelkle Eq. 14; `B` is not that discrete increment), exact scalar observed mean of that increment `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` (Driver et al., 2017, Eq. 5 of that Eq. 3 printed addend after the `T0MEANS` carry and the `CINT` increment; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`), exact scalar within-interval `TDPREDEFFECT` carry `e^{A(t−u)} M x` for `t0 < u < t` (Driver et al., 2017, Eq. 1–2 Green-function integral of Eq. 2; §7.2 dissipation; not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14), exact scalar observed mean of that carry `τ + λ(μ_t + e^{a(t−u)} m x)` (Driver et al., 2017, Eq. 5 of that carried latent mean; `τ + λ μ_t` is not that observed mean), exact scalar first-occasion `T0TIPREDEFFECT` shift `t0_b z` and Eq. 3 first-summand carry `e^{A Δt} t0_b z` (Driver et al., 2017, Table 3, p. 13; Eq. 3, p. 5; `T0TIPREDEFFECT` is not `TIPREDEFFECT` `B`; `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`; `e^{A Δt} t0_b z` is not `t0_b z`), exact scalar observed mean of that first-occasion carry `τ + λ(μ_t + e^{a Δt} t0_b z)` (Driver et al., 2017, Eq. 5 of that Table 3 / Eq. 3 first-summand composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean), exact scalar first-occasion `T0TDPREDEFFECT` shift `t0_m x0` and Eq. 3 first-summand carry `e^{A Δt} t0_m x0` (Driver et al., 2017, Table 3, p. 13; Eq. 3, p. 5; JSS PDF re-opened 2026-08-20T19:10Z; `T0TDPREDEFFECT` is not `TDPREDEFFECT` `M`; `t0_m x0` is not `M x`; `e^{A Δt} t0_m x0` is not `t0_m x0`; `e^{A Δt} t0_m x0` is not `e^{A(t−u)} M x` for `t0 < u < t`; `t0_m x0` is not `t0_b z`; an impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`), exact scalar observed mean of that first-occasion TD carry `τ + λ(μ_t + e^{a Δt} t0_m x0)` (Driver et al., 2017, Eq. 5 of that Table 3 / Eq. 3 first-summand TD composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean; `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean), exact scalar §7.2 level-change `CINT` `κ = −a m x` (Driver et al., 2017, §7.2, pp. 20–21; `a < 0` so `−κ / a = m x`; not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`, and not the extra near-zero-drift latent process also named in §7.2), exact scalar Eq. 3 increment of that setting `(1 − e^{a Δt}) m x` (not `m x`, not `κ`, and not `TIPREDEFFECT`), exact scalar §7.2 extra near-zero-drift latent process contribution `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-20T23:10Z; identification `TDPREDEFFECT` on the extra process is 1; extra `DRIFT` printed as `−0.000001`; precisely 0 causes computational problems; `ε = a` is `a_{ηξ} x Δt e^{a Δt}`; not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`; `ε ≥ 0` fails closed), exact scalar observed mean of that extra-process contribution `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a))` (Driver et al., 2017, Eq. 5, p. 5; §7.2, pp. 22–23; JSS PDF re-opened 2026-08-21T06:12Z; the extra process has `LAMBDA` 0 and is not an observed indicator; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; the contribution is not `E(y_t)`; the evolved-plus-contribution latent mean is not `E(y_t)`), exact scalar after-t0 extra-process contribution `a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)` for `t0 < u < t` and Eq. 5 observed mean `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a))` (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-21T06:32Z; `T0TDPREDEFFECT` uses `Δt` for both the evolution and the extra drive; `TDPREDEFFECT` after `t0` uses `t − u`; `e^{a(t−u)} m x` is a Dirac on the original process, not this `DRIFT` drive), exact scalar §7.2 `asymTIPREDEFFECT` `-B z / a` (Driver et al., 2017, §7.2, pp. 20–21; JSS PDF opened 2026-08-21T13:08Z; expected total change in process means given a time-independent predictor; `a < 0`; not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; `a ≥ 0` fails closed), exact scalar §7.2 `addedTIPREDVAR` `(B / a)² v` (Driver et al., 2017, §7.2, pp. 20–21; stable between-subject variance accounted for by a time-independent predictor; not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`), exact scalar Table 2 `asymCINT` `-κ / a` (Driver et al., 2017, Table 2, p. 12; Eq. 3 as `Δt → ∞`; `a < 0`; not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; `a ≥ 0` fails closed), exact scalar p. 16 stationary `T0MEANS` `-κ / a + −B z / a` (Driver et al., 2017, p. 16; constrained first-occasion mean using `T0MEANSbase` / `T0MEANSfree`; not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean), exact scalar Eq. 5 of that constrained mean `τ + λ(−κ / a + −B z / a)` (Driver et al., 2017, §4.3, pp. 9–10; Eq. 5, p. 5; JSS PDF re-opened 2026-08-21T20:07Z; form the stationary latent mean first, then `τ + λ` of that mean; `τ + λ μ_0` is not that observed mean; `τ + λ(−κ / a)` is not that observed mean when `B z ≠ 0`; `τ + λ μ_t` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`), exact scalar §4.3 / p. 16 stationary `T0VAR` `trait + −q / (2 a) + (B / a)² v` (Driver et al., 2017, §4.3, pp. 9–10; p. 16; JSS PDF re-opened 2026-08-22T03:07Z; not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`), exact scalar lagged covariance of that constrained process `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T19:13Z; trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map), exact scalar Eq. 5 of that lagged covariance `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ` (`Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance), exact scalar later-occasion variance of that constrained process `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T23:12Z; trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt` and is not that later map; `Q_Δt` is not that later map), exact scalar Eq. 5 of that later-occasion variance `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ` (the lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not that later observed variance; the later-occasion latent variance is not that observed variance)), exact scalar later-occasion variance of §4.3 predetermined `T0VAR` `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T05:12Z; trait and `addedTIPREDVAR` do not enter `Q_Δt`; free `T0VAR` `p_0` is not that later map; setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map; stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map; as `Δt → ∞` with stable `a < 0` the composition approaches contemporaneous stationary `T0VAR`; as `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`; nonzero diffusion with `a ≥ 0` is a growing process and is kept. Eq. 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ` (`MANIFESTVAR` is not that later observed variance; the predetermined later-occasion latent variance is not that observed variance; stationary later observed variance is not that observed variance when `p_0` is free)), exact scalar lagged covariance of §4.3 predetermined `T0VAR` `trait + e^{a Δt} p_0 + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T09:04Z; trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`; free `T0VAR` `p_0` is not that lagged map; setting `p_0 = −q / (2 a)` recovers the stationary lagged map; stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map; later-occasion variance includes `Q_Δt` and is not that lagged map. Eq. 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ` (`MANIFESTVAR` does not enter; the predetermined lagged latent covariance is not that observed covariance; predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance; stationary lagged observed covariance is not that observed covariance when `p_0` is free)), exact scalar first-occasion variance of §4.3 predetermined `T0VAR` `trait + p_0 + (B / a)² v` (free `p_0` is not that map; stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free; lagged covariance decays the state and is not that map; later-occasion variance includes `Q_Δt` and is not that map), exact scalar Eq. 5 of that predetermined first-occasion variance `λ²(trait + p_0 + (B / a)² v) + θ + ψ` (`MANIFESTVAR` is not that first-occasion observed variance; the predetermined first-occasion latent variance is not that observed variance; stationary first-occasion observed variance is not that observed variance when `p_0` is free; predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance; later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z; first-occasion lagged omits `e^{a s} Q_u`; later-occasion variance does not lag; stationary lagged uses `−q / (2 a)`; decaying the later total is not that map; Eq. 5 of that later-start lagged covariance is `λ²` of it plus `ψ`; `Θ` does not enter; first-occasion lagged observed omits `e^{a s} Q_u`; later observed variance includes `Q_u` and `θ`; later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z; later-occasion variance at `u` omits `Q_s`; later-start lagged covariance omits `Q_s`; stationary later uses `−q / (2 a)`; evolving the later total as if it were all state is not that map; ignoring `startoffset` omits `e^{2 a s} Q_u`; Eq. 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`; `MANIFESTVAR` is not that observed variance; p. 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; footnote 4; §7.1; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-27T17:25Z; unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`; the trait-plus-state autocorrelation uses `TRAITVAR` and is not `discreteDRIFTstd`; p. 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:06Z; unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`; the continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`; `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z; unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`; the discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`; `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z); unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`; the discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`; `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z; unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`; `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z; unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`; the asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`; `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z; the affected variance is free `T0VAR`, not `asymDIFFUSION`; unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`; `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`; `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`; `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); 2017-era `addedT0TIPREDVAR` is `t0_b² v` (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z; `T0TIPREDEFFECT %*% TIPREDVAR %*% t(T0TIPREDEFFECT)` immediately after `T0TIPREDEFFECTstd`; form `t0_b` first, then square, then multiply by `v`; a zero coefficient or zero predictor variance is exactly zero; free `T0TIPREDEFFECT` does not require `a < 0`; `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map; `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance; free `T0VAR` is not this extra TI variance; `TRAITVAR` is not this extra TI variance; Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z; form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; `t0_b² v` is the latent extra, not the observed extra; `λ² p_0 + θ` is first-occasion observed variance, not this extra; `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra; `MANIFESTVAR` `θ` is not this extra; Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v`; form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; lasting asymptotic extra requires `a < 0`; `(B / a)² v` is the latent extra, not the observed extra; `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra; `λ² p + θ` is stationary observed variance, not this extra; `MANIFESTVAR` `θ` is not this extra; p. 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance; unstandardised `M` is not `TDPREDEFFECTstd`; `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`; intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`; `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`; Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance; unstandardised `t0_m` is not `T0TDPREDEFFECTstd`; `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`; `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`; `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`; free `T0VAR` does not require `a < 0`; p. 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; default ridge is 0); unstandardised `T0VAR` is not `T0VARstd`; `T0TDPREDEFFECTstd` is not `T0VARstd`; `addedT0TIPREDVAR` is not `T0VARstd`; p. 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend); unstandardised `TRAITVAR` is not `TRAITVARstd`; `T0VARstd` is not `TRAITVARstd` even when both equal 1; `addedT0TIPREDVAR` is not `TRAITVARstd`; p. 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0); unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`; `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1; `MANIFESTVAR` is not `MANIFESTTRAITVARstd`; p. 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug); unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`; `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1; Equation 5 `Var(y)` is not `MANIFESTVARstd`; p. 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`); unstandardised `TIPREDVAR` is not `TIPREDVARstd`; `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1; §7.2 `addedTIPREDVAR` is not `TIPREDVARstd`; p. 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`); unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`; `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1; `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`; p. 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `discreteCINT` is not `discreteCINTstd`; `κ / √p` is not `discreteCINTstd`; `(-κ / a) / √p` is not `discreteCINTstd`; `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`; unstandardised `asymCINT` is not `asymCINTstd`; `κ / √p` is not `asymCINTstd`; `discreteCINTstd` is not `asymCINTstd`; `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`; unstandardised `T0MEANS` is not `T0MEANSstd`; `T0VARstd` is not `T0MEANSstd`; `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`; `MANIFESTMEANSstd` is `τ / √θ` after strictly positive `MANIFESTVAR`; unstandardised `MANIFESTMEANS` is not `MANIFESTMEANSstd`; `MANIFESTVARstd` is not `MANIFESTMEANSstd`; `τ / √(λ² Var(η) + θ)` is not `MANIFESTMEANSstd`;))))), CWC-then-event-time residual lag, irregular already-centered residual log-rate, and strong/strict-gated two-group OLS latent-mean difference are implemented on the stacked psychometric PR and are not implemented-main until exact-head checks, review, and protected-main integration complete; full ESEM/set-ESEM, formative composites, DSEM, and matrix continuous-time dynamics remain accepted-target **Implementation maturity:** partial — construct classification, valid log-ratio/logistic-normal indicator gates, CPU `f64` OLS and posterior-draw loading point-estimate averaging, Rubin `T_m = Ū_m + (1+1/m) B_m` on draw-level OLS loadings, cluster-mean within/between OLS with the CWC contextual effect and Kish ESS WLS, event-time discrete lag-1 and exact scalar local log-rate, exact scalar forward map and unequal-interval remapping, exact scalar discrete effect of a constant predictor, first-order discrete effect of a time-varying predictor with matched sampling and constancy intervals (Voelkle et al., 2012, Eq. 14), exact scalar discrete process noise (Driver, Oud, & Voelkle, 2017, Eq. 3), exact scalar lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), exact scalar stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; §4.3; p. 16 `asymDIFFUSION`), exact scalar trait-plus-state variance and lagged covariance (Driver et al., 2017, §4.3 `TRAITVAR`; not process noise and not `asymDIFFUSION`), exact scalar observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5, p. 5; Table 2, p. 12; `λ² Var(η) + θ` when `MANIFESTTRAITVAR` is zero, else `λ² Var(η) + θ + ψ`; lagged `λ² cov(η_t, η_{t-1}) + ψ`; `MANIFESTVAR` is `Θ`, not `Var(y)`; `Θ` does not enter lagged observed covariance; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; observed-indicator mean is `τ + λ μ` (`MANIFESTMEANS` is `τ`, not `E(y)`; `CINT` is not `MANIFESTMEANS`; `T0MEANS` is not `E(y)`; Equation 1 is the SDE; not a Kalman filter), exact scalar discrete latent mean `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ` (Driver et al., 2017, Eq. 3; `T0MEANS` is not `μ_t`; `CINT` is not the discrete increment), exact scalar evolved observed-indicator mean `τ + λ μ_t` (Driver et al., 2017, Eq. 5 of that Eq. 3 map; the first-occasion map `τ + λ μ_0` is not `E(y_t)`), exact scalar contemporaneous `TDPREDEFFECT` impulse `m x` (Driver et al., 2017, Eq. 3 fourth summand; Table 2 `TDPREDEFFECT` is `M`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; the §7.2 level-change form is not that impulse), exact scalar observed mean of that contemporaneous impulse `τ + λ(μ_t + m x)` (Driver et al., 2017, Eq. 5 of that Eq. 3 composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`), exact scalar time-independent `TIPREDEFFECT` increment `A^{-1}[e^{A Δt} − I] B z` (Driver et al., 2017, Eq. 3 second summand; Table 2 `TIPREDEFFECT` is `B`, not `κ`, not `M`, and not Voelkle Eq. 14; `B` is not that discrete increment), exact scalar observed mean of that increment `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` (Driver et al., 2017, Eq. 5 of that Eq. 3 printed addend after the `T0MEANS` carry and the `CINT` increment; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`), exact scalar within-interval `TDPREDEFFECT` carry `e^{A(t−u)} M x` for `t0 < u < t` (Driver et al., 2017, Eq. 1–2 Green-function integral of Eq. 2; §7.2 dissipation; not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14), exact scalar observed mean of that carry `τ + λ(μ_t + e^{a(t−u)} m x)` (Driver et al., 2017, Eq. 5 of that carried latent mean; `τ + λ μ_t` is not that observed mean), exact scalar first-occasion `T0TIPREDEFFECT` shift `t0_b z` and Eq. 3 first-summand carry `e^{A Δt} t0_b z` (Driver et al., 2017, Table 3, p. 13; Eq. 3, p. 5; `T0TIPREDEFFECT` is not `TIPREDEFFECT` `B`; `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`; `e^{A Δt} t0_b z` is not `t0_b z`), exact scalar observed mean of that first-occasion carry `τ + λ(μ_t + e^{a Δt} t0_b z)` (Driver et al., 2017, Eq. 5 of that Table 3 / Eq. 3 first-summand composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean), exact scalar first-occasion `T0TDPREDEFFECT` shift `t0_m x0` and Eq. 3 first-summand carry `e^{A Δt} t0_m x0` (Driver et al., 2017, Table 3, p. 13; Eq. 3, p. 5; JSS PDF re-opened 2026-08-20T19:10Z; `T0TDPREDEFFECT` is not `TDPREDEFFECT` `M`; `t0_m x0` is not `M x`; `e^{A Δt} t0_m x0` is not `t0_m x0`; `e^{A Δt} t0_m x0` is not `e^{A(t−u)} M x` for `t0 < u < t`; `t0_m x0` is not `t0_b z`; an impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`), exact scalar observed mean of that first-occasion TD carry `τ + λ(μ_t + e^{a Δt} t0_m x0)` (Driver et al., 2017, Eq. 5 of that Table 3 / Eq. 3 first-summand TD composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean; `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean), exact scalar §7.2 level-change `CINT` `κ = −a m x` (Driver et al., 2017, §7.2, pp. 20–21; `a < 0` so `−κ / a = m x`; not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`, and not the extra near-zero-drift latent process also named in §7.2), exact scalar Eq. 3 increment of that setting `(1 − e^{a Δt}) m x` (not `m x`, not `κ`, and not `TIPREDEFFECT`), exact scalar §7.2 extra near-zero-drift latent process contribution `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-20T23:10Z; identification `TDPREDEFFECT` on the extra process is 1; extra `DRIFT` printed as `−0.000001`; precisely 0 causes computational problems; `ε = a` is `a_{ηξ} x Δt e^{a Δt}`; not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`; `ε ≥ 0` fails closed), exact scalar observed mean of that extra-process contribution `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a))` (Driver et al., 2017, Eq. 5, p. 5; §7.2, pp. 22–23; JSS PDF re-opened 2026-08-21T06:12Z; the extra process has `LAMBDA` 0 and is not an observed indicator; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; the contribution is not `E(y_t)`; the evolved-plus-contribution latent mean is not `E(y_t)`), exact scalar after-t0 extra-process contribution `a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)` for `t0 < u < t` and Eq. 5 observed mean `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a))` (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-21T06:32Z; `T0TDPREDEFFECT` uses `Δt` for both the evolution and the extra drive; `TDPREDEFFECT` after `t0` uses `t − u`; `e^{a(t−u)} m x` is a Dirac on the original process, not this `DRIFT` drive), exact scalar §7.2 `asymTIPREDEFFECT` `-B z / a` (Driver et al., 2017, §7.2, pp. 20–21; JSS PDF opened 2026-08-21T13:08Z; expected total change in process means given a time-independent predictor; `a < 0`; not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; `a ≥ 0` fails closed), exact scalar §7.2 `addedTIPREDVAR` `(B / a)² v` (Driver et al., 2017, §7.2, pp. 20–21; stable between-subject variance accounted for by a time-independent predictor; not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`), exact scalar Table 2 `asymCINT` `-κ / a` (Driver et al., 2017, Table 2, p. 12; Eq. 3 as `Δt → ∞`; `a < 0`; not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; `a ≥ 0` fails closed), exact scalar p. 16 stationary `T0MEANS` `-κ / a + −B z / a` (Driver et al., 2017, p. 16; constrained first-occasion mean using `T0MEANSbase` / `T0MEANSfree`; not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean), exact scalar Eq. 5 of that constrained mean `τ + λ(−κ / a + −B z / a)` (Driver et al., 2017, §4.3, pp. 9–10; Eq. 5, p. 5; JSS PDF re-opened 2026-08-21T20:07Z; form the stationary latent mean first, then `τ + λ` of that mean; `τ + λ μ_0` is not that observed mean; `τ + λ(−κ / a)` is not that observed mean when `B z ≠ 0`; `τ + λ μ_t` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`), exact scalar §4.3 / p. 16 stationary `T0VAR` `trait + −q / (2 a) + (B / a)² v` (Driver et al., 2017, §4.3, pp. 9–10; p. 16; JSS PDF re-opened 2026-08-22T03:07Z; not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`), exact scalar lagged covariance of that constrained process `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T19:13Z; trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map), exact scalar Eq. 5 of that lagged covariance `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ` (`Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance), exact scalar later-occasion variance of that constrained process `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T23:12Z; trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt` and is not that later map; `Q_Δt` is not that later map), exact scalar Eq. 5 of that later-occasion variance `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ` (the lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not that later observed variance; the later-occasion latent variance is not that observed variance)), exact scalar later-occasion variance of §4.3 predetermined `T0VAR` `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T05:12Z; trait and `addedTIPREDVAR` do not enter `Q_Δt`; free `T0VAR` `p_0` is not that later map; setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map; stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map; as `Δt → ∞` with stable `a < 0` the composition approaches contemporaneous stationary `T0VAR`; as `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`; nonzero diffusion with `a ≥ 0` is a growing process and is kept. Eq. 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ` (`MANIFESTVAR` is not that later observed variance; the predetermined later-occasion latent variance is not that observed variance; stationary later observed variance is not that observed variance when `p_0` is free)), exact scalar lagged covariance of §4.3 predetermined `T0VAR` `trait + e^{a Δt} p_0 + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T09:04Z; trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`; free `T0VAR` `p_0` is not that lagged map; setting `p_0 = −q / (2 a)` recovers the stationary lagged map; stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map; later-occasion variance includes `Q_Δt` and is not that lagged map. Eq. 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ` (`MANIFESTVAR` does not enter; the predetermined lagged latent covariance is not that observed covariance; predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance; stationary lagged observed covariance is not that observed covariance when `p_0` is free)), exact scalar first-occasion variance of §4.3 predetermined `T0VAR` `trait + p_0 + (B / a)² v` (free `p_0` is not that map; stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free; lagged covariance decays the state and is not that map; later-occasion variance includes `Q_Δt` and is not that map), exact scalar Eq. 5 of that predetermined first-occasion variance `λ²(trait + p_0 + (B / a)² v) + θ + ψ` (`MANIFESTVAR` is not that first-occasion observed variance; the predetermined first-occasion latent variance is not that observed variance; stationary first-occasion observed variance is not that observed variance when `p_0` is free; predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance; later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z; first-occasion lagged omits `e^{a s} Q_u`; later-occasion variance does not lag; stationary lagged uses `−q / (2 a)`; decaying the later total is not that map; Eq. 5 of that later-start lagged covariance is `λ²` of it plus `ψ`; `Θ` does not enter; first-occasion lagged observed omits `e^{a s} Q_u`; later observed variance includes `Q_u` and `θ`; later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z; later-occasion variance at `u` omits `Q_s`; later-start lagged covariance omits `Q_s`; stationary later uses `−q / (2 a)`; evolving the later total as if it were all state is not that map; ignoring `startoffset` omits `e^{2 a s} Q_u`; Eq. 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`; `MANIFESTVAR` is not that observed variance; p. 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; footnote 4; §7.1; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-27T17:25Z; unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`; the trait-plus-state autocorrelation uses `TRAITVAR` and is not `discreteDRIFTstd`; p. 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:06Z; unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`; the continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`; `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z; unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`; the discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`; `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z); unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`; the discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`; `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z; unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`; `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z; unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`; the asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`; `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z; the affected variance is free `T0VAR`, not `asymDIFFUSION`; unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`; `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`; `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`; `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); 2017-era `addedT0TIPREDVAR` is `t0_b² v` (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z; `T0TIPREDEFFECT %*% TIPREDVAR %*% t(T0TIPREDEFFECT)` immediately after `T0TIPREDEFFECTstd`; form `t0_b` first, then square, then multiply by `v`; a zero coefficient or zero predictor variance is exactly zero; free `T0TIPREDEFFECT` does not require `a < 0`; `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map; `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance; free `T0VAR` is not this extra TI variance; `TRAITVAR` is not this extra TI variance; Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z; form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; `t0_b² v` is the latent extra, not the observed extra; `λ² p_0 + θ` is first-occasion observed variance, not this extra; `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra; `MANIFESTVAR` `θ` is not this extra; Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v`; form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; lasting asymptotic extra requires `a < 0`; `(B / a)² v` is the latent extra, not the observed extra; `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra; `λ² p + θ` is stationary observed variance, not this extra; `MANIFESTVAR` `θ` is not this extra; p. 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance; unstandardised `M` is not `TDPREDEFFECTstd`; `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`; intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`; `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`; Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance; unstandardised `t0_m` is not `T0TDPREDEFFECTstd`; `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`; `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`; `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`; free `T0VAR` does not require `a < 0`; p. 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; default ridge is 0); unstandardised `T0VAR` is not `T0VARstd`; `T0TDPREDEFFECTstd` is not `T0VARstd`; `addedT0TIPREDVAR` is not `T0VARstd`; p. 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend); unstandardised `TRAITVAR` is not `TRAITVARstd`; `T0VARstd` is not `TRAITVARstd` even when both equal 1; `addedT0TIPREDVAR` is not `TRAITVARstd`; p. 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0); unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`; `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1; `MANIFESTVAR` is not `MANIFESTTRAITVARstd`; p. 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug); unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`; `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1; Equation 5 `Var(y)` is not `MANIFESTVARstd`; p. 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`); unstandardised `TIPREDVAR` is not `TIPREDVARstd`; `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1; §7.2 `addedTIPREDVAR` is not `TIPREDVARstd`; p. 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`); unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`; `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1; `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`; p. 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `discreteCINT` is not `discreteCINTstd`; `κ / √p` is not `discreteCINTstd`; `(-κ / a) / √p` is not `discreteCINTstd`; `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`; unstandardised `asymCINT` is not `asymCINTstd`; `κ / √p` is not `asymCINTstd`; `discreteCINTstd` is not `asymCINTstd`; `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`; unstandardised `T0MEANS` is not `T0MEANSstd`; `T0VARstd` is not `T0MEANSstd`; `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`;))))), CWC-then-event-time residual lag, irregular already-centered residual log-rate, and strong/strict-gated two-group OLS latent-mean difference are implemented on the consolidation vehicle PR `integration/psychometric-standardisation` (folding draft stack #181–#218) and are not implemented-main until exact-head checks, review, and protected-main integration complete; full ESEM/set-ESEM, formative composites, DSEM, and matrix continuous-time dynamics remain accepted-target **Date:** 2026-08-05 **Supersedes:** None. ADR 0012 governs upstream topic measurement/network coordinates; this ADR governs higher-order psychometric structure and longitudinal interpretation. diff --git a/docs/research/multilevel-event-time-recovery.md b/docs/research/multilevel-event-time-recovery.md index 8d1ca42d9..b9841819b 100644 --- a/docs/research/multilevel-event-time-recovery.md +++ b/docs/research/multilevel-event-time-recovery.md @@ -58,7 +58,7 @@ This slice stays inside `psychometric_core`. It does not add a second invariance 52. recover the exact scalar Eq. 5 of later-start lagged §4.3 predetermined `T0VAR` `λ²(trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v) + ψ` (Driver et al., 2017, Eq. 5, p. 5; Eq. 3–4, pp. 4–5; Table 2, p. 12; JSS PDF re-opened 2026-08-23T10:27Z; form the later-start lagged latent covariance first, then `λ² c + ψ`; a zero loading is exactly `ψ`; independent `ε_t` does not enter) and refuse treating `θ`, the later-start lagged latent covariance, first-occasion lagged observed covariance, predetermined later observed variance, or stationary lagged observed covariance as `cov(y_{t0+u+s}, y_{t0+u})` when `p_0` is free; 53. recover the exact scalar later-start later-occasion variance of §4.3 predetermined `T0VAR` `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3, pp. 9–10; Eq. 3–4, pp. 4–5; Table 2, p. 12; p. 16; §7.2, pp. 20–21; JSS PDF re-opened 2026-08-23T11:05Z; Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; form the later-start within-subject variance first, then evolve that state, then include the trait, then include the TI extra variance, then add; trait and `addedTIPREDVAR` do not enter `Q_s`; setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map; as `u → 0+` the composition approaches later-occasion variance over `s`; as `s → 0+` the composition approaches later-occasion variance at `u`) and refuse treating that composition as later-occasion variance at `u`, as later-start lagged covariance, as stationary later-occasion variance, as `e^{2 a s}` of the later total plus `Q_s`, or as later-occasion variance over the lag interval alone; 54. recover the exact scalar Eq. 5 of later-start later-occasion §4.3 predetermined `T0VAR` `λ²(trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v) + θ + ψ` (Driver et al., 2017, Eq. 5, p. 5; Eq. 3–4, pp. 4–5; Table 2, p. 12; JSS PDF re-opened 2026-08-23T11:05Z; form the later-start later-occasion latent variance first, then `λ² p + θ + ψ`; a zero loading is exactly `θ + ψ`) and refuse treating `θ`, the later-start later-occasion latent variance, predetermined later observed variance, later-start lagged observed covariance, or stationary later-occasion observed variance as `Var(y_{t0+u+s})` when `p_0` is free; -55. recover the exact scalar p. 16 `discreteDRIFTstd` `e^{a Δt}` after forming strictly positive `asymDIFFUSION` `−q / (2 a)` (Driver et al., 2017, p. 16; Eq. 3, p. 5; footnote 4; Table 2, p. 12; §7.1, pp. 18–19; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-27T17:25Z; the printed example on p. 16; form the within-subject variance first, then `φ = exp(a Δt)`; 2017-era source forms `discreteDRIFT * standardiser` where the scalar stationary SD ratio is 1; `a ≥ 0` and `q = 0` fail closed; executable on current main); +55. recover the exact scalar p. 16 `discreteDRIFTstd` `e^{a Δt}` after forming strictly positive `asymDIFFUSION` `−q / (2 a)` (Driver et al., 2017, p. 16; Eq. 3, p. 5; footnote 4; Table 2, p. 12; §7.1, pp. 18–19; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-27T17:25Z; the printed example on p. 16; form the within-subject variance first, then `φ = exp(a Δt)`; 2017-era source forms `discreteDRIFT * standardiser` where the scalar stationary SD ratio is 1; `a ≥ 0` and `q = 0` fail closed; executable on this PR (`#310`) until protected-main integration); 56. refuse treating unstandardised `discreteDRIFT` `e^{a Δt}` as `discreteDRIFTstd`, refuse treating the §7.1 trait-plus-state autocorrelation `(trait + e^{a Δt} p + added) / (trait + p + added)` as `discreteDRIFTstd`, and refuse treating `TRAITVAR` as the footnote 4 standardisation variance; 57. recover the exact scalar p. 16 `discreteDIFFUSIONstd` `Q_Δt / (−q / (2 a))` after forming strictly positive `asymDIFFUSION` `−q / (2 a)` (Driver et al., 2017, p. 16; Eq. 3–4, pp. 4–5; footnote 4; §7.1, pp. 18–19; JSS PDF re-opened 2026-08-23T13:06Z; form the within-subject variance first, then `Q_Δt`, then the ratio; scalar stationary map is `1 − exp(2 a Δt)`; `a ≥ 0` and `q = 0` fail closed); 58. refuse treating unstandardised `discreteDIFFUSION` `Q_Δt` as `discreteDIFFUSIONstd`, refuse treating the continuous standardisation `q / (−q / (2 a)) = −2 a` as `discreteDIFFUSIONstd`, refuse treating `Q_Δt / (trait + p + added)` as `discreteDIFFUSIONstd`, and refuse treating `TRAITVAR` as the footnote 4 standardisation variance; From beba8ea6dd979b7355077f12110e04ea00426af0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 06:37:32 +0000 Subject: [PATCH 007/296] =?UTF-8?q?feat(psychometric):=20recover=20Driver?= =?UTF-8?q?=20=C2=A77.1=20trait-plus-state=20expected=20autocorrelation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Name the covariance ratio (trait + e^{aΔt} p + added) / (trait + p + added) as a scalar map distinct from unstandardised discreteDRIFT and from discreteDRIFTstd. Form lagged covariance first, then contemporaneous total, then addedTIPREDVAR, then the ratio. Claim-boundary tests refuse substituting either discrete auto-effect for that named quantity. --- ARCHITECTURE.md | 6 +- CHANGELOG.md | 2 + CLAUDE.md | 2 +- crates/psychometric_core/src/error.rs | 44 +++ crates/psychometric_core/src/event_time.rs | 347 +++++++++++++++++- crates/psychometric_core/src/lib.rs | 16 + ...multilevel_event_time_recovery_contract.rs | 147 +++++++- .../scientific_claim_boundary_contract.rs | 100 ++++- docs/TRACEABILITY.md | 2 +- docs/adr/0005-posterior-esem-dsem.md | 4 +- .../multilevel-event-time-recovery.md | 7 +- 11 files changed, 636 insertions(+), 41 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index e6dafbcc2..078717fe6 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -114,11 +114,11 @@ boundaries above remain the target modular MSA architecture. | `membership_target` | language, episode, template, department, and opportunity-pool targets cannot collapse into entity or project | | `topic_measurement` | logistic-normal ALR/ILR coordinates and the CPU `f64` TRSL-TM reference estimator | | `analysis_engine` | bounded cutoff-safe temporal evidence readiness execution and digest-bound terminal artifacts | -| `psychometric_core` | posterior-aware structural input gates, CWC within/between OLS plus the contextual effect, event-time log-rate, unequal-interval discrete-lag remapping, constant-predictor discrete effect, time-varying-predictor discrete effect (Eq. 14), exact scalar discrete process noise (Driver et al., 2017, Eq. 3), lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; `asymDIFFUSION`), trait-plus-state variance (Driver et al., 2017, §4.3 `TRAITVAR`; not process noise), observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5; Table 2 `MANIFESTVAR` is `Θ`, not `Var(y)`; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; `Θ` does not enter lagged observed covariance; observed-indicator mean is `τ + λ μ`; `MANIFESTMEANS` is not `E(y)`; `CINT` is not `MANIFESTMEANS`; discrete latent mean is `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ`; `T0MEANS` is not `μ_t`; `CINT` is not the discrete increment; evolved observed mean is `τ + λ μ_t`; `τ + λ μ_0` is not `E(y_t)`; contemporaneous `TDPREDEFFECT` impulse is `m x`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that contemporaneous impulse is `τ + λ(μ_t + m x)`, and `τ + λ μ_t` is not that observed mean; time-independent `TIPREDEFFECT` increment is `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, not `M x`, not Voelkle Eq. 14, and not the coefficient `B`; Eq. 5 of that increment is `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`; within-interval `TDPREDEFFECT` carry is `e^{A(t−u)} M x` for `t0 < u < t`, not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that carry is `τ + λ(μ_t + e^{a(t−u)} m x)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that carried observed mean when `u ≠ t`; first-occasion `T0TIPREDEFFECT` shift is `t0_b z` and Eq. 3 first-summand carry is `e^{A Δt} t0_b z` (`T0TIPREDEFFECT` is not `TIPREDEFFECT` `B`; `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`; `e^{A Δt} t0_b z` is not `t0_b z`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_b z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean), first-occasion `T0TDPREDEFFECT` shift is `t0_m x0` and Eq. 3 first-summand carry is `e^{A Δt} t0_m x0` (`T0TDPREDEFFECT` is not `TDPREDEFFECT` `M`; `t0_m x0` is not `M x`; `e^{A Δt} t0_m x0` is not `t0_m x0`; `e^{A Δt} t0_m x0` is not `e^{A(t−u)} M x` for `t0 < u < t`; `t0_m x0` is not `t0_b z`; an impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_m x0)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean; `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean; §7.2 level-change `CINT` is `κ = −a m x` with `a < 0` so `−κ / a = m x` (`−a m x` is not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`, and not the extra near-zero-drift latent process also named in §7.2; Eq. 3 of that setting is `(1 − e^{a Δt}) m x`, which is not `m x`, not `κ`, and not `TIPREDEFFECT`; §7.2 extra-process contribution is `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (`ε = a` is `a_{ηξ} x Δt e^{a Δt}`; identification `TDPREDEFFECT` on the extra process is 1; printed extra `DRIFT` is `−0.000001`; not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`; `ε ≥ 0` fails closed; Eq. 5 of that contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)`; the extra process has `LAMBDA` 0 and is not an observed indicator; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; the contribution is not `E(y_t)`; the evolved-plus-contribution latent mean is not `E(y_t)`; after-t0 extra-process `TDPREDEFFECT` is `a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)` for `t0 < u < t` while `μ_t` uses `Δt`; Eq. 5 of that after-t0 contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)`; the first-occasion extra-process observed mean is not that observed mean when `u ≠ t0`; `e^{a(t−u)} m x` is a Dirac on the original process, not this `DRIFT` drive; §7.2 `asymTIPREDEFFECT` is `-B z / a` for `a < 0` (`-B z / a` is not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; §7.2 `addedTIPREDVAR` is `(B / a)² v`, not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`; Table 2 `asymCINT` is `-κ / a` for `a < 0` and is not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; p. 16 stationary `T0MEANS` is `-κ / a + −B z / a` and is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean; Eq. 5 of that constrained mean is `τ + λ(−κ / a + −B z / a)`; `τ + λ μ_0` is not that observed mean; `τ + λ(−κ / a)` is not that observed mean when `B z ≠ 0`; `τ + λ μ_t` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`; stationary `T0VAR` is `trait + −q / (2 a) + (B / a)² v` (not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`); lagged stationary `T0VAR` is `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (trait and `addedTIPREDVAR` do not decay; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map; Eq. 5 of that lagged covariance is `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ`; `Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance); later-occasion stationary `T0VAR` is `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt`; `Q_Δt` is not that later map; Eq. 5 of that later-occasion variance is `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ`; lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not `Var(y_t)`; the later-occasion latent variance is not `Var(y_t)`))), irregular already-centered residual lag, Rubin `T` on OLS loadings, and strong-gated latent means (two-observation residual variance is identically `0` and caps at strong/scalar; Putnick & Bornstein, 2016) | +| `psychometric_core` | posterior-aware structural input gates, CWC within/between OLS plus the contextual effect, event-time log-rate, unequal-interval discrete-lag remapping, constant-predictor discrete effect, time-varying-predictor discrete effect (Eq. 14), exact scalar discrete process noise (Driver et al., 2017, Eq. 3), lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; `asymDIFFUSION`), trait-plus-state variance and expected autocorrelation (Driver et al., 2017, §4.3 `TRAITVAR`; §7.1; not process noise, not discreteDRIFT, not discreteDRIFTstd), observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5; Table 2 `MANIFESTVAR` is `Θ`, not `Var(y)`; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; `Θ` does not enter lagged observed covariance; observed-indicator mean is `τ + λ μ`; `MANIFESTMEANS` is not `E(y)`; `CINT` is not `MANIFESTMEANS`; discrete latent mean is `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ`; `T0MEANS` is not `μ_t`; `CINT` is not the discrete increment; evolved observed mean is `τ + λ μ_t`; `τ + λ μ_0` is not `E(y_t)`; contemporaneous `TDPREDEFFECT` impulse is `m x`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that contemporaneous impulse is `τ + λ(μ_t + m x)`, and `τ + λ μ_t` is not that observed mean; time-independent `TIPREDEFFECT` increment is `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, not `M x`, not Voelkle Eq. 14, and not the coefficient `B`; Eq. 5 of that increment is `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`; within-interval `TDPREDEFFECT` carry is `e^{A(t−u)} M x` for `t0 < u < t`, not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that carry is `τ + λ(μ_t + e^{a(t−u)} m x)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that carried observed mean when `u ≠ t`; first-occasion `T0TIPREDEFFECT` shift is `t0_b z` and Eq. 3 first-summand carry is `e^{A Δt} t0_b z` (`T0TIPREDEFFECT` is not `TIPREDEFFECT` `B`; `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`; `e^{A Δt} t0_b z` is not `t0_b z`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_b z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean), first-occasion `T0TDPREDEFFECT` shift is `t0_m x0` and Eq. 3 first-summand carry is `e^{A Δt} t0_m x0` (`T0TDPREDEFFECT` is not `TDPREDEFFECT` `M`; `t0_m x0` is not `M x`; `e^{A Δt} t0_m x0` is not `t0_m x0`; `e^{A Δt} t0_m x0` is not `e^{A(t−u)} M x` for `t0 < u < t`; `t0_m x0` is not `t0_b z`; an impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_m x0)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean; `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean; §7.2 level-change `CINT` is `κ = −a m x` with `a < 0` so `−κ / a = m x` (`−a m x` is not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`, and not the extra near-zero-drift latent process also named in §7.2; Eq. 3 of that setting is `(1 − e^{a Δt}) m x`, which is not `m x`, not `κ`, and not `TIPREDEFFECT`; §7.2 extra-process contribution is `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (`ε = a` is `a_{ηξ} x Δt e^{a Δt}`; identification `TDPREDEFFECT` on the extra process is 1; printed extra `DRIFT` is `−0.000001`; not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`; `ε ≥ 0` fails closed; Eq. 5 of that contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)`; the extra process has `LAMBDA` 0 and is not an observed indicator; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; the contribution is not `E(y_t)`; the evolved-plus-contribution latent mean is not `E(y_t)`; after-t0 extra-process `TDPREDEFFECT` is `a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)` for `t0 < u < t` while `μ_t` uses `Δt`; Eq. 5 of that after-t0 contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)`; the first-occasion extra-process observed mean is not that observed mean when `u ≠ t0`; `e^{a(t−u)} m x` is a Dirac on the original process, not this `DRIFT` drive; §7.2 `asymTIPREDEFFECT` is `-B z / a` for `a < 0` (`-B z / a` is not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; §7.2 `addedTIPREDVAR` is `(B / a)² v`, not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`; Table 2 `asymCINT` is `-κ / a` for `a < 0` and is not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; p. 16 stationary `T0MEANS` is `-κ / a + −B z / a` and is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean; Eq. 5 of that constrained mean is `τ + λ(−κ / a + −B z / a)`; `τ + λ μ_0` is not that observed mean; `τ + λ(−κ / a)` is not that observed mean when `B z ≠ 0`; `τ + λ μ_t` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`; stationary `T0VAR` is `trait + −q / (2 a) + (B / a)² v` (not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`); lagged stationary `T0VAR` is `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (trait and `addedTIPREDVAR` do not decay; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map; Eq. 5 of that lagged covariance is `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ`; `Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance); later-occasion stationary `T0VAR` is `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt`; `Q_Δt` is not that later map; Eq. 5 of that later-occasion variance is `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ`; lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not `Var(y_t)`; the later-occasion latent variance is not `Var(y_t)`))), irregular already-centered residual lag, Rubin `T` on OLS loadings, and strong-gated latent means (two-observation residual variance is identically `0` and caps at strong/scalar; Putnick & Bornstein, 2016) | | `validation_core` | RMSE, bias, coverage, graph, and Monte Carlo metrics | | `tepp_api` | versioned DTO, schema, and export contracts | -| `psychometric_core` | posterior-aware structural input gates, CWC within/between OLS plus the contextual effect, event-time log-rate, unequal-interval discrete-lag remapping, constant-predictor discrete effect, time-varying-predictor discrete effect (Eq. 14), exact scalar discrete process noise (Driver et al., 2017, Eq. 3), lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; `asymDIFFUSION`), trait-plus-state variance (Driver et al., 2017, §4.3 `TRAITVAR`; not process noise), observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5; Table 2 `MANIFESTVAR` is `Θ`, not `Var(y)`; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; `Θ` does not enter lagged observed covariance; observed-indicator mean is `τ + λ μ`; `MANIFESTMEANS` is not `E(y)`; `CINT` is not `MANIFESTMEANS`; discrete latent mean is `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ`; `T0MEANS` is not `μ_t`; `CINT` is not the discrete increment; evolved observed mean is `τ + λ μ_t`; `τ + λ μ_0` is not `E(y_t)`; contemporaneous `TDPREDEFFECT` impulse is `m x`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that contemporaneous impulse is `τ + λ(μ_t + m x)`, and `τ + λ μ_t` is not that observed mean; time-independent `TIPREDEFFECT` increment is `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, not `M x`, not Voelkle Eq. 14, and not the coefficient `B`; Eq. 5 of that increment is `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`; within-interval `TDPREDEFFECT` carry is `e^{A(t−u)} M x` for `t0 < u < t`, not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that carry is `τ + λ(μ_t + e^{a(t−u)} m x)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that carried observed mean when `u ≠ t`; first-occasion `T0TIPREDEFFECT` shift is `t0_b z` and Eq. 3 first-summand carry is `e^{A Δt} t0_b z` (`T0TIPREDEFFECT` is not `TIPREDEFFECT` `B`; `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`; `e^{A Δt} t0_b z` is not `t0_b z`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_b z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean), first-occasion `T0TDPREDEFFECT` shift is `t0_m x0` and Eq. 3 first-summand carry is `e^{A Δt} t0_m x0` (`T0TDPREDEFFECT` is not `TDPREDEFFECT` `M`; `t0_m x0` is not `M x`; `e^{A Δt} t0_m x0` is not `t0_m x0`; `e^{A Δt} t0_m x0` is not `e^{A(t−u)} M x` for `t0 < u < t`; `t0_m x0` is not `t0_b z`; an impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_m x0)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean; `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean; §7.2 level-change `CINT` is `κ = −a m x` with `a < 0` so `−κ / a = m x` (`−a m x` is not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`, and not the extra near-zero-drift latent process also named in §7.2; Eq. 3 of that setting is `(1 − e^{a Δt}) m x`, which is not `m x`, not `κ`, and not `TIPREDEFFECT`; §7.2 extra-process contribution is `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (`ε = a` is `a_{ηξ} x Δt e^{a Δt}`; identification `TDPREDEFFECT` on the extra process is 1; printed extra `DRIFT` is `−0.000001`; not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`; `ε ≥ 0` fails closed; Eq. 5 of that contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)`; the extra process has `LAMBDA` 0 and is not an observed indicator; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; the contribution is not `E(y_t)`; the evolved-plus-contribution latent mean is not `E(y_t)`; after-t0 extra-process `TDPREDEFFECT` is `a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)` for `t0 < u < t` while `μ_t` uses `Δt`; Eq. 5 of that after-t0 contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)`; the first-occasion extra-process observed mean is not that observed mean when `u ≠ t0`; `e^{a(t−u)} m x` is a Dirac on the original process, not this `DRIFT` drive; §7.2 `asymTIPREDEFFECT` is `-B z / a` for `a < 0` (`-B z / a` is not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; §7.2 `addedTIPREDVAR` is `(B / a)² v`, not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`; Table 2 `asymCINT` is `-κ / a` for `a < 0` and is not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; p. 16 stationary `T0MEANS` is `-κ / a + −B z / a` and is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean; Eq. 5 of that constrained mean is `τ + λ(−κ / a + −B z / a)`; `τ + λ μ_0` is not that observed mean; `τ + λ(−κ / a)` is not that observed mean when `B z ≠ 0`; `τ + λ μ_t` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`; stationary `T0VAR` is `trait + −q / (2 a) + (B / a)² v` (not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`); lagged stationary `T0VAR` is `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (trait and `addedTIPREDVAR` do not decay; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map; Eq. 5 of that lagged covariance is `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ`; `Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance); later-occasion stationary `T0VAR` is `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt`; `Q_Δt` is not that later map; Eq. 5 of that later-occasion variance is `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ`; lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not `Var(y_t)`; the later-occasion latent variance is not `Var(y_t)`); predetermined later-occasion `T0VAR` is `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (free `T0VAR` `p_0` is not that later map; setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map; stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map; Eq. 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ`; `MANIFESTVAR` is not `Var(y_t)`; the predetermined later-occasion latent variance is not `Var(y_t)`; stationary later observed variance is not that observed variance when `p_0` is free); predetermined lagged `T0VAR` is `trait + e^{a Δt} p_0 + (B / a)² v` (free `T0VAR` `p_0` is not that lagged map; setting `p_0 = −q / (2 a)` recovers the stationary lagged map; stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map; later-occasion variance includes `Q_Δt` and is not that lagged map; Eq. 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ`; `MANIFESTVAR` does not enter; the predetermined lagged latent covariance is not that observed covariance; predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance; stationary lagged observed covariance is not that observed covariance when `p_0` is free; the predetermined first-occasion variance of §4.3 predetermined `T0VAR` is `trait + p_0 + (B / a)² v`; free `p_0` is not that map; stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free; lagged covariance decays the state and is not that map; later-occasion variance includes `Q_Δt` and is not that map; Eq. 5 of that predetermined first-occasion variance is `λ²(trait + p_0 + (B / a)² v) + θ + ψ`; `MANIFESTVAR` is not that first-occasion observed variance; the predetermined first-occasion latent variance is not that observed variance; stationary first-occasion observed variance is not that observed variance when `p_0` is free; predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance; later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z; first-occasion lagged omits `e^{a s} Q_u`; later-occasion variance does not lag; stationary lagged uses `−q / (2 a)`; decaying the later total is not that map; Eq. 5 of that later-start lagged covariance is `λ²` of it plus `ψ`; `Θ` does not enter; first-occasion lagged observed omits `e^{a s} Q_u`; later observed variance includes `Q_u` and `θ`; later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z; later-occasion variance at `u` omits `Q_s`; later-start lagged covariance omits `Q_s`; stationary later uses `−q / (2 a)`; evolving the later total as if it were all state is not that map; ignoring `startoffset` omits `e^{2 a s} Q_u`; Eq. 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`; `MANIFESTVAR` is not that observed variance; p. 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4; unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`; the §7.1 trait-plus-state autocorrelation uses `TRAITVAR` and is not `discreteDRIFTstd`; `TRAITVAR` is not the standardisation variance; p. 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4; unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`; the continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`; `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z; unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`; the discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`; `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z); unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`; the discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`; `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z; unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`; `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z; unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`; the asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`; `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z; the affected variance is free `T0VAR`, not `asymDIFFUSION`; unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`; `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`; `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`; `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); 2017-era `addedT0TIPREDVAR` is `t0_b² v` (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z; `T0TIPREDEFFECT %*% TIPREDVAR %*% t(T0TIPREDEFFECT)` immediately after `T0TIPREDEFFECTstd`; form `t0_b` first, then square, then multiply by `v`; a zero coefficient or zero predictor variance is exactly zero; free `T0TIPREDEFFECT` does not require `a < 0`; `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map; `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance; free `T0VAR` is not this extra TI variance; `TRAITVAR` is not this extra TI variance; Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z; form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; `t0_b² v` is the latent extra, not the observed extra; `λ² p_0 + θ` is first-occasion observed variance, not this extra; `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra; `MANIFESTVAR` `θ` is not this extra; Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v`; form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; lasting asymptotic extra requires `a < 0`; `(B / a)² v` is the latent extra, not the observed extra; `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra; `λ² p + θ` is stationary observed variance, not this extra; `MANIFESTVAR` `θ` is not this extra; p. 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance; unstandardised `M` is not `TDPREDEFFECTstd`; `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`; intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`; `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`; Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance; unstandardised `t0_m` is not `T0TDPREDEFFECTstd`; `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`; `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`; `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`; free `T0VAR` does not require `a < 0`; p. 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; default ridge is 0); unstandardised `T0VAR` is not `T0VARstd`; `T0TDPREDEFFECTstd` is not `T0VARstd`; `addedT0TIPREDVAR` is not `T0VARstd`; p. 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend); unstandardised `TRAITVAR` is not `TRAITVARstd`; `T0VARstd` is not `TRAITVARstd` even when both equal 1; `addedT0TIPREDVAR` is not `TRAITVARstd`; p. 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0); unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`; `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1; `MANIFESTVAR` is not `MANIFESTTRAITVARstd`; p. 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug); unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`; `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1; Equation 5 `Var(y)` is not `MANIFESTVARstd`; p. 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`); unstandardised `TIPREDVAR` is not `TIPREDVARstd`; `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1; §7.2 `addedTIPREDVAR` is not `TIPREDVARstd`; p. 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`); unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`; `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1; `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`; p. 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `discreteCINT` is not `discreteCINTstd`; `κ / √p` is not `discreteCINTstd`; `(-κ / a) / √p` is not `discreteCINTstd`; `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`; unstandardised `asymCINT` is not `asymCINTstd`; `κ / √p` is not `asymCINTstd`; `discreteCINTstd` is not `asymCINTstd`; `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`; unstandardised `T0MEANS` is not `T0MEANSstd`; `T0VARstd` is not `T0MEANSstd`; `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`; `MANIFESTMEANSstd` is `τ / √θ` after strictly positive `MANIFESTVAR`; unstandardised `MANIFESTMEANS` is not `MANIFESTMEANSstd`; `MANIFESTVARstd` is not `MANIFESTMEANSstd`; `τ / √(λ² Var(η) + θ)` is not `MANIFESTMEANSstd`; p. 16 `CINTstd` is `κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `CINT` is not `CINTstd`; `asymCINTstd` is not `CINTstd`; `discreteCINTstd` is not `CINTstd`; `κ / √(trait + p + added)` is not `CINTstd`;))))), irregular already-centered residual lag, Rubin `T` on OLS loadings, and strong-gated latent means (two-observation residual variance is identically `0` and caps at strong/scalar; Putnick & Bornstein, 2016) | -| `psychometric_core` | posterior-aware structural input gates, CWC within/between OLS plus the contextual effect, event-time log-rate, unequal-interval discrete-lag remapping, constant-predictor discrete effect, time-varying-predictor discrete effect (Eq. 14), exact scalar discrete process noise (Driver et al., 2017, Eq. 3), lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; `asymDIFFUSION`), trait-plus-state variance (Driver et al., 2017, §4.3 `TRAITVAR`; not process noise), observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5; Table 2 `MANIFESTVAR` is `Θ`, not `Var(y)`; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; `Θ` does not enter lagged observed covariance; observed-indicator mean is `τ + λ μ`; `MANIFESTMEANS` is not `E(y)`; `CINT` is not `MANIFESTMEANS`; discrete latent mean is `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ`; `T0MEANS` is not `μ_t`; `CINT` is not the discrete increment; evolved observed mean is `τ + λ μ_t`; `τ + λ μ_0` is not `E(y_t)`; contemporaneous `TDPREDEFFECT` impulse is `m x`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that contemporaneous impulse is `τ + λ(μ_t + m x)`, and `τ + λ μ_t` is not that observed mean; time-independent `TIPREDEFFECT` increment is `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, not `M x`, not Voelkle Eq. 14, and not the coefficient `B`; Eq. 5 of that increment is `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`; within-interval `TDPREDEFFECT` carry is `e^{A(t−u)} M x` for `t0 < u < t`, not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that carry is `τ + λ(μ_t + e^{a(t−u)} m x)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that carried observed mean when `u ≠ t`; first-occasion `T0TIPREDEFFECT` shift is `t0_b z` and Eq. 3 first-summand carry is `e^{A Δt} t0_b z` (`T0TIPREDEFFECT` is not `TIPREDEFFECT` `B`; `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`; `e^{A Δt} t0_b z` is not `t0_b z`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_b z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean), first-occasion `T0TDPREDEFFECT` shift is `t0_m x0` and Eq. 3 first-summand carry is `e^{A Δt} t0_m x0` (`T0TDPREDEFFECT` is not `TDPREDEFFECT` `M`; `t0_m x0` is not `M x`; `e^{A Δt} t0_m x0` is not `t0_m x0`; `e^{A Δt} t0_m x0` is not `e^{A(t−u)} M x` for `t0 < u < t`; `t0_m x0` is not `t0_b z`; an impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_m x0)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean; `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean; §7.2 level-change `CINT` is `κ = −a m x` with `a < 0` so `−κ / a = m x` (`−a m x` is not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`, and not the extra near-zero-drift latent process also named in §7.2; Eq. 3 of that setting is `(1 − e^{a Δt}) m x`, which is not `m x`, not `κ`, and not `TIPREDEFFECT`; §7.2 extra-process contribution is `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (`ε = a` is `a_{ηξ} x Δt e^{a Δt}`; identification `TDPREDEFFECT` on the extra process is 1; printed extra `DRIFT` is `−0.000001`; not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`; `ε ≥ 0` fails closed; Eq. 5 of that contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)`; the extra process has `LAMBDA` 0 and is not an observed indicator; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; the contribution is not `E(y_t)`; the evolved-plus-contribution latent mean is not `E(y_t)`; after-t0 extra-process `TDPREDEFFECT` is `a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)` for `t0 < u < t` while `μ_t` uses `Δt`; Eq. 5 of that after-t0 contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)`; the first-occasion extra-process observed mean is not that observed mean when `u ≠ t0`; `e^{a(t−u)} m x` is a Dirac on the original process, not this `DRIFT` drive; §7.2 `asymTIPREDEFFECT` is `-B z / a` for `a < 0` (`-B z / a` is not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; §7.2 `addedTIPREDVAR` is `(B / a)² v`, not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`; Table 2 `asymCINT` is `-κ / a` for `a < 0` and is not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; p. 16 stationary `T0MEANS` is `-κ / a + −B z / a` and is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean; Eq. 5 of that constrained mean is `τ + λ(−κ / a + −B z / a)`; `τ + λ μ_0` is not that observed mean; `τ + λ(−κ / a)` is not that observed mean when `B z ≠ 0`; `τ + λ μ_t` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`; stationary `T0VAR` is `trait + −q / (2 a) + (B / a)² v` (not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`); lagged stationary `T0VAR` is `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (trait and `addedTIPREDVAR` do not decay; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map; Eq. 5 of that lagged covariance is `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ`; `Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance); later-occasion stationary `T0VAR` is `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt`; `Q_Δt` is not that later map; Eq. 5 of that later-occasion variance is `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ`; lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not `Var(y_t)`; the later-occasion latent variance is not `Var(y_t)`); predetermined later-occasion `T0VAR` is `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (free `T0VAR` `p_0` is not that later map; setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map; stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map; Eq. 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ`; `MANIFESTVAR` is not `Var(y_t)`; the predetermined later-occasion latent variance is not `Var(y_t)`; stationary later observed variance is not that observed variance when `p_0` is free); predetermined lagged `T0VAR` is `trait + e^{a Δt} p_0 + (B / a)² v` (free `T0VAR` `p_0` is not that lagged map; setting `p_0 = −q / (2 a)` recovers the stationary lagged map; stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map; later-occasion variance includes `Q_Δt` and is not that lagged map; Eq. 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ`; `MANIFESTVAR` does not enter; the predetermined lagged latent covariance is not that observed covariance; predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance; stationary lagged observed covariance is not that observed covariance when `p_0` is free; the predetermined first-occasion variance of §4.3 predetermined `T0VAR` is `trait + p_0 + (B / a)² v`; free `p_0` is not that map; stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free; lagged covariance decays the state and is not that map; later-occasion variance includes `Q_Δt` and is not that map; Eq. 5 of that predetermined first-occasion variance is `λ²(trait + p_0 + (B / a)² v) + θ + ψ`; `MANIFESTVAR` is not that first-occasion observed variance; the predetermined first-occasion latent variance is not that observed variance; stationary first-occasion observed variance is not that observed variance when `p_0` is free; predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance; later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z; first-occasion lagged omits `e^{a s} Q_u`; later-occasion variance does not lag; stationary lagged uses `−q / (2 a)`; decaying the later total is not that map; Eq. 5 of that later-start lagged covariance is `λ²` of it plus `ψ`; `Θ` does not enter; first-occasion lagged observed omits `e^{a s} Q_u`; later observed variance includes `Q_u` and `θ`; later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z; later-occasion variance at `u` omits `Q_s`; later-start lagged covariance omits `Q_s`; stationary later uses `−q / (2 a)`; evolving the later total as if it were all state is not that map; ignoring `startoffset` omits `e^{2 a s} Q_u`; Eq. 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`; `MANIFESTVAR` is not that observed variance; p. 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4; unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`; the §7.1 trait-plus-state autocorrelation uses `TRAITVAR` and is not `discreteDRIFTstd`; `TRAITVAR` is not the standardisation variance; p. 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4; unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`; the continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`; `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z; unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`; the discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`; `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z); unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`; the discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`; `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z; unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`; `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z; unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`; the asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`; `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z; the affected variance is free `T0VAR`, not `asymDIFFUSION`; unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`; `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`; `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`; `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); 2017-era `addedT0TIPREDVAR` is `t0_b² v` (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z; `T0TIPREDEFFECT %*% TIPREDVAR %*% t(T0TIPREDEFFECT)` immediately after `T0TIPREDEFFECTstd`; form `t0_b` first, then square, then multiply by `v`; a zero coefficient or zero predictor variance is exactly zero; free `T0TIPREDEFFECT` does not require `a < 0`; `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map; `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance; free `T0VAR` is not this extra TI variance; `TRAITVAR` is not this extra TI variance; Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z; form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; `t0_b² v` is the latent extra, not the observed extra; `λ² p_0 + θ` is first-occasion observed variance, not this extra; `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra; `MANIFESTVAR` `θ` is not this extra; Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v`; form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; lasting asymptotic extra requires `a < 0`; `(B / a)² v` is the latent extra, not the observed extra; `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra; `λ² p + θ` is stationary observed variance, not this extra; `MANIFESTVAR` `θ` is not this extra; p. 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance; unstandardised `M` is not `TDPREDEFFECTstd`; `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`; intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`; `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`; Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance; unstandardised `t0_m` is not `T0TDPREDEFFECTstd`; `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`; `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`; `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`; free `T0VAR` does not require `a < 0`; p. 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; default ridge is 0); unstandardised `T0VAR` is not `T0VARstd`; `T0TDPREDEFFECTstd` is not `T0VARstd`; `addedT0TIPREDVAR` is not `T0VARstd`; p. 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend); unstandardised `TRAITVAR` is not `TRAITVARstd`; `T0VARstd` is not `TRAITVARstd` even when both equal 1; `addedT0TIPREDVAR` is not `TRAITVARstd`; p. 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0); unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`; `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1; `MANIFESTVAR` is not `MANIFESTTRAITVARstd`; p. 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug); unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`; `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1; Equation 5 `Var(y)` is not `MANIFESTVARstd`; p. 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`); unstandardised `TIPREDVAR` is not `TIPREDVARstd`; `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1; §7.2 `addedTIPREDVAR` is not `TIPREDVARstd`; p. 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`); unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`; `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1; `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`; p. 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `discreteCINT` is not `discreteCINTstd`; `κ / √p` is not `discreteCINTstd`; `(-κ / a) / √p` is not `discreteCINTstd`; `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`; unstandardised `asymCINT` is not `asymCINTstd`; `κ / √p` is not `asymCINTstd`; `discreteCINTstd` is not `asymCINTstd`; `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`; unstandardised `T0MEANS` is not `T0MEANSstd`; `T0VARstd` is not `T0MEANSstd`; `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`;))))), irregular already-centered residual lag, Rubin `T` on OLS loadings, and strong-gated latent means (two-observation residual variance is identically `0` and caps at strong/scalar; Putnick & Bornstein, 2016) | +| `psychometric_core` | posterior-aware structural input gates, CWC within/between OLS plus the contextual effect, event-time log-rate, unequal-interval discrete-lag remapping, constant-predictor discrete effect, time-varying-predictor discrete effect (Eq. 14), exact scalar discrete process noise (Driver et al., 2017, Eq. 3), lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; `asymDIFFUSION`), trait-plus-state variance and expected autocorrelation (Driver et al., 2017, §4.3 `TRAITVAR`; §7.1; not process noise, not discreteDRIFT, not discreteDRIFTstd), observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5; Table 2 `MANIFESTVAR` is `Θ`, not `Var(y)`; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; `Θ` does not enter lagged observed covariance; observed-indicator mean is `τ + λ μ`; `MANIFESTMEANS` is not `E(y)`; `CINT` is not `MANIFESTMEANS`; discrete latent mean is `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ`; `T0MEANS` is not `μ_t`; `CINT` is not the discrete increment; evolved observed mean is `τ + λ μ_t`; `τ + λ μ_0` is not `E(y_t)`; contemporaneous `TDPREDEFFECT` impulse is `m x`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that contemporaneous impulse is `τ + λ(μ_t + m x)`, and `τ + λ μ_t` is not that observed mean; time-independent `TIPREDEFFECT` increment is `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, not `M x`, not Voelkle Eq. 14, and not the coefficient `B`; Eq. 5 of that increment is `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`; within-interval `TDPREDEFFECT` carry is `e^{A(t−u)} M x` for `t0 < u < t`, not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that carry is `τ + λ(μ_t + e^{a(t−u)} m x)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that carried observed mean when `u ≠ t`; first-occasion `T0TIPREDEFFECT` shift is `t0_b z` and Eq. 3 first-summand carry is `e^{A Δt} t0_b z` (`T0TIPREDEFFECT` is not `TIPREDEFFECT` `B`; `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`; `e^{A Δt} t0_b z` is not `t0_b z`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_b z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean), first-occasion `T0TDPREDEFFECT` shift is `t0_m x0` and Eq. 3 first-summand carry is `e^{A Δt} t0_m x0` (`T0TDPREDEFFECT` is not `TDPREDEFFECT` `M`; `t0_m x0` is not `M x`; `e^{A Δt} t0_m x0` is not `t0_m x0`; `e^{A Δt} t0_m x0` is not `e^{A(t−u)} M x` for `t0 < u < t`; `t0_m x0` is not `t0_b z`; an impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_m x0)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean; `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean; §7.2 level-change `CINT` is `κ = −a m x` with `a < 0` so `−κ / a = m x` (`−a m x` is not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`, and not the extra near-zero-drift latent process also named in §7.2; Eq. 3 of that setting is `(1 − e^{a Δt}) m x`, which is not `m x`, not `κ`, and not `TIPREDEFFECT`; §7.2 extra-process contribution is `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (`ε = a` is `a_{ηξ} x Δt e^{a Δt}`; identification `TDPREDEFFECT` on the extra process is 1; printed extra `DRIFT` is `−0.000001`; not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`; `ε ≥ 0` fails closed; Eq. 5 of that contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)`; the extra process has `LAMBDA` 0 and is not an observed indicator; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; the contribution is not `E(y_t)`; the evolved-plus-contribution latent mean is not `E(y_t)`; after-t0 extra-process `TDPREDEFFECT` is `a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)` for `t0 < u < t` while `μ_t` uses `Δt`; Eq. 5 of that after-t0 contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)`; the first-occasion extra-process observed mean is not that observed mean when `u ≠ t0`; `e^{a(t−u)} m x` is a Dirac on the original process, not this `DRIFT` drive; §7.2 `asymTIPREDEFFECT` is `-B z / a` for `a < 0` (`-B z / a` is not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; §7.2 `addedTIPREDVAR` is `(B / a)² v`, not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`; Table 2 `asymCINT` is `-κ / a` for `a < 0` and is not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; p. 16 stationary `T0MEANS` is `-κ / a + −B z / a` and is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean; Eq. 5 of that constrained mean is `τ + λ(−κ / a + −B z / a)`; `τ + λ μ_0` is not that observed mean; `τ + λ(−κ / a)` is not that observed mean when `B z ≠ 0`; `τ + λ μ_t` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`; stationary `T0VAR` is `trait + −q / (2 a) + (B / a)² v` (not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`); lagged stationary `T0VAR` is `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (trait and `addedTIPREDVAR` do not decay; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map; Eq. 5 of that lagged covariance is `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ`; `Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance); later-occasion stationary `T0VAR` is `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt`; `Q_Δt` is not that later map; Eq. 5 of that later-occasion variance is `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ`; lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not `Var(y_t)`; the later-occasion latent variance is not `Var(y_t)`); predetermined later-occasion `T0VAR` is `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (free `T0VAR` `p_0` is not that later map; setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map; stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map; Eq. 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ`; `MANIFESTVAR` is not `Var(y_t)`; the predetermined later-occasion latent variance is not `Var(y_t)`; stationary later observed variance is not that observed variance when `p_0` is free); predetermined lagged `T0VAR` is `trait + e^{a Δt} p_0 + (B / a)² v` (free `T0VAR` `p_0` is not that lagged map; setting `p_0 = −q / (2 a)` recovers the stationary lagged map; stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map; later-occasion variance includes `Q_Δt` and is not that lagged map; Eq. 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ`; `MANIFESTVAR` does not enter; the predetermined lagged latent covariance is not that observed covariance; predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance; stationary lagged observed covariance is not that observed covariance when `p_0` is free; the predetermined first-occasion variance of §4.3 predetermined `T0VAR` is `trait + p_0 + (B / a)² v`; free `p_0` is not that map; stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free; lagged covariance decays the state and is not that map; later-occasion variance includes `Q_Δt` and is not that map; Eq. 5 of that predetermined first-occasion variance is `λ²(trait + p_0 + (B / a)² v) + θ + ψ`; `MANIFESTVAR` is not that first-occasion observed variance; the predetermined first-occasion latent variance is not that observed variance; stationary first-occasion observed variance is not that observed variance when `p_0` is free; predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance; later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z; first-occasion lagged omits `e^{a s} Q_u`; later-occasion variance does not lag; stationary lagged uses `−q / (2 a)`; decaying the later total is not that map; Eq. 5 of that later-start lagged covariance is `λ²` of it plus `ψ`; `Θ` does not enter; first-occasion lagged observed omits `e^{a s} Q_u`; later observed variance includes `Q_u` and `θ`; later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z; later-occasion variance at `u` omits `Q_s`; later-start lagged covariance omits `Q_s`; stationary later uses `−q / (2 a)`; evolving the later total as if it were all state is not that map; ignoring `startoffset` omits `e^{2 a s} Q_u`; Eq. 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`; `MANIFESTVAR` is not that observed variance; p. 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4; unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`; the §7.1 trait-plus-state autocorrelation uses `TRAITVAR` and is not `discreteDRIFTstd`; `TRAITVAR` is not the standardisation variance; p. 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4; unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`; the continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`; `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z; unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`; the discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`; `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z); unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`; the discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`; `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z; unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`; `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z; unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`; the asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`; `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z; the affected variance is free `T0VAR`, not `asymDIFFUSION`; unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`; `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`; `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`; `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); 2017-era `addedT0TIPREDVAR` is `t0_b² v` (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z; `T0TIPREDEFFECT %*% TIPREDVAR %*% t(T0TIPREDEFFECT)` immediately after `T0TIPREDEFFECTstd`; form `t0_b` first, then square, then multiply by `v`; a zero coefficient or zero predictor variance is exactly zero; free `T0TIPREDEFFECT` does not require `a < 0`; `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map; `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance; free `T0VAR` is not this extra TI variance; `TRAITVAR` is not this extra TI variance; Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z; form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; `t0_b² v` is the latent extra, not the observed extra; `λ² p_0 + θ` is first-occasion observed variance, not this extra; `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra; `MANIFESTVAR` `θ` is not this extra; Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v`; form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; lasting asymptotic extra requires `a < 0`; `(B / a)² v` is the latent extra, not the observed extra; `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra; `λ² p + θ` is stationary observed variance, not this extra; `MANIFESTVAR` `θ` is not this extra; p. 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance; unstandardised `M` is not `TDPREDEFFECTstd`; `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`; intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`; `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`; Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance; unstandardised `t0_m` is not `T0TDPREDEFFECTstd`; `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`; `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`; `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`; free `T0VAR` does not require `a < 0`; p. 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; default ridge is 0); unstandardised `T0VAR` is not `T0VARstd`; `T0TDPREDEFFECTstd` is not `T0VARstd`; `addedT0TIPREDVAR` is not `T0VARstd`; p. 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend); unstandardised `TRAITVAR` is not `TRAITVARstd`; `T0VARstd` is not `TRAITVARstd` even when both equal 1; `addedT0TIPREDVAR` is not `TRAITVARstd`; p. 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0); unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`; `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1; `MANIFESTVAR` is not `MANIFESTTRAITVARstd`; p. 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug); unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`; `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1; Equation 5 `Var(y)` is not `MANIFESTVARstd`; p. 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`); unstandardised `TIPREDVAR` is not `TIPREDVARstd`; `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1; §7.2 `addedTIPREDVAR` is not `TIPREDVARstd`; p. 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`); unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`; `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1; `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`; p. 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `discreteCINT` is not `discreteCINTstd`; `κ / √p` is not `discreteCINTstd`; `(-κ / a) / √p` is not `discreteCINTstd`; `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`; unstandardised `asymCINT` is not `asymCINTstd`; `κ / √p` is not `asymCINTstd`; `discreteCINTstd` is not `asymCINTstd`; `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`; unstandardised `T0MEANS` is not `T0MEANSstd`; `T0VARstd` is not `T0MEANSstd`; `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`; `MANIFESTMEANSstd` is `τ / √θ` after strictly positive `MANIFESTVAR`; unstandardised `MANIFESTMEANS` is not `MANIFESTMEANSstd`; `MANIFESTVARstd` is not `MANIFESTMEANSstd`; `τ / √(λ² Var(η) + θ)` is not `MANIFESTMEANSstd`; p. 16 `CINTstd` is `κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `CINT` is not `CINTstd`; `asymCINTstd` is not `CINTstd`; `discreteCINTstd` is not `CINTstd`; `κ / √(trait + p + added)` is not `CINTstd`;))))), irregular already-centered residual lag, Rubin `T` on OLS loadings, and strong-gated latent means (two-observation residual variance is identically `0` and caps at strong/scalar; Putnick & Bornstein, 2016) | +| `psychometric_core` | posterior-aware structural input gates, CWC within/between OLS plus the contextual effect, event-time log-rate, unequal-interval discrete-lag remapping, constant-predictor discrete effect, time-varying-predictor discrete effect (Eq. 14), exact scalar discrete process noise (Driver et al., 2017, Eq. 3), lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; `asymDIFFUSION`), trait-plus-state variance and expected autocorrelation (Driver et al., 2017, §4.3 `TRAITVAR`; §7.1; not process noise, not discreteDRIFT, not discreteDRIFTstd), observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5; Table 2 `MANIFESTVAR` is `Θ`, not `Var(y)`; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; `Θ` does not enter lagged observed covariance; observed-indicator mean is `τ + λ μ`; `MANIFESTMEANS` is not `E(y)`; `CINT` is not `MANIFESTMEANS`; discrete latent mean is `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ`; `T0MEANS` is not `μ_t`; `CINT` is not the discrete increment; evolved observed mean is `τ + λ μ_t`; `τ + λ μ_0` is not `E(y_t)`; contemporaneous `TDPREDEFFECT` impulse is `m x`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that contemporaneous impulse is `τ + λ(μ_t + m x)`, and `τ + λ μ_t` is not that observed mean; time-independent `TIPREDEFFECT` increment is `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, not `M x`, not Voelkle Eq. 14, and not the coefficient `B`; Eq. 5 of that increment is `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`; within-interval `TDPREDEFFECT` carry is `e^{A(t−u)} M x` for `t0 < u < t`, not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that carry is `τ + λ(μ_t + e^{a(t−u)} m x)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that carried observed mean when `u ≠ t`; first-occasion `T0TIPREDEFFECT` shift is `t0_b z` and Eq. 3 first-summand carry is `e^{A Δt} t0_b z` (`T0TIPREDEFFECT` is not `TIPREDEFFECT` `B`; `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`; `e^{A Δt} t0_b z` is not `t0_b z`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_b z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean), first-occasion `T0TDPREDEFFECT` shift is `t0_m x0` and Eq. 3 first-summand carry is `e^{A Δt} t0_m x0` (`T0TDPREDEFFECT` is not `TDPREDEFFECT` `M`; `t0_m x0` is not `M x`; `e^{A Δt} t0_m x0` is not `t0_m x0`; `e^{A Δt} t0_m x0` is not `e^{A(t−u)} M x` for `t0 < u < t`; `t0_m x0` is not `t0_b z`; an impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_m x0)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean; `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean; §7.2 level-change `CINT` is `κ = −a m x` with `a < 0` so `−κ / a = m x` (`−a m x` is not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`, and not the extra near-zero-drift latent process also named in §7.2; Eq. 3 of that setting is `(1 − e^{a Δt}) m x`, which is not `m x`, not `κ`, and not `TIPREDEFFECT`; §7.2 extra-process contribution is `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (`ε = a` is `a_{ηξ} x Δt e^{a Δt}`; identification `TDPREDEFFECT` on the extra process is 1; printed extra `DRIFT` is `−0.000001`; not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`; `ε ≥ 0` fails closed; Eq. 5 of that contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)`; the extra process has `LAMBDA` 0 and is not an observed indicator; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; the contribution is not `E(y_t)`; the evolved-plus-contribution latent mean is not `E(y_t)`; after-t0 extra-process `TDPREDEFFECT` is `a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)` for `t0 < u < t` while `μ_t` uses `Δt`; Eq. 5 of that after-t0 contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)`; the first-occasion extra-process observed mean is not that observed mean when `u ≠ t0`; `e^{a(t−u)} m x` is a Dirac on the original process, not this `DRIFT` drive; §7.2 `asymTIPREDEFFECT` is `-B z / a` for `a < 0` (`-B z / a` is not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; §7.2 `addedTIPREDVAR` is `(B / a)² v`, not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`; Table 2 `asymCINT` is `-κ / a` for `a < 0` and is not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; p. 16 stationary `T0MEANS` is `-κ / a + −B z / a` and is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean; Eq. 5 of that constrained mean is `τ + λ(−κ / a + −B z / a)`; `τ + λ μ_0` is not that observed mean; `τ + λ(−κ / a)` is not that observed mean when `B z ≠ 0`; `τ + λ μ_t` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`; stationary `T0VAR` is `trait + −q / (2 a) + (B / a)² v` (not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`); lagged stationary `T0VAR` is `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (trait and `addedTIPREDVAR` do not decay; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map; Eq. 5 of that lagged covariance is `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ`; `Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance); later-occasion stationary `T0VAR` is `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt`; `Q_Δt` is not that later map; Eq. 5 of that later-occasion variance is `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ`; lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not `Var(y_t)`; the later-occasion latent variance is not `Var(y_t)`); predetermined later-occasion `T0VAR` is `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (free `T0VAR` `p_0` is not that later map; setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map; stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map; Eq. 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ`; `MANIFESTVAR` is not `Var(y_t)`; the predetermined later-occasion latent variance is not `Var(y_t)`; stationary later observed variance is not that observed variance when `p_0` is free); predetermined lagged `T0VAR` is `trait + e^{a Δt} p_0 + (B / a)² v` (free `T0VAR` `p_0` is not that lagged map; setting `p_0 = −q / (2 a)` recovers the stationary lagged map; stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map; later-occasion variance includes `Q_Δt` and is not that lagged map; Eq. 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ`; `MANIFESTVAR` does not enter; the predetermined lagged latent covariance is not that observed covariance; predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance; stationary lagged observed covariance is not that observed covariance when `p_0` is free; the predetermined first-occasion variance of §4.3 predetermined `T0VAR` is `trait + p_0 + (B / a)² v`; free `p_0` is not that map; stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free; lagged covariance decays the state and is not that map; later-occasion variance includes `Q_Δt` and is not that map; Eq. 5 of that predetermined first-occasion variance is `λ²(trait + p_0 + (B / a)² v) + θ + ψ`; `MANIFESTVAR` is not that first-occasion observed variance; the predetermined first-occasion latent variance is not that observed variance; stationary first-occasion observed variance is not that observed variance when `p_0` is free; predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance; later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z; first-occasion lagged omits `e^{a s} Q_u`; later-occasion variance does not lag; stationary lagged uses `−q / (2 a)`; decaying the later total is not that map; Eq. 5 of that later-start lagged covariance is `λ²` of it plus `ψ`; `Θ` does not enter; first-occasion lagged observed omits `e^{a s} Q_u`; later observed variance includes `Q_u` and `θ`; later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z; later-occasion variance at `u` omits `Q_s`; later-start lagged covariance omits `Q_s`; stationary later uses `−q / (2 a)`; evolving the later total as if it were all state is not that map; ignoring `startoffset` omits `e^{2 a s} Q_u`; Eq. 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`; `MANIFESTVAR` is not that observed variance; p. 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4; unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`; the §7.1 trait-plus-state autocorrelation uses `TRAITVAR` and is not `discreteDRIFTstd`; `TRAITVAR` is not the standardisation variance; p. 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4; unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`; the continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`; `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z; unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`; the discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`; `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z); unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`; the discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`; `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z; unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`; `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z; unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`; the asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`; `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z; the affected variance is free `T0VAR`, not `asymDIFFUSION`; unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`; `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`; `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`; `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); 2017-era `addedT0TIPREDVAR` is `t0_b² v` (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z; `T0TIPREDEFFECT %*% TIPREDVAR %*% t(T0TIPREDEFFECT)` immediately after `T0TIPREDEFFECTstd`; form `t0_b` first, then square, then multiply by `v`; a zero coefficient or zero predictor variance is exactly zero; free `T0TIPREDEFFECT` does not require `a < 0`; `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map; `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance; free `T0VAR` is not this extra TI variance; `TRAITVAR` is not this extra TI variance; Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z; form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; `t0_b² v` is the latent extra, not the observed extra; `λ² p_0 + θ` is first-occasion observed variance, not this extra; `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra; `MANIFESTVAR` `θ` is not this extra; Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v`; form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; lasting asymptotic extra requires `a < 0`; `(B / a)² v` is the latent extra, not the observed extra; `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra; `λ² p + θ` is stationary observed variance, not this extra; `MANIFESTVAR` `θ` is not this extra; p. 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance; unstandardised `M` is not `TDPREDEFFECTstd`; `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`; intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`; `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`; Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance; unstandardised `t0_m` is not `T0TDPREDEFFECTstd`; `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`; `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`; `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`; free `T0VAR` does not require `a < 0`; p. 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; default ridge is 0); unstandardised `T0VAR` is not `T0VARstd`; `T0TDPREDEFFECTstd` is not `T0VARstd`; `addedT0TIPREDVAR` is not `T0VARstd`; p. 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend); unstandardised `TRAITVAR` is not `TRAITVARstd`; `T0VARstd` is not `TRAITVARstd` even when both equal 1; `addedT0TIPREDVAR` is not `TRAITVARstd`; p. 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0); unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`; `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1; `MANIFESTVAR` is not `MANIFESTTRAITVARstd`; p. 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug); unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`; `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1; Equation 5 `Var(y)` is not `MANIFESTVARstd`; p. 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`); unstandardised `TIPREDVAR` is not `TIPREDVARstd`; `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1; §7.2 `addedTIPREDVAR` is not `TIPREDVARstd`; p. 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`); unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`; `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1; `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`; p. 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `discreteCINT` is not `discreteCINTstd`; `κ / √p` is not `discreteCINTstd`; `(-κ / a) / √p` is not `discreteCINTstd`; `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`; unstandardised `asymCINT` is not `asymCINTstd`; `κ / √p` is not `asymCINTstd`; `discreteCINTstd` is not `asymCINTstd`; `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`; unstandardised `T0MEANS` is not `T0MEANSstd`; `T0VARstd` is not `T0MEANSstd`; `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`;))))), irregular already-centered residual lag, Rubin `T` on OLS loadings, and strong-gated latent means (two-observation residual variance is identically `0` and caps at strong/scalar; Putnick & Bornstein, 2016) | Foundation crates expose only tested contracts. Empty façades are not public diff --git a/CHANGELOG.md b/CHANGELOG.md index 062a69412..3b0f27efe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,8 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang ## [Unreleased] +- `psychometric_core` recovers the Driver, Oud, and Voelkle (2017, §7.1, pp. 18–19; Eq. 3–4, pp. 4–5; §4.3, p. 9; §7.2 `addedTIPREDVAR`; JSS PDF re-opened 2026-09-01T06:30Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104) scalar trait-plus-state expected autocorrelation. Section 7.1 names traits the stable between-subject differences (unit-level unobserved heterogeneity) and warns that omitting `TRAITVAR` mixes between- and within-person information; Figure 4 shows auto-effects (persistence) reduced after traits. The paper does not print this ratio; it is the covariance ratio of the already-recovered §4.3 maps. Form the lagged covariance `trait + e^{a Δt} p` first, then the contemporaneous total `trait + p`, then include `addedTIPREDVAR`, then `(trait + e^{a Δt} p + added) / (trait + p + added)`. A zero trait and a zero TI extra recovers `e^{a Δt}` numerically and remains a distinct named quantity. A zero state with a positive trait or extra is exactly 1. A zero total variance fails closed. Growing `a > 0` is a covariance ratio of those named maps and is kept. Unstandardised `discreteDRIFT` `e^{a Δt}` is the state auto-effect, not this total-variance ratio. Page 16 `discreteDRIFTstd` standardises `DRIFT` using only within-subject `asymDIFFUSION` (footnote 4), not the total. `TRAITVAR` is not the standardisation variance. Meredith (1993) remains unread (Unpaywall historically `is_oa: false`; Springer `content/pdf` is an HTML stub). Mislevy (1991, *Psychometrika, 56*, 177–196) remains unread on the same terms (DOI `10.1007/bf02294457`). Still not a Kalman filter, not a matrix `expm`, not ESEM estimation, not DSEM, not RI-CLPM, and not ctsem estimation. + - `event_core` adds bounded Allen interval-consistency classification, atomic path-consistency closure, contradiction/resource refusals, and an explicit dependency-error fallback without claiming unrestricted global satisfiability. - `psychometric_core` recovers the Driver, Oud, and Voelkle (2017, Table 2, p. 12 `MANIFESTTRAITVAR`; §7.1, p. 19; p. 16 `MANIFESTTRAITVARstd`; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-27T14:20Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104) scalar standardised manifest-trait variance on current main after `0ce16e8` dropped the pre-consolidation code while research notes already named the map (register items 83–84). Table 2 names `MANIFESTTRAITVAR` `Ψ_τ` the additional time-invariant variance-covariance on the measurement level and sets it `NULL` when there is no manifest trait. Equation 5 writes `Γ ~ N(τ, Ψ)` and names that covariance the manifest traits. Section 7.1 names manifest traits stable individual differences in indicator levels, distinct from process-level `TRAITVAR` `φ_ξ`. Page 16 prints standardised matrices with the suffix `std` when appropriate. The printed example on p. 16 is `discreteDRIFTstd`, not `MANIFESTTRAITVARstd`. Footnote 4 standardises using only the relevant variance, not the total. The relevant variance for that named indicator-level correlation is `MANIFESTTRAITVAR`, not process-level `TRAITVAR` and not residual `MANIFESTVAR` `θ`. The 2017-era source forms `MANIFESTTRAITVARstd` only when `MANIFESTTRAITVAR != 0`, as `solve(sqrt(diag(MANIFESTTRAITVAR) + ridging)) %&% MANIFESTTRAITVAR` when `verbose = TRUE`. OpenMx `%&%` is `t(A) %*% B %*% A`. Unlike `TRAITVARstd`, that formation adds `diag(c(ridging), n.manifest)`. The default `ridging = FALSE` adds 0, not `0.0001`; that ridge is a numerical hack and is not this exact map. The scalar correlation is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR`. Form strictly positive `ψ` first, then `1 / √ψ`, then `(1 / √ψ) ψ (1 / √ψ)`. Unstandardised `MANIFESTTRAITVAR` is defined for a zero trait; standardised `MANIFESTTRAITVAR` is not. Zero `MANIFESTTRAITVAR` skips forming `MANIFESTTRAITVARstd` in the 2017-era source and fails closed here. Indicator-level trait variance is an event-time structural quantity, so a non-event clock fails closed. `MANIFESTTRAITVAR` does not require stable `a < 0`. Distinct positive `ψ` recover the same 1. `trait / trait = 1` is `TRAITVARstd` and recovers the same number and remains a distinct named quantity. `θ` is `MANIFESTVAR` and is measurement error, not this correlation. Meredith (1993) remains unread (web search 2026-08-27T14:20Z: Springer/Cambridge Core paywalled; Unpaywall historically `is_oa: false`; Springer `content/pdf` is an HTML stub). Mislevy (1991, *Psychometrika, 56*, 177–196) remains unread on the same terms (DOI `10.1007/bf02294457`). Still not a Kalman filter, not a matrix `expm`, not ESEM estimation, not DSEM, and not ctsem estimation. diff --git a/CLAUDE.md b/CLAUDE.md index 339692cf0..5096a3e74 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -15,7 +15,7 @@ Read and follow `AGENTS.md` before changing this repository. The repository-wide - Do not remove repeated report language with global stopword lists or use TF-IDF/BM25 as inferential weights. Model template, section, copied-text, style, modality, and corpus-background sources explicitly. - Do not treat raw topic proportions as ordinary Euclidean indicators. Use logistic-normal coordinates or valid log-ratio coordinates and propagate posterior uncertainty into ESEM/DSEM. - Do not treat metric/weak invariance as a latent-mean license. Strong (equal loading and intercept) or strict is required; `#84` `metric` licenses shared metric meaning only. Putnick and Bornstein (2016, PMC5145197 opened 2026-08-19T22:15Z) require scalar invariance before latent-mean comparison; residual invariance is not a prerequisite. Two-observation series have no residual degrees of freedom (`ordinary_least_squares_fit` returns residual variance `0`) and cap at strong/scalar; they still license means. This is two-group OLS, not MGCFA. Meredith (1993) names remain unread labels (Unpaywall/OpenAlex 2026-08-25T11:32Z: closed). -- Do not use the difference quotient as a continuous-time rate. The scalar map is `a = ln(φ) / Δt` on event time. Discrete lags from unequal event intervals are not one coefficient; remap them through that log-rate. Binary64 `exp(a Δt) = 0` is not a discrete lag. A constant predictor's discrete effect is Voelkle et al. (2012, Eq. 12), evaluated as `a_yx (expm1(z) / a_xx)` with `z = a_xx Δt` so a finite result is not lost when `z` overflows to `-∞` or when `a_yx Δt` overflows. When `expm1(z)` overflows at a finite `z`, rewrite in log space; a zero continuous effect is exactly zero; an overflowing `a_yx/a_xx` rewrite term fails closed. The first-order product is the underflow limit of that equation, not the general constant-predictor discrete effect. A time-varying predictor whose sampling interval equals its constancy interval uses Voelkle et al. (2012, Eq. 14): `b* = a_yx Δt`. Unmatched intervals fail closed (Oud & Jansen, 2000, unread). Discrete process noise is Driver et al. (2017, Eq. 3): `Q_Δt = 0.5 q (expm1(z) / a)` with `z = 2 (a Δt)` and `q = G G⊤ ≥ 0`; do not form `2 a` first; `a = 0` and `z → 0` recover `q Δt`; a zero diffusion is exactly zero; an overflowing rewrite scale `0.5 q / a` fails closed; this is not a Kalman filter. `Q_Δt` is `cov(η_t | η_{t-1})`, not `Var(η_t)`. The lagged covariance is `exp(a Δt) p` and the unconditional variance is `exp(2 a Δt) p + Q_Δt` (Driver et al., 2017, Eq. 3–4, pp. 4–5; JSS has no numbered §2.2). A zero diffusion whose `2 (a Δt)` overflows to `+∞` is not a finite `Var(η_t)`. The stationary within-subject variance is the `Δt → ∞` limit of Eq. 4: `-q / (2 a)` for stable `a < 0` (JSS p. 16 `asymDIFFUSION`; §4.3). When `2 a` is finite, form `q / -(2 a)` so `q / a` overflow does not lose a finite result (`q = MAX`, `a = -0.75` → `MAX / 1.5`). When `2 a` overflows, form `(q / a) * -0.5`. Do not form `0.5 q` first (`q = from_bits(1)` underflows). `a ≥ 0` has no finite stationary variance. Finite-interval `Q_Δt` is not that limit. Trait-plus-state variance is `trait + state` and lagged covariance is `trait + exp(a Δt) p` (Driver et al., 2017, §4.3, p. 9). Trait variance is not process noise and not `asymDIFFUSION`. Evolving the summed variance as if it were all state is not that map. This is not RI-CLPM. Observed-indicator variance is `λ² Var(η) + θ` when `MANIFESTTRAITVAR` is zero and `λ² Var(η) + θ + ψ` otherwise (Driver et al., 2017, Eq. 5, p. 5; Table 2, p. 12). Lagged observed covariance is `λ² cov(η_t, η_{t-1}) + ψ`; `MANIFESTVAR` does not enter. Observed-indicator mean is `τ + λ μ` (Driver et al., 2017, Eq. 5; Table 2, p. 12). `MANIFESTMEANS` is `τ`, not `E(y)`. `E(η)` is not `E(y)`. `CINT` is not `MANIFESTMEANS`. `T0MEANS` is not `E(y)`. The discrete latent mean is `μ_t = exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ` (Driver et al., 2017, Eq. 3, p. 4; Table 2, p. 12). `T0MEANS` is not `μ_t`. `CINT` is not that discrete increment. A zero drift is `κ Δt`. Underflow of `exp(a Δt)` to `+0` drops the carried `T0MEANS` and keeps `−κ / a`. The evolved observed mean is `τ + λ μ_t` (Driver et al., 2017, Eq. 5 of that Eq. 3 map). The first-occasion map `τ + λ μ_0` is not `E(y_t)`. `μ_t` is not `E(y_t)`. The contemporaneous time-dependent predictor impulse is `m x` (Driver et al., 2017, Eq. 3 fourth summand; Table 2 `TDPREDEFFECT` is `M`). Form `μ_t` first, then add `m x`. `TDPREDEFFECT` is not `CINT`. `M x` is not `A^{-1}[e^{A Δt} − I] B z` and is not Voelkle et al. (2012, Eq. 14). The §7.2 level-change form is not that impulse. The observed mean of that contemporaneous impulse is `τ + λ(μ_t + m x)` (Driver et al., 2017, Eq. 5 of the Eq. 3 fourth-summand composition). The evolved map `τ + λ μ_t` is not that observed mean. The carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`. The evolved-plus-impulse latent mean is not `E(y_t)`. The time-independent predictor increment is `A^{-1}[e^{A Δt} − I] B z` (Driver et al., 2017, Eq. 3 second summand; Table 2 `TIPREDEFFECT` is `B`). Form `B z` first, then the discrete intercept map. A zero drift is `B z Δt`. `TIPREDEFFECT` is `B`, not that discrete increment. `A^{-1}[e^{A Δt} − I] B z` is not `CINT`, not `M x`, and not Voelkle et al. (2012, Eq. 14). The observed mean of that increment is `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` (Driver et al., 2017, Eq. 5 of the Eq. 3 printed addend after the `T0MEANS` carry and the `CINT` increment). The evolved map `τ + λ μ_t` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`. The evolved-plus-increment latent mean is not `E(y_t)`. The within-interval time-dependent impulse carry is `e^{A(t−u)} M x` for `t0 < u < t` (Driver et al., 2017, Eq. 1–2 Green-function integral of Eq. 2; §7.2 dissipation). Form `m x` first, then `e^{a(t−u)} m x`. A zero drift is `m x` with no dissipation. Underflow of `e^{a(t−u)}` to `+0` is vanishing dissipation and is kept. `e^{A(t−u)} M x` is not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle et al. (2012, Eq. 14). An impulse at `u = t` is the contemporaneous map. An impulse at `u ≤ t0` is already in `η(t0)`. The observed mean of that carry is `τ + λ(μ_t + e^{a(t−u)} m x)` (Driver et al., 2017, Eq. 5 of the Eq. 1–2 carried latent mean). The evolved map `τ + λ μ_t` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean when `u ≠ t`. `MANIFESTMEANS` is not `E(y_t)`. The carried latent mean is not `E(y_t)`. The first-occasion time-independent predictor shift is `t0_b z` (Driver et al., 2017, Table 3 `T0TIPREDEFFECT`; Eq. 3 first summand). Form `t0_b z` first, then `e^{a Δt} t0_b z`. Form `μ_t` first, then add that carry. A zero drift is `t0_b z`. Underflow of `e^{a Δt}` to `+0` is a vanishing carry of the first-occasion shift and is kept. `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`. `e^{A Δt} t0_b z` is not `t0_b z`. `T0TIPREDEFFECT` is the coefficient, not the shift. The observed mean of that first-occasion carry is `τ + λ(μ_t + e^{a Δt} t0_b z)` (Driver et al., 2017, Eq. 5 of the Table 3 / Eq. 3 first-summand composition). The evolved map `τ + λ μ_t` is not that observed mean. The process-increment map `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The impulse-carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t0`. The evolved-plus-carry latent mean is not `E(y_t)`. The first-occasion time-dependent predictor shift is `t0_m x0` (Driver et al., 2017, Table 3 `T0TDPREDEFFECT`; Eq. 3 first summand; JSS PDF re-opened 2026-08-20T19:10Z). Form `t0_m x0` first, then `e^{a Δt} t0_m x0`. Form `μ_t` first, then add that carry. A zero drift is `t0_m x0`. Underflow of `e^{a Δt}` to `+0` is a vanishing carry of the first-occasion shift and is kept. `t0_m x0` is not `M x`, not `e^{A(t−u)} M x` for `t0 < u < t`, not `t0_b z`, not `A^{-1}[e^{A Δt} − I] B z`, and not `CINT`. `e^{A Δt} t0_m x0` is not `t0_m x0`. `T0TDPREDEFFECT` is the coefficient, not the shift. An impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`. The observed mean of that first-occasion TD carry is `τ + λ(μ_t + e^{a Δt} t0_m x0)` (Driver et al., 2017, Eq. 5 of the Table 3 / Eq. 3 first-summand TD composition; JSS PDF re-opened 2026-08-20T19:07Z). The evolved map `τ + λ μ_t` is not that observed mean. The process-increment map `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The impulse-carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t0`. The first-occasion TI map `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean. The evolved-plus-carry latent mean is not `E(y_t)`. The lasting level-change `CINT` is `κ = −a m x` (Driver et al., 2017, §7.2, pp. 20–21; JSS PDF re-opened 2026-08-20T19:45Z). Form `m x` first, then multiply by `−a`. Stable `a < 0` is required so `−κ / a = m x` is an equilibrium offset. `a ≥ 0` cannot hold a new process mean. `−a m x` is not the dissipating Dirac `m x`, not a free `CINT`, and not `A^{-1}[e^{A Δt} − I] B z`. The extra near-zero-drift latent process also named in §7.2 is a different specification and is not this `CINT` setting. Equation 3 maps that intercept as `(1 − e^{a Δt}) m x` (JSS PDF re-opened 2026-08-20T19:50Z). Form the level-change `CINT` first, then the discrete intercept map. Underflow of `e^{a Δt}` to `+0` keeps `m x`. `(1 − e^{a Δt}) m x` is not `m x`, not `κ`, and not `A^{-1}[e^{A Δt} − I] B z`. The printed §7.2 lasting level change is an extra near-zero-drift latent process (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-20T23:10Z). `T0MEANS`, `CINT`, `T0VAR`, `DIFFUSION`, and `TRAITVAR` of that process are fixed to 0; `TDPREDEFFECT` on it is fixed to 1; its `DRIFT` diagonal is very close to 0 (printed example `−0.000001`; precisely 0 causes computational problems); the original process is driven by the `DRIFT` coupling `a_{ηξ}`. After a unit identification impulse the scalar contribution is `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (`ε = a` is `a_{ηξ} x Δt e^{a Δt}`). Form `a_{ηξ} x` first. A zero coupling or zero predictor is exactly zero. `ε ≥ 0` fails closed. That contribution is not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`. The observed mean of that extra-process contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a))` (Driver et al., 2017, Eq. 5 of that §7.2 contribution; JSS PDF re-opened 2026-08-21T06:12Z). The extra process has `LAMBDA` 0 and is not an observed indicator. Original indicators load on the original process after the `DRIFT` coupling. The evolved map `τ + λ μ_t` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The contribution is not `E(y_t)`. The evolved-plus-contribution latent mean is not `E(y_t)`. `T0TDPREDEFFECT` on the extra process begins at `t = 0` and uses `Δt = t − t0` for both the original-process evolution and the extra drive. `TDPREDEFFECT` after `t0` uses `t − u` with `t0 < u < t` for the extra drive while `μ_t` still uses `Δt`. The observed mean of that after-t0 extra-process contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a))` (Driver et al., 2017, Eq. 5 of that §7.2 after-t0 contribution; JSS PDF re-opened 2026-08-21T06:32Z). The first-occasion extra-process observed mean is not that observed mean when `u ≠ t0`. The impulse-carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is a Dirac on the original process and is not that `DRIFT` drive. An impulse at `u = t0` or `u = t` is not interior. The asymptotic time-independent predictor effect is `-B z / a` (Driver et al., 2017, §7.2, pp. 20–21; JSS PDF opened 2026-08-21T13:08Z). Form `B z` first, then divide by `-a`. Stable `a < 0` is required. `a ≥ 0` cannot hold a finite process-mean change. `-B z / a` is not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`. The asymptotic time-independent predictor variance is `(B / a)² v` (Driver et al., 2017, §7.2, pp. 20–21 `addedTIPREDVAR`). Form the unit asymptotic effect first, then square, then multiply by `v`. `(B / a)² v` is not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`. The asymptotic continuous intercept is `-κ / a` (Driver et al., 2017, Table 2, p. 12 `asymCINT`; Eq. 3 as `Δt → ∞`; JSS PDF opened 2026-08-21T16:13Z). Form `κ` first, then divide by `-a`. Stable `a < 0` is required. `-κ / a` is not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`. The p. 16 stationary `T0MEANS` constraint is `-κ / a + −B z / a`. Form the intercept contribution first, then include the TI extra effect, then add. That constrained first-occasion mean is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean. Equation 5 of that constrained mean is `τ + λ(−κ / a + −B z / a)` (Driver et al., 2017, §4.3, pp. 9–10; Eq. 5, p. 5; JSS PDF re-opened 2026-08-21T20:07Z). Form the stationary latent mean first, then `τ + λ` of that mean. `τ + λ μ_0` for free `T0MEANS` is not that composition. `τ + λ(−κ / a)` is not that composition when `B z ≠ 0`. `τ + λ μ_t` is not that composition. `MANIFESTMEANS` is not `E(y_0)`. The constrained latent mean is not `E(y_0)`. The p. 16 constrained first-occasion variance `trait + −q / (2 a) + (B / a)² v` is not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`). The lagged covariance of that constrained process is `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T19:13Z). Trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`. Contemporaneous `T0VAR` is not that lagged map. Decaying the constrained total as if it were all state is not that lagged map. Equation 5 of that lagged covariance is `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ`. `Θ` does not enter. Contemporaneous `Var(y_0)` is not that lagged observed covariance. The lagged latent covariance is not that observed covariance. The later-occasion variance of that constrained process is `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T23:12Z). Trait and `addedTIPREDVAR` do not enter `Q_Δt`. Under stationarity that composition equals contemporaneous `T0VAR`. Evolving the constrained total as if it were all state is not that later map. The lagged covariance omits `Q_Δt` and is not that later map. `Q_Δt` is not that later map. Equation 5 of that later-occasion variance is `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ`. The lagged observed covariance omits `Q_Δt` and `θ`. `MANIFESTVAR` is not `Var(y_t)`. The later-occasion latent variance is not `Var(y_t)`. The later-occasion variance of §4.3 predetermined `T0VAR` is `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T05:12Z). Trait and `addedTIPREDVAR` do not enter `Q_Δt`. Free `T0VAR` `p_0` is not that later map. Setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map. Stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free. Evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map. As `Δt → ∞` with stable `a < 0` the composition approaches contemporaneous stationary `T0VAR`. As `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`. Nonzero diffusion with `a ≥ 0` is a growing process and is kept. Equation 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ`. `MANIFESTVAR` is not `Var(y_t)`. The predetermined later-occasion latent variance is not `Var(y_t)`. Stationary later observed variance is not that observed variance when `p_0` is free. The lagged covariance of §4.3 predetermined `T0VAR` is `trait + e^{a Δt} p_0 + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T09:04Z). Trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`. Free `T0VAR` `p_0` is not that lagged map. Setting `p_0 = −q / (2 a)` recovers the stationary lagged map. Stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free. Evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map. Later-occasion variance includes `Q_Δt` and is not that lagged map. As `Δt → ∞` with stable `a < 0` the state term vanishes. As `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`. Equation 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ`. `MANIFESTVAR` does not enter. The predetermined lagged latent covariance is not that observed covariance. Predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance. Stationary lagged observed covariance is not that observed covariance when `p_0` is free. The predetermined first-occasion variance of §4.3 predetermined `T0VAR` is `trait + p_0 + (B / a)² v`. Free `p_0` is not that map. Stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free. Lagged covariance decays the state and is not that map. Later-occasion variance includes `Q_Δt` and is not that map. Equation 5 of that predetermined first-occasion variance is `λ²(trait + p_0 + (B / a)² v) + θ + ψ`. `MANIFESTVAR` is not that first-occasion observed variance. The predetermined first-occasion latent variance is not that observed variance. Stationary first-occasion observed variance is not that observed variance when `p_0` is free. Predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance. Later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z). First-occasion lagged omits `e^{a s} Q_u`. Later-occasion variance does not lag. Stationary lagged uses `−q / (2 a)`. Decaying the later total is not that map. Equation 5 of that later-start lagged covariance is `λ²` of it plus `ψ`. Independent `ε_t` does not enter. First-occasion lagged observed omits `e^{a s} Q_u`. Predetermined later observed variance includes `Q_u` and `θ` and is not that later-start lagged observed covariance. Later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z). Later-occasion variance at `u` omits `Q_s`. Later-start lagged covariance omits `Q_s`. Stationary later uses `−q / (2 a)`. Evolving the later total as if it were all state is not that map. Ignoring `startoffset` omits `e^{2 a s} Q_u`. Equation 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`. `MANIFESTVAR` is not that observed variance. Page 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; footnote 4; §7.1; JSS PDF re-opened 2026-08-23T11:40Z). Unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`. The §7.1 trait-plus-state autocorrelation `(trait + e^{a Δt} p + added) / (trait + p + added)` uses `TRAITVAR` and is not `discreteDRIFTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:06Z). Unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`. The continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`. `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`. `TRAITVAR` is not the standardisation variance. Page 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z). Unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`. The discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`. `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`. `TRAITVAR` is not the standardisation variance. Page 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z). Unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`. The discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`. `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z). Unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`. The finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`. `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z). Unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`. The asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`. The finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`. `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Page 16 / Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z). The affected variance is free first-occasion `T0VAR`, not `asymDIFFUSION`. Unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`. `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`. `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`. `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. 2017-era `addedT0TIPREDVAR` is `t0_b² v` after a first-occasion time-independent predictor (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z). Form `t0_b` first, then square, then multiply by `v`. A zero coefficient or zero predictor variance is exactly zero. Free `T0TIPREDEFFECT` does not require `a < 0`. `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map. `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance. Free `T0VAR` is not this extra TI variance. `TRAITVAR` is not this extra TI variance. Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z). Form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`. A zero loading or zero extra is exactly zero. `t0_b² v` is the latent extra, not the observed extra. `λ² p_0 + θ` is first-occasion observed variance, not this extra. `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra. `MANIFESTVAR` `θ` is not this extra. Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v` (Driver et al., 2017, Eq. 5, p. 5; Table 2, p. 12; §7.2, pp. 20–21; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:23Z). Form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`. A zero loading or zero extra is exactly zero. Lasting asymptotic extra requires `a < 0`. `(B / a)² v` is the latent extra, not the observed extra. `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra. `λ² p + θ` is stationary observed variance, not this extra. `MANIFESTVAR` `θ` is not this extra. Page 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance. Unstandardised `M` is not `TDPREDEFFECTstd`. `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`. intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`. `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`. Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance. Unstandardised `t0_m` is not `T0TDPREDEFFECTstd`. `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`. `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`. `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`. Free `T0VAR` does not require `a < 0`. Page 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; the default ridge is 0). Unstandardised `T0VAR` is not `T0VARstd`. `T0TDPREDEFFECTstd` is not `T0VARstd`. `addedT0TIPREDVAR` is not `T0VARstd`. Page 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend). Unstandardised `TRAITVAR` is not `TRAITVARstd`. `T0VARstd` is not `TRAITVARstd` even when both equal 1. `addedT0TIPREDVAR` is not `TRAITVARstd`. Page 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0). Unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`. `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1. `MANIFESTVAR` is not `MANIFESTTRAITVARstd`. Page 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug). Unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`. `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1. Equation 5 `Var(y)` is not `MANIFESTVARstd`. Page 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`). Unstandardised `TIPREDVAR` is not `TIPREDVARstd`. `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1. Section 7.2 `addedTIPREDVAR` is not `TIPREDVARstd`. Page 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`). Unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`. `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1. `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`. Page 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`. Unstandardised `discreteCINT` is not `discreteCINTstd`. `κ / √p` is not `discreteCINTstd`. `(-κ / a) / √p` is not `discreteCINTstd`. `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`. Unstandardised `asymCINT` is not `asymCINTstd`. `κ / √p` is not `asymCINTstd`. `discreteCINTstd` is not `asymCINTstd`. `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`. Unstandardised `T0MEANS` is not `T0MEANSstd`. `T0VARstd` is not `T0MEANSstd`. `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`. Page 16 `MANIFESTMEANSstd` is `τ / √θ` after strictly positive `MANIFESTVAR`. Unstandardised `MANIFESTMEANS` is not `MANIFESTMEANSstd`. `MANIFESTVARstd` is not `MANIFESTMEANSstd`. `τ / √(λ² Var(η) + θ)` is not `MANIFESTMEANSstd`. Page 16 `CINTstd` is `κ / √p` after strictly positive `asymDIFFUSION`. Unstandardised `CINT` is not `CINTstd`. `asymCINTstd` is not `CINTstd`. `discreteCINTstd` is not `CINTstd`. `κ / √(trait + p + added)` is not `CINTstd`. Evolving from that stationary start with `CINT` and `TIPREDEFFECT` stays at the stationary mean. Equation 1 is the latent SDE, not the measurement model. Form `(λ p) λ` then add `θ`, then add `ψ`. `MANIFESTVAR` is `Θ`, not `Var(y)`. `MANIFESTTRAITVAR` is `Ψ_τ`, not `Θ`. `TRAITVAR` is latent and scaled by `λ²`. `Var(η)` is not `Var(y)`. +- Do not use the difference quotient as a continuous-time rate. The scalar map is `a = ln(φ) / Δt` on event time. Discrete lags from unequal event intervals are not one coefficient; remap them through that log-rate. Binary64 `exp(a Δt) = 0` is not a discrete lag. A constant predictor's discrete effect is Voelkle et al. (2012, Eq. 12), evaluated as `a_yx (expm1(z) / a_xx)` with `z = a_xx Δt` so a finite result is not lost when `z` overflows to `-∞` or when `a_yx Δt` overflows. When `expm1(z)` overflows at a finite `z`, rewrite in log space; a zero continuous effect is exactly zero; an overflowing `a_yx/a_xx` rewrite term fails closed. The first-order product is the underflow limit of that equation, not the general constant-predictor discrete effect. A time-varying predictor whose sampling interval equals its constancy interval uses Voelkle et al. (2012, Eq. 14): `b* = a_yx Δt`. Unmatched intervals fail closed (Oud & Jansen, 2000, unread). Discrete process noise is Driver et al. (2017, Eq. 3): `Q_Δt = 0.5 q (expm1(z) / a)` with `z = 2 (a Δt)` and `q = G G⊤ ≥ 0`; do not form `2 a` first; `a = 0` and `z → 0` recover `q Δt`; a zero diffusion is exactly zero; an overflowing rewrite scale `0.5 q / a` fails closed; this is not a Kalman filter. `Q_Δt` is `cov(η_t | η_{t-1})`, not `Var(η_t)`. The lagged covariance is `exp(a Δt) p` and the unconditional variance is `exp(2 a Δt) p + Q_Δt` (Driver et al., 2017, Eq. 3–4, pp. 4–5; JSS has no numbered §2.2). A zero diffusion whose `2 (a Δt)` overflows to `+∞` is not a finite `Var(η_t)`. The stationary within-subject variance is the `Δt → ∞` limit of Eq. 4: `-q / (2 a)` for stable `a < 0` (JSS p. 16 `asymDIFFUSION`; §4.3). When `2 a` is finite, form `q / -(2 a)` so `q / a` overflow does not lose a finite result (`q = MAX`, `a = -0.75` → `MAX / 1.5`). When `2 a` overflows, form `(q / a) * -0.5`. Do not form `0.5 q` first (`q = from_bits(1)` underflows). `a ≥ 0` has no finite stationary variance. Finite-interval `Q_Δt` is not that limit. Trait-plus-state variance is `trait + state` and lagged covariance is `trait + exp(a Δt) p` (Driver et al., 2017, §4.3, p. 9). Trait variance is not process noise and not `asymDIFFUSION`. Evolving the summed variance as if it were all state is not that map. This is not RI-CLPM. Observed-indicator variance is `λ² Var(η) + θ` when `MANIFESTTRAITVAR` is zero and `λ² Var(η) + θ + ψ` otherwise (Driver et al., 2017, Eq. 5, p. 5; Table 2, p. 12). Lagged observed covariance is `λ² cov(η_t, η_{t-1}) + ψ`; `MANIFESTVAR` does not enter. Observed-indicator mean is `τ + λ μ` (Driver et al., 2017, Eq. 5; Table 2, p. 12). `MANIFESTMEANS` is `τ`, not `E(y)`. `E(η)` is not `E(y)`. `CINT` is not `MANIFESTMEANS`. `T0MEANS` is not `E(y)`. The discrete latent mean is `μ_t = exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ` (Driver et al., 2017, Eq. 3, p. 4; Table 2, p. 12). `T0MEANS` is not `μ_t`. `CINT` is not that discrete increment. A zero drift is `κ Δt`. Underflow of `exp(a Δt)` to `+0` drops the carried `T0MEANS` and keeps `−κ / a`. The evolved observed mean is `τ + λ μ_t` (Driver et al., 2017, Eq. 5 of that Eq. 3 map). The first-occasion map `τ + λ μ_0` is not `E(y_t)`. `μ_t` is not `E(y_t)`. The contemporaneous time-dependent predictor impulse is `m x` (Driver et al., 2017, Eq. 3 fourth summand; Table 2 `TDPREDEFFECT` is `M`). Form `μ_t` first, then add `m x`. `TDPREDEFFECT` is not `CINT`. `M x` is not `A^{-1}[e^{A Δt} − I] B z` and is not Voelkle et al. (2012, Eq. 14). The §7.2 level-change form is not that impulse. The observed mean of that contemporaneous impulse is `τ + λ(μ_t + m x)` (Driver et al., 2017, Eq. 5 of the Eq. 3 fourth-summand composition). The evolved map `τ + λ μ_t` is not that observed mean. The carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`. The evolved-plus-impulse latent mean is not `E(y_t)`. The time-independent predictor increment is `A^{-1}[e^{A Δt} − I] B z` (Driver et al., 2017, Eq. 3 second summand; Table 2 `TIPREDEFFECT` is `B`). Form `B z` first, then the discrete intercept map. A zero drift is `B z Δt`. `TIPREDEFFECT` is `B`, not that discrete increment. `A^{-1}[e^{A Δt} − I] B z` is not `CINT`, not `M x`, and not Voelkle et al. (2012, Eq. 14). The observed mean of that increment is `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` (Driver et al., 2017, Eq. 5 of the Eq. 3 printed addend after the `T0MEANS` carry and the `CINT` increment). The evolved map `τ + λ μ_t` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`. The evolved-plus-increment latent mean is not `E(y_t)`. The within-interval time-dependent impulse carry is `e^{A(t−u)} M x` for `t0 < u < t` (Driver et al., 2017, Eq. 1–2 Green-function integral of Eq. 2; §7.2 dissipation). Form `m x` first, then `e^{a(t−u)} m x`. A zero drift is `m x` with no dissipation. Underflow of `e^{a(t−u)}` to `+0` is vanishing dissipation and is kept. `e^{A(t−u)} M x` is not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle et al. (2012, Eq. 14). An impulse at `u = t` is the contemporaneous map. An impulse at `u ≤ t0` is already in `η(t0)`. The observed mean of that carry is `τ + λ(μ_t + e^{a(t−u)} m x)` (Driver et al., 2017, Eq. 5 of the Eq. 1–2 carried latent mean). The evolved map `τ + λ μ_t` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean when `u ≠ t`. `MANIFESTMEANS` is not `E(y_t)`. The carried latent mean is not `E(y_t)`. The first-occasion time-independent predictor shift is `t0_b z` (Driver et al., 2017, Table 3 `T0TIPREDEFFECT`; Eq. 3 first summand). Form `t0_b z` first, then `e^{a Δt} t0_b z`. Form `μ_t` first, then add that carry. A zero drift is `t0_b z`. Underflow of `e^{a Δt}` to `+0` is a vanishing carry of the first-occasion shift and is kept. `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`. `e^{A Δt} t0_b z` is not `t0_b z`. `T0TIPREDEFFECT` is the coefficient, not the shift. The observed mean of that first-occasion carry is `τ + λ(μ_t + e^{a Δt} t0_b z)` (Driver et al., 2017, Eq. 5 of the Table 3 / Eq. 3 first-summand composition). The evolved map `τ + λ μ_t` is not that observed mean. The process-increment map `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The impulse-carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t0`. The evolved-plus-carry latent mean is not `E(y_t)`. The first-occasion time-dependent predictor shift is `t0_m x0` (Driver et al., 2017, Table 3 `T0TDPREDEFFECT`; Eq. 3 first summand; JSS PDF re-opened 2026-08-20T19:10Z). Form `t0_m x0` first, then `e^{a Δt} t0_m x0`. Form `μ_t` first, then add that carry. A zero drift is `t0_m x0`. Underflow of `e^{a Δt}` to `+0` is a vanishing carry of the first-occasion shift and is kept. `t0_m x0` is not `M x`, not `e^{A(t−u)} M x` for `t0 < u < t`, not `t0_b z`, not `A^{-1}[e^{A Δt} − I] B z`, and not `CINT`. `e^{A Δt} t0_m x0` is not `t0_m x0`. `T0TDPREDEFFECT` is the coefficient, not the shift. An impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`. The observed mean of that first-occasion TD carry is `τ + λ(μ_t + e^{a Δt} t0_m x0)` (Driver et al., 2017, Eq. 5 of the Table 3 / Eq. 3 first-summand TD composition; JSS PDF re-opened 2026-08-20T19:07Z). The evolved map `τ + λ μ_t` is not that observed mean. The process-increment map `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The impulse-carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t0`. The first-occasion TI map `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean. The evolved-plus-carry latent mean is not `E(y_t)`. The lasting level-change `CINT` is `κ = −a m x` (Driver et al., 2017, §7.2, pp. 20–21; JSS PDF re-opened 2026-08-20T19:45Z). Form `m x` first, then multiply by `−a`. Stable `a < 0` is required so `−κ / a = m x` is an equilibrium offset. `a ≥ 0` cannot hold a new process mean. `−a m x` is not the dissipating Dirac `m x`, not a free `CINT`, and not `A^{-1}[e^{A Δt} − I] B z`. The extra near-zero-drift latent process also named in §7.2 is a different specification and is not this `CINT` setting. Equation 3 maps that intercept as `(1 − e^{a Δt}) m x` (JSS PDF re-opened 2026-08-20T19:50Z). Form the level-change `CINT` first, then the discrete intercept map. Underflow of `e^{a Δt}` to `+0` keeps `m x`. `(1 − e^{a Δt}) m x` is not `m x`, not `κ`, and not `A^{-1}[e^{A Δt} − I] B z`. The printed §7.2 lasting level change is an extra near-zero-drift latent process (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-20T23:10Z). `T0MEANS`, `CINT`, `T0VAR`, `DIFFUSION`, and `TRAITVAR` of that process are fixed to 0; `TDPREDEFFECT` on it is fixed to 1; its `DRIFT` diagonal is very close to 0 (printed example `−0.000001`; precisely 0 causes computational problems); the original process is driven by the `DRIFT` coupling `a_{ηξ}`. After a unit identification impulse the scalar contribution is `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (`ε = a` is `a_{ηξ} x Δt e^{a Δt}`). Form `a_{ηξ} x` first. A zero coupling or zero predictor is exactly zero. `ε ≥ 0` fails closed. That contribution is not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`. The observed mean of that extra-process contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a))` (Driver et al., 2017, Eq. 5 of that §7.2 contribution; JSS PDF re-opened 2026-08-21T06:12Z). The extra process has `LAMBDA` 0 and is not an observed indicator. Original indicators load on the original process after the `DRIFT` coupling. The evolved map `τ + λ μ_t` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The contribution is not `E(y_t)`. The evolved-plus-contribution latent mean is not `E(y_t)`. `T0TDPREDEFFECT` on the extra process begins at `t = 0` and uses `Δt = t − t0` for both the original-process evolution and the extra drive. `TDPREDEFFECT` after `t0` uses `t − u` with `t0 < u < t` for the extra drive while `μ_t` still uses `Δt`. The observed mean of that after-t0 extra-process contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a))` (Driver et al., 2017, Eq. 5 of that §7.2 after-t0 contribution; JSS PDF re-opened 2026-08-21T06:32Z). The first-occasion extra-process observed mean is not that observed mean when `u ≠ t0`. The impulse-carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is a Dirac on the original process and is not that `DRIFT` drive. An impulse at `u = t0` or `u = t` is not interior. The asymptotic time-independent predictor effect is `-B z / a` (Driver et al., 2017, §7.2, pp. 20–21; JSS PDF opened 2026-08-21T13:08Z). Form `B z` first, then divide by `-a`. Stable `a < 0` is required. `a ≥ 0` cannot hold a finite process-mean change. `-B z / a` is not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`. The asymptotic time-independent predictor variance is `(B / a)² v` (Driver et al., 2017, §7.2, pp. 20–21 `addedTIPREDVAR`). Form the unit asymptotic effect first, then square, then multiply by `v`. `(B / a)² v` is not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`. The asymptotic continuous intercept is `-κ / a` (Driver et al., 2017, Table 2, p. 12 `asymCINT`; Eq. 3 as `Δt → ∞`; JSS PDF opened 2026-08-21T16:13Z). Form `κ` first, then divide by `-a`. Stable `a < 0` is required. `-κ / a` is not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`. The p. 16 stationary `T0MEANS` constraint is `-κ / a + −B z / a`. Form the intercept contribution first, then include the TI extra effect, then add. That constrained first-occasion mean is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean. Equation 5 of that constrained mean is `τ + λ(−κ / a + −B z / a)` (Driver et al., 2017, §4.3, pp. 9–10; Eq. 5, p. 5; JSS PDF re-opened 2026-08-21T20:07Z). Form the stationary latent mean first, then `τ + λ` of that mean. `τ + λ μ_0` for free `T0MEANS` is not that composition. `τ + λ(−κ / a)` is not that composition when `B z ≠ 0`. `τ + λ μ_t` is not that composition. `MANIFESTMEANS` is not `E(y_0)`. The constrained latent mean is not `E(y_0)`. The p. 16 constrained first-occasion variance `trait + −q / (2 a) + (B / a)² v` is not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`). The lagged covariance of that constrained process is `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T19:13Z). Trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`. Contemporaneous `T0VAR` is not that lagged map. Decaying the constrained total as if it were all state is not that lagged map. Equation 5 of that lagged covariance is `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ`. `Θ` does not enter. Contemporaneous `Var(y_0)` is not that lagged observed covariance. The lagged latent covariance is not that observed covariance. The later-occasion variance of that constrained process is `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T23:12Z). Trait and `addedTIPREDVAR` do not enter `Q_Δt`. Under stationarity that composition equals contemporaneous `T0VAR`. Evolving the constrained total as if it were all state is not that later map. The lagged covariance omits `Q_Δt` and is not that later map. `Q_Δt` is not that later map. Equation 5 of that later-occasion variance is `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ`. The lagged observed covariance omits `Q_Δt` and `θ`. `MANIFESTVAR` is not `Var(y_t)`. The later-occasion latent variance is not `Var(y_t)`. The later-occasion variance of §4.3 predetermined `T0VAR` is `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T05:12Z). Trait and `addedTIPREDVAR` do not enter `Q_Δt`. Free `T0VAR` `p_0` is not that later map. Setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map. Stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free. Evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map. As `Δt → ∞` with stable `a < 0` the composition approaches contemporaneous stationary `T0VAR`. As `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`. Nonzero diffusion with `a ≥ 0` is a growing process and is kept. Equation 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ`. `MANIFESTVAR` is not `Var(y_t)`. The predetermined later-occasion latent variance is not `Var(y_t)`. Stationary later observed variance is not that observed variance when `p_0` is free. The lagged covariance of §4.3 predetermined `T0VAR` is `trait + e^{a Δt} p_0 + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T09:04Z). Trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`. Free `T0VAR` `p_0` is not that lagged map. Setting `p_0 = −q / (2 a)` recovers the stationary lagged map. Stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free. Evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map. Later-occasion variance includes `Q_Δt` and is not that lagged map. As `Δt → ∞` with stable `a < 0` the state term vanishes. As `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`. Equation 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ`. `MANIFESTVAR` does not enter. The predetermined lagged latent covariance is not that observed covariance. Predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance. Stationary lagged observed covariance is not that observed covariance when `p_0` is free. The predetermined first-occasion variance of §4.3 predetermined `T0VAR` is `trait + p_0 + (B / a)² v`. Free `p_0` is not that map. Stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free. Lagged covariance decays the state and is not that map. Later-occasion variance includes `Q_Δt` and is not that map. Equation 5 of that predetermined first-occasion variance is `λ²(trait + p_0 + (B / a)² v) + θ + ψ`. `MANIFESTVAR` is not that first-occasion observed variance. The predetermined first-occasion latent variance is not that observed variance. Stationary first-occasion observed variance is not that observed variance when `p_0` is free. Predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance. Later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z). First-occasion lagged omits `e^{a s} Q_u`. Later-occasion variance does not lag. Stationary lagged uses `−q / (2 a)`. Decaying the later total is not that map. Equation 5 of that later-start lagged covariance is `λ²` of it plus `ψ`. Independent `ε_t` does not enter. First-occasion lagged observed omits `e^{a s} Q_u`. Predetermined later observed variance includes `Q_u` and `θ` and is not that later-start lagged observed covariance. Later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z). Later-occasion variance at `u` omits `Q_s`. Later-start lagged covariance omits `Q_s`. Stationary later uses `−q / (2 a)`. Evolving the later total as if it were all state is not that map. Ignoring `startoffset` omits `e^{2 a s} Q_u`. Equation 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`. `MANIFESTVAR` is not that observed variance. Page 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; footnote 4; §7.1; JSS PDF re-opened 2026-08-23T11:40Z). Unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`. The §7.1 trait-plus-state autocorrelation `(trait + e^{a Δt} p + added) / (trait + p + added)` uses `TRAITVAR` and is not `discreteDRIFTstd`. The named scalar map is `recover_trait_plus_state_expected_autocorrelation`; it is not unstandardised `discreteDRIFT` `e^{a Δt}` even when trait and added vanish and the numbers coincide. `TRAITVAR` is not the standardisation variance. Page 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:06Z). Unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`. The continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`. `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`. `TRAITVAR` is not the standardisation variance. Page 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z). Unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`. The discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`. `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`. `TRAITVAR` is not the standardisation variance. Page 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z). Unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`. The discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`. `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z). Unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`. The finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`. `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z). Unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`. The asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`. The finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`. `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Page 16 / Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z). The affected variance is free first-occasion `T0VAR`, not `asymDIFFUSION`. Unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`. `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`. `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`. `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. 2017-era `addedT0TIPREDVAR` is `t0_b² v` after a first-occasion time-independent predictor (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z). Form `t0_b` first, then square, then multiply by `v`. A zero coefficient or zero predictor variance is exactly zero. Free `T0TIPREDEFFECT` does not require `a < 0`. `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map. `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance. Free `T0VAR` is not this extra TI variance. `TRAITVAR` is not this extra TI variance. Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z). Form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`. A zero loading or zero extra is exactly zero. `t0_b² v` is the latent extra, not the observed extra. `λ² p_0 + θ` is first-occasion observed variance, not this extra. `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra. `MANIFESTVAR` `θ` is not this extra. Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v` (Driver et al., 2017, Eq. 5, p. 5; Table 2, p. 12; §7.2, pp. 20–21; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:23Z). Form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`. A zero loading or zero extra is exactly zero. Lasting asymptotic extra requires `a < 0`. `(B / a)² v` is the latent extra, not the observed extra. `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra. `λ² p + θ` is stationary observed variance, not this extra. `MANIFESTVAR` `θ` is not this extra. Page 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance. Unstandardised `M` is not `TDPREDEFFECTstd`. `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`. intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`. `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`. Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance. Unstandardised `t0_m` is not `T0TDPREDEFFECTstd`. `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`. `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`. `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`. Free `T0VAR` does not require `a < 0`. Page 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; the default ridge is 0). Unstandardised `T0VAR` is not `T0VARstd`. `T0TDPREDEFFECTstd` is not `T0VARstd`. `addedT0TIPREDVAR` is not `T0VARstd`. Page 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend). Unstandardised `TRAITVAR` is not `TRAITVARstd`. `T0VARstd` is not `TRAITVARstd` even when both equal 1. `addedT0TIPREDVAR` is not `TRAITVARstd`. Page 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0). Unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`. `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1. `MANIFESTVAR` is not `MANIFESTTRAITVARstd`. Page 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug). Unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`. `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1. Equation 5 `Var(y)` is not `MANIFESTVARstd`. Page 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`). Unstandardised `TIPREDVAR` is not `TIPREDVARstd`. `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1. Section 7.2 `addedTIPREDVAR` is not `TIPREDVARstd`. Page 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`). Unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`. `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1. `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`. Page 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`. Unstandardised `discreteCINT` is not `discreteCINTstd`. `κ / √p` is not `discreteCINTstd`. `(-κ / a) / √p` is not `discreteCINTstd`. `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`. Unstandardised `asymCINT` is not `asymCINTstd`. `κ / √p` is not `asymCINTstd`. `discreteCINTstd` is not `asymCINTstd`. `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`. Unstandardised `T0MEANS` is not `T0MEANSstd`. `T0VARstd` is not `T0MEANSstd`. `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`. Page 16 `MANIFESTMEANSstd` is `τ / √θ` after strictly positive `MANIFESTVAR`. Unstandardised `MANIFESTMEANS` is not `MANIFESTMEANSstd`. `MANIFESTVARstd` is not `MANIFESTMEANSstd`. `τ / √(λ² Var(η) + θ)` is not `MANIFESTMEANSstd`. Page 16 `CINTstd` is `κ / √p` after strictly positive `asymDIFFUSION`. Unstandardised `CINT` is not `CINTstd`. `asymCINTstd` is not `CINTstd`. `discreteCINTstd` is not `CINTstd`. `κ / √(trait + p + added)` is not `CINTstd`. Evolving from that stationary start with `CINT` and `TIPREDEFFECT` stays at the stationary mean. Equation 1 is the latent SDE, not the measurement model. Form `(λ p) λ` then add `θ`, then add `ψ`. `MANIFESTVAR` is `Θ`, not `Var(y)`. `MANIFESTTRAITVAR` is `Ψ_τ`, not `Θ`. `TRAITVAR` is latent and scaled by `λ²`. `Var(η)` is not `Var(y)`. - Separate cluster means before within-unit lag. CWC plus an event-time lag is not DSEM. Subtracting the person-specific mean from a raw autoregressive series does not isolate the lagged within-person effect (Curran & Bauer, 2011, pp. 607–608); already-centered residuals with irregular event intervals use the exact scalar map. - Do not treat the CWC cluster-mean coefficient as the between-cluster effect. It is the contextual effect `between − within` (Enders & Tofighi, 2007, Table 2, pp. 124–127). - Never use future-available evidence in historical model fits. diff --git a/crates/psychometric_core/src/error.rs b/crates/psychometric_core/src/error.rs index 4ab2695e0..ada153a86 100644 --- a/crates/psychometric_core/src/error.rs +++ b/crates/psychometric_core/src/error.rs @@ -710,6 +710,23 @@ pub enum PsychometricError { /// `MANIFESTVARstd`. `λ² Var(η) + θ` is `Var(y)`, not the /// correlation form of `Θ`. ObservedVarianceIsNotStandardisedManifestVariance, + /// Driver §7.1 trait-plus-state expected autocorrelation was + /// requested with a zero total variance. The covariance ratio + /// `(trait + e^{a Δt} p + added) / (trait + p + added)` is + /// undefined at a zero denominator. + TraitPlusStateExpectedAutocorrelationRequiresPositiveTotalVariance, + /// Driver §7.1 trait-plus-state expected autocorrelation was + /// treated as unstandardised `discreteDRIFT`. The total-variance + /// ratio is not `e^{a Δt}` when `TRAITVAR` or `addedTIPREDVAR` + /// is nonzero. Equal numbers when both vanish remain distinct + /// named quantities. + TraitPlusStateExpectedAutocorrelationIsNotDiscreteDrift, + /// Driver §7.1 trait-plus-state expected autocorrelation was + /// treated as p. 16 `discreteDRIFTstd`. Footnote 4 standardises + /// `DRIFT` using only within-subject `asymDIFFUSION`, not the + /// total. The autocorrelation uses `TRAITVAR` and is not that + /// map. + TraitPlusStateExpectedAutocorrelationIsNotStandardisedDiscreteDrift, } impl fmt::Display for PsychometricError { @@ -1235,6 +1252,15 @@ impl fmt::Display for PsychometricError { Self::ObservedVarianceIsNotStandardisedManifestVariance => { "observed-indicator variance is not standardised measurement-error variance" } + Self::TraitPlusStateExpectedAutocorrelationRequiresPositiveTotalVariance => { + "trait-plus-state expected autocorrelation requires a strictly positive total variance" + } + Self::TraitPlusStateExpectedAutocorrelationIsNotDiscreteDrift => { + "trait-plus-state expected autocorrelation is not discrete drift" + } + Self::TraitPlusStateExpectedAutocorrelationIsNotStandardisedDiscreteDrift => { + "trait-plus-state expected autocorrelation is not standardised discrete drift" + } }; formatter.write_str(message) } @@ -2073,4 +2099,22 @@ mod tests { "measurement error is not standardised manifest-trait variance" ); } + + #[test] + fn trait_plus_state_expected_autocorrelation_boundary_messages_are_stable() { + assert_eq!( + PsychometricError::TraitPlusStateExpectedAutocorrelationRequiresPositiveTotalVariance + .to_string(), + "trait-plus-state expected autocorrelation requires a strictly positive total variance" + ); + assert_eq!( + PsychometricError::TraitPlusStateExpectedAutocorrelationIsNotDiscreteDrift.to_string(), + "trait-plus-state expected autocorrelation is not discrete drift" + ); + assert_eq!( + PsychometricError::TraitPlusStateExpectedAutocorrelationIsNotStandardisedDiscreteDrift + .to_string(), + "trait-plus-state expected autocorrelation is not standardised discrete drift" + ); + } } diff --git a/crates/psychometric_core/src/event_time.rs b/crates/psychometric_core/src/event_time.rs index a29bc5c18..ca2ca18f4 100644 --- a/crates/psychometric_core/src/event_time.rs +++ b/crates/psychometric_core/src/event_time.rs @@ -917,6 +917,122 @@ pub fn recover_trait_plus_state_lagged_covariance( require_finite(trait_variance + state_lagged) } +/// Exact scalar §7.1 trait-plus-state expected autocorrelation. +/// +/// Driver, Oud, and Voelkle (2017, §7.1, pp. 18–19; Eq. 3–4, pp. 4–5; +/// §4.3, p. 9; §7.2 `addedTIPREDVAR`; JSS PDF re-opened +/// 2026-09-01T06:30Z from +/// ) +/// name traits the stable between-subject differences (unit-level +/// unobserved heterogeneity). Omitting `TRAITVAR` mixes between- and +/// within-person information (Balestra & Nerlove, 1966; Oud & Jansen, +/// 2000, unread; Halaby, 2004). Figure 4 shows auto-effects +/// (persistence) reduced after traits are modelled. The paper does +/// not print this ratio; it is the covariance ratio of the already +/// recovered §4.3 maps. Form the lagged covariance +/// `trait + e^{a Δt} p` first, then the contemporaneous total +/// `trait + p`, then include `addedTIPREDVAR`, then the ratio +/// `(trait + e^{a Δt} p + added) / (trait + p + added)`. A zero +/// trait and a zero TI extra recovers `e^{a Δt}` numerically and +/// remains a distinct named quantity. A zero state with a positive +/// trait or extra is exactly 1. A zero total variance has no +/// autocorrelation and fails closed. Growing `a > 0` is a covariance +/// ratio of those named maps and is kept; it is not a stationary +/// correlation and is not `discreteDRIFTstd` (which requires stable +/// `a < 0` and strictly positive `asymDIFFUSION`). Unstandardised +/// `discreteDRIFT` `e^{a Δt}` is the state auto-effect, not this +/// total-variance ratio. Page 16 `discreteDRIFTstd` standardises +/// `DRIFT` using only within-subject `asymDIFFUSION` (footnote 4), +/// not the total. `TRAITVAR` is not the standardisation variance. +/// The interval must be event time and strictly positive. This is +/// not a Kalman filter, not a matrix `expm`, not RI-CLPM, not DSEM, +/// and not ctsem estimation. +/// +/// # Errors +/// +/// Propagates [`recover_trait_plus_state_lagged_covariance`] and +/// [`recover_trait_plus_state_latent_variance`]. Returns +/// [`PsychometricError::InvalidNumericInput`] when the TI extra +/// variance is negative or non-finite or a sum or ratio overflows, +/// and +/// [`PsychometricError::TraitPlusStateExpectedAutocorrelationRequiresPositiveTotalVariance`] +/// when `trait + p + added` is zero. +pub fn recover_trait_plus_state_expected_autocorrelation( + trait_variance: f64, + state_variance: f64, + added_time_independent_variance: f64, + log_rate: f64, + event_delta: f64, + clock: LagClock, +) -> Result { + if !added_time_independent_variance.is_finite() || added_time_independent_variance < 0.0 { + return Err(PsychometricError::InvalidNumericInput); + } + let lagged = recover_trait_plus_state_lagged_covariance( + trait_variance, + state_variance, + log_rate, + event_delta, + clock, + )?; + let contemporaneous = recover_trait_plus_state_latent_variance(trait_variance, state_variance)?; + let numerator = if added_time_independent_variance == 0.0 { + lagged + } else { + require_finite(lagged + added_time_independent_variance)? + }; + let denominator = if added_time_independent_variance == 0.0 { + contemporaneous + } else { + require_finite(contemporaneous + added_time_independent_variance)? + }; + if denominator == 0.0 { + return Err( + PsychometricError::TraitPlusStateExpectedAutocorrelationRequiresPositiveTotalVariance, + ); + } + require_finite(numerator / denominator) +} + +/// Refuse treating §7.1 trait-plus-state expected autocorrelation as +/// unstandardised `discreteDRIFT`. +/// +/// `(trait + e^{a Δt} p + added) / (trait + p + added)` is the +/// total-variance ratio. `e^{a Δt}` is the state auto-effect. +/// Equal numbers when trait and added vanish remain distinct named +/// quantities. +/// +/// # Errors +/// +/// Always returns +/// [`PsychometricError::TraitPlusStateExpectedAutocorrelationIsNotDiscreteDrift`]. +pub fn refuse_trait_plus_state_expected_autocorrelation_as_discrete_drift( + expected_autocorrelation: f64, + discrete_drift: f64, +) -> Result { + let _ = (expected_autocorrelation, discrete_drift); + Err(PsychometricError::TraitPlusStateExpectedAutocorrelationIsNotDiscreteDrift) +} + +/// Refuse treating §7.1 trait-plus-state expected autocorrelation as +/// p. 16 `discreteDRIFTstd`. +/// +/// Footnote 4 standardises `DRIFT` using only within-subject +/// `asymDIFFUSION`, not the total. The autocorrelation uses +/// `TRAITVAR` and is not that map. +/// +/// # Errors +/// +/// Always returns +/// [`PsychometricError::TraitPlusStateExpectedAutocorrelationIsNotStandardisedDiscreteDrift`]. +pub fn refuse_trait_plus_state_expected_autocorrelation_as_standardised_discrete_drift( + expected_autocorrelation: f64, + standardised_discrete_drift: f64, +) -> Result { + let _ = (expected_autocorrelation, standardised_discrete_drift); + Err(PsychometricError::TraitPlusStateExpectedAutocorrelationIsNotStandardisedDiscreteDrift) +} + /// Refuse treating Driver §4.3 trait variance as process noise. /// /// A stable trait has `DIFFUSION` fixed to zero. The ctsem @@ -6853,8 +6969,8 @@ pub(crate) fn fit_scalar_log_rate(pairs: &[(f64, f64, f64)]) -> Result 0.28); + assert_eq!( + recover_trait_plus_state_expected_autocorrelation( + 0.0, + state_variance, + 0.0, + drift, + delta, + LagClock::EventTime, + ), + Ok(discrete_drift) + ); + assert_eq!( + recover_trait_plus_state_expected_autocorrelation( + trait_variance, + 0.0, + added, + drift, + delta, + LagClock::EventTime, + ), + Ok(1.0) + ); + let far = recover_trait_plus_state_expected_autocorrelation( + trait_variance, + state_variance, + added, + drift, + 700.0, + LagClock::EventTime, + ) + .expect("Δt→∞"); + let asymptotic = (trait_variance + added) / (trait_variance + state_variance + added); + assert!((far - asymptotic).abs() < 1e-15); + let growing = recover_trait_plus_state_expected_autocorrelation( + trait_variance, + state_variance, + added, + 0.5, + delta, + LagClock::EventTime, + ) + .expect("a>0"); + assert!(growing > 1.0); + assert_eq!( + refuse_trait_plus_state_expected_autocorrelation_as_discrete_drift( + recovered, + discrete_drift + ), + Err(PsychometricError::TraitPlusStateExpectedAutocorrelationIsNotDiscreteDrift) + ); + assert_eq!( + refuse_trait_plus_state_expected_autocorrelation_as_standardised_discrete_drift( + recovered, + discrete_drift + ), + Err( + PsychometricError::TraitPlusStateExpectedAutocorrelationIsNotStandardisedDiscreteDrift + ) + ); + } + + #[test] + fn trait_plus_state_expected_autocorrelation_invalid_inputs_fail_closed() { + assert_eq!( + recover_trait_plus_state_expected_autocorrelation( + 0.0, + 0.0, + 0.0, + -0.5, + 1.0, + LagClock::EventTime, + ), + Err( + PsychometricError::TraitPlusStateExpectedAutocorrelationRequiresPositiveTotalVariance + ) + ); + assert_eq!( + recover_trait_plus_state_expected_autocorrelation( + 0.8, + 0.4, + -0.1, + -0.5, + 1.0, + LagClock::EventTime, + ), + Err(PsychometricError::InvalidNumericInput) + ); + assert_eq!( + recover_trait_plus_state_expected_autocorrelation( + 0.8, + 0.4, + f64::NAN, + -0.5, + 1.0, + LagClock::EventTime, + ), + Err(PsychometricError::InvalidNumericInput) + ); + assert_eq!( + recover_trait_plus_state_expected_autocorrelation( + 0.8, + 0.4, + f64::INFINITY, + -0.5, + 1.0, + LagClock::EventTime, + ), + Err(PsychometricError::InvalidNumericInput) + ); + assert_eq!( + recover_trait_plus_state_expected_autocorrelation( + 0.8, + 0.4, + 0.2, + -0.5, + 1.0, + LagClock::SystemTime, + ), + Err(PsychometricError::EventTimeRequired) + ); + assert_eq!( + recover_trait_plus_state_expected_autocorrelation( + 0.8, + 0.4, + 0.2, + -0.5, + 0.0, + LagClock::EventTime, + ), + Err(PsychometricError::NonPositiveInterval) + ); + assert_eq!( + recover_trait_plus_state_expected_autocorrelation( + -0.1, + 0.4, + 0.2, + -0.5, + 1.0, + LagClock::EventTime, + ), + Err(PsychometricError::InvalidNumericInput) + ); + assert_eq!( + recover_trait_plus_state_expected_autocorrelation( + 1e308, + 0.0, + 1e308, + 0.0, + 1.0, + LagClock::EventTime, + ), + Err(PsychometricError::InvalidNumericInput) + ); + assert_eq!( + recover_trait_plus_state_expected_autocorrelation( + 0.0, + 1e308, + 1e308, + -800.0, + 1.0, + LagClock::EventTime, + ), + Err(PsychometricError::InvalidNumericInput) + ); + } + #[test] fn non_event_clocks_and_difference_quotient_fail_closed() { for clock in [ @@ -11147,8 +11466,8 @@ mod tests { } #[test] - fn stationary_initial_observed_variance_recovers_driver_equation_five_of_section_four_point_three() - { + fn stationary_initial_observed_variance_recovers_driver_equation_five_of_section_four_point_three( + ) { // Driver et al. (2017, §4.3, pp. 9–10; Eq. 5, p. 5) // constrain first-occasion variances to the model-predicted // variance. Equation 5 maps Var(y_0) = λ² of that variance @@ -11723,8 +12042,8 @@ mod tests { } #[test] - fn stationary_lagged_observed_covariance_recovers_driver_equation_five_of_section_four_point_three() - { + fn stationary_lagged_observed_covariance_recovers_driver_equation_five_of_section_four_point_three( + ) { // Driver et al. (2017, §4.3, pp. 9–10; Eq. 5, p. 5) // lagged observed covariance of stationary T0VAR is // λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ. @@ -12300,8 +12619,8 @@ mod tests { #[test] #[allow(clippy::too_many_lines)] - fn stationary_later_observed_variance_recovers_driver_equation_five_of_section_four_point_three() - { + fn stationary_later_observed_variance_recovers_driver_equation_five_of_section_four_point_three( + ) { // Driver et al. (2017, §4.3, pp. 9–10; Eq. 5, p. 5) // later-occasion observed variance of stationary T0VAR is // λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ. @@ -14455,8 +14774,8 @@ mod tests { } #[test] - fn discrete_observed_mean_with_initial_time_independent_predictor_recovers_driver_equation_five() - { + fn discrete_observed_mean_with_initial_time_independent_predictor_recovers_driver_equation_five( + ) { let loading = 2.0_f64; let drift = -0.5_f64; let delta = 2.0_f64; @@ -14606,8 +14925,8 @@ mod tests { } #[test] - fn discrete_observed_mean_with_initial_time_independent_predictor_refuses_evolved_mean_and_overflow() - { + fn discrete_observed_mean_with_initial_time_independent_predictor_refuses_evolved_mean_and_overflow( + ) { let loading = 2.0_f64; let recovered = recover_discrete_observed_mean_with_initial_time_independent_predictor( loading, @@ -15225,8 +15544,8 @@ mod tests { } #[test] - fn discrete_observed_mean_with_initial_time_dependent_predictor_refuses_evolved_mean_and_overflow() - { + fn discrete_observed_mean_with_initial_time_dependent_predictor_refuses_evolved_mean_and_overflow( + ) { let recovered = recover_discrete_observed_mean_with_initial_time_dependent_predictor( 2.0, 1.0, diff --git a/crates/psychometric_core/src/lib.rs b/crates/psychometric_core/src/lib.rs index c081a63f6..d41b37093 100644 --- a/crates/psychometric_core/src/lib.rs +++ b/crates/psychometric_core/src/lib.rs @@ -261,6 +261,16 @@ //! correlation; zero `MANIFESTTRAITVAR` fails closed; a non-event //! clock fails closed; `MANIFESTTRAITVAR` does not require `a < 0`; //! JSS PDF re-opened 2026-08-27T14:20Z), +//! recovers the Driver §7.1 trait-plus-state expected autocorrelation +//! `(trait + e^{a Δt} p + added) / (trait + p + added)` (form the +//! lagged covariance first, then the contemporaneous total, then +//! add `addedTIPREDVAR`, then the ratio; zero trait and zero added +//! recovers `e^{a Δt}` numerically and remains a distinct named +//! quantity; zero state with positive trait or extra is exactly 1; +//! zero total variance fails closed; `a > 0` is a growing covariance +//! ratio and is kept; unstandardised `e^{a Δt}` is not this map; +//! `discreteDRIFTstd` is not this map; JSS PDF re-opened +//! 2026-09-01T06:30Z), //! and refuses //! latent-mean comparison below strong invariance. @@ -442,6 +452,8 @@ pub use event_time::recover_stationary_later_observed_variance; pub use event_time::recover_time_dependent_predictor_impulse; /// Exact scalar within-interval `TDPREDEFFECT` carry `e^{A(t−u)} M x`. pub use event_time::recover_time_dependent_predictor_impulse_carry; +/// Exact scalar §7.1 trait-plus-state expected autocorrelation `(trait + e^{a Δt} p + added) / (trait + p + added)`. +pub use event_time::recover_trait_plus_state_expected_autocorrelation; /// Exact scalar trait-plus-state lagged covariance. pub use event_time::recover_trait_plus_state_lagged_covariance; /// Exact scalar trait-plus-state latent variance. @@ -715,6 +727,10 @@ pub use event_time::refuse_time_independent_effect_as_time_varying_discrete_effe pub use event_time::refuse_time_independent_observed_mean_as_initial_time_dependent_observed_mean; /// Refuse treating process-increment `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` as the first-occasion TI-predictor observed mean. pub use event_time::refuse_time_independent_observed_mean_as_initial_time_independent_observed_mean; +/// Refuse treating §7.1 trait-plus-state expected autocorrelation as unstandardised `discreteDRIFT`. +pub use event_time::refuse_trait_plus_state_expected_autocorrelation_as_discrete_drift; +/// Refuse treating §7.1 trait-plus-state expected autocorrelation as p. 16 `discreteDRIFTstd`. +pub use event_time::refuse_trait_plus_state_expected_autocorrelation_as_standardised_discrete_drift; /// Refuse treating §4.3 trait-plus-state lagged covariance as lagged stationary `T0VAR`. pub use event_time::refuse_trait_plus_state_lagged_covariance_as_stationary_lagged_latent_covariance; /// Refuse treating `κ / √(trait + p + added)` as `CINTstd`. diff --git a/crates/psychometric_core/tests/multilevel_event_time_recovery_contract.rs b/crates/psychometric_core/tests/multilevel_event_time_recovery_contract.rs index 1c0027f44..6bcf43faf 100644 --- a/crates/psychometric_core/tests/multilevel_event_time_recovery_contract.rs +++ b/crates/psychometric_core/tests/multilevel_event_time_recovery_contract.rs @@ -2,10 +2,8 @@ #![allow(clippy::cast_precision_loss)] use psychometric_core::{ - ClusteredEventScore, ClusteredScore, EventOccasion, IndicatorKind, LagClock, - LaggedWithinResidual, PsychometricError, map_discrete_lag_across_event_intervals, - ordinary_least_squares_slope, recover_asymptotic_continuous_intercept, - recover_asymptotic_time_independent_predictor_effect, + map_discrete_lag_across_event_intervals, ordinary_least_squares_slope, + recover_asymptotic_continuous_intercept, recover_asymptotic_time_independent_predictor_effect, recover_asymptotic_time_independent_predictor_variance, recover_cluster_mean_within_between_slopes, recover_discrete_constant_predictor_effect, recover_discrete_continuous_intercept_effect, recover_discrete_lag_from_log_rate, @@ -45,8 +43,8 @@ use psychometric_core::{ recover_stationary_lagged_observed_covariance, recover_stationary_latent_variance, recover_stationary_later_latent_variance, recover_stationary_later_observed_variance, recover_time_dependent_predictor_impulse, recover_time_dependent_predictor_impulse_carry, - recover_trait_plus_state_lagged_covariance, recover_trait_plus_state_latent_variance, - recover_within_residual_event_time_log_rate, + recover_trait_plus_state_expected_autocorrelation, recover_trait_plus_state_lagged_covariance, + recover_trait_plus_state_latent_variance, recover_within_residual_event_time_log_rate, refuse_after_extra_process_contribution_as_observed_mean, refuse_after_extra_process_latent_mean_as_observed_mean, refuse_asymptotic_continuous_intercept_as_asymptotic_time_independent_effect, @@ -159,12 +157,16 @@ use psychometric_core::{ refuse_time_independent_effect_as_time_varying_discrete_effect, refuse_time_independent_observed_mean_as_initial_time_dependent_observed_mean, refuse_time_independent_observed_mean_as_initial_time_independent_observed_mean, + refuse_trait_plus_state_expected_autocorrelation_as_discrete_drift, + refuse_trait_plus_state_expected_autocorrelation_as_standardised_discrete_drift, 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_manifest_trait_variance_as_standardised_manifest_trait_variance, refuse_unstandardised_manifest_variance_as_standardised_manifest_variance, - refuse_unstandardised_trait_variance_as_standardised_trait_variance, + refuse_unstandardised_trait_variance_as_standardised_trait_variance, ClusteredEventScore, + ClusteredScore, EventOccasion, IndicatorKind, LagClock, LaggedWithinResidual, + PsychometricError, }; fn rmse(truth: &[f64], recovered: &[f64]) -> f64 { @@ -932,6 +934,121 @@ fn trait_plus_state_recovers_driver_section_four_point_three() { ); } +#[test] +fn trait_plus_state_expected_autocorrelation_recovers_driver_section_seven_point_one() { + let trait_variance = 0.8_f64; + let state_variance = 0.4_f64; + let added = 0.2_f64; + let drift = -0.5_f64; + let delta = 1.0_f64; + let recovered = recover_trait_plus_state_expected_autocorrelation( + trait_variance, + state_variance, + added, + drift, + delta, + LagClock::EventTime, + ) + .expect("ρ"); + let lagged = recover_trait_plus_state_lagged_covariance( + trait_variance, + state_variance, + drift, + delta, + LagClock::EventTime, + ) + .expect("lagged"); + let contemporaneous = + recover_trait_plus_state_latent_variance(trait_variance, state_variance).expect("total"); + let expected = (lagged + added) / (contemporaneous + added); + let error = rmse(&[expected], &[recovered]); + assert!( + error < 1e-15, + "Driver §7.1 trait-plus-state expected autocorrelation RMSE {error}" + ); + let discrete_drift = + recover_discrete_lag_from_log_rate(drift, delta, LagClock::EventTime).expect("φ"); + let collapsed = rmse(&[expected], &[discrete_drift]); + assert!( + collapsed > error, + "treating e^{{a Δt}} as the §7.1 autocorrelation is not that map: collapsed RMSE {collapsed} must exceed {error}" + ); + assert!((discrete_drift - recovered).abs() > 0.28); + assert_eq!( + recover_trait_plus_state_expected_autocorrelation( + 0.0, + state_variance, + 0.0, + drift, + delta, + LagClock::EventTime, + ), + Ok(discrete_drift) + ); + assert_eq!( + recover_trait_plus_state_expected_autocorrelation( + trait_variance, + 0.0, + added, + drift, + delta, + LagClock::EventTime, + ), + Ok(1.0) + ); + let far = recover_trait_plus_state_expected_autocorrelation( + trait_variance, + state_variance, + added, + drift, + 700.0, + LagClock::EventTime, + ) + .expect("Δt→∞"); + let asymptotic = (trait_variance + added) / (trait_variance + state_variance + added); + let far_error = rmse(&[asymptotic], &[far]); + assert!( + far_error < 1e-15, + "as Δt → ∞ the state term vanishes: RMSE {far_error}" + ); + assert_eq!( + refuse_trait_plus_state_expected_autocorrelation_as_discrete_drift( + recovered, + discrete_drift + ), + Err(PsychometricError::TraitPlusStateExpectedAutocorrelationIsNotDiscreteDrift) + ); + assert_eq!( + refuse_trait_plus_state_expected_autocorrelation_as_standardised_discrete_drift( + recovered, + discrete_drift + ), + Err(PsychometricError::TraitPlusStateExpectedAutocorrelationIsNotStandardisedDiscreteDrift) + ); + assert_eq!( + recover_trait_plus_state_expected_autocorrelation( + 0.0, + 0.0, + 0.0, + drift, + delta, + LagClock::EventTime, + ), + Err(PsychometricError::TraitPlusStateExpectedAutocorrelationRequiresPositiveTotalVariance) + ); + assert_eq!( + recover_trait_plus_state_expected_autocorrelation( + trait_variance, + state_variance, + added, + drift, + delta, + LagClock::SystemTime, + ), + Err(PsychometricError::EventTimeRequired) + ); +} + #[test] fn manifest_observed_variance_recovers_driver_equation_five() { let loading = 2.0_f64; @@ -2239,8 +2356,8 @@ fn discrete_observed_mean_with_initial_time_independent_predictor_is_not_impulse } #[test] -fn discrete_observed_mean_with_initial_time_independent_predictor_refuses_evolved_process_impulse_and_carry() - { +fn discrete_observed_mean_with_initial_time_independent_predictor_refuses_evolved_process_impulse_and_carry( +) { let loading = 2.0_f64; let drift = -0.5_f64; let delta = 2.0_f64; @@ -2394,8 +2511,8 @@ fn discrete_observed_mean_with_initial_time_independent_predictor_zero_loading_i } #[test] -fn discrete_observed_mean_with_initial_time_independent_predictor_refuses_overflow_and_non_event_clocks() - { +fn discrete_observed_mean_with_initial_time_independent_predictor_refuses_overflow_and_non_event_clocks( +) { assert_eq!( recover_discrete_observed_mean_with_initial_time_independent_predictor( 1e308, @@ -3326,8 +3443,8 @@ fn discrete_observed_mean_with_initial_time_dependent_predictor_is_not_impulse_o #[test] #[allow(clippy::too_many_lines)] -fn discrete_observed_mean_with_initial_time_dependent_predictor_refuses_evolved_process_impulse_and_carry() - { +fn discrete_observed_mean_with_initial_time_dependent_predictor_refuses_evolved_process_impulse_and_carry( +) { let loading = 2.0_f64; let drift = -0.5_f64; let delta = 2.0_f64; @@ -3500,8 +3617,8 @@ fn discrete_observed_mean_with_initial_time_dependent_predictor_zero_loading_is_ } #[test] -fn discrete_observed_mean_with_initial_time_dependent_predictor_refuses_overflow_and_non_event_clocks() - { +fn discrete_observed_mean_with_initial_time_dependent_predictor_refuses_overflow_and_non_event_clocks( +) { assert_eq!( recover_discrete_observed_mean_with_initial_time_dependent_predictor( 1e308, diff --git a/crates/psychometric_core/tests/scientific_claim_boundary_contract.rs b/crates/psychometric_core/tests/scientific_claim_boundary_contract.rs index 6ccf7f38b..34df26bc4 100644 --- a/crates/psychometric_core/tests/scientific_claim_boundary_contract.rs +++ b/crates/psychometric_core/tests/scientific_claim_boundary_contract.rs @@ -1,7 +1,6 @@ //! Scientific claim boundaries for compositional coordinates and posterior draws. use psychometric_core::{ - ClusteredEventScore, ClusteredScore, IndicatorKind, LagClock, LaggedWithinResidual, ordinary_least_squares_slope, posterior_draw_point_estimate_mean, recover_asymptotic_continuous_intercept, recover_asymptotic_time_independent_predictor_effect, recover_asymptotic_time_independent_predictor_variance, @@ -40,8 +39,8 @@ use psychometric_core::{ recover_stationary_lagged_observed_covariance, recover_stationary_latent_variance, recover_stationary_later_latent_variance, recover_stationary_later_observed_variance, recover_time_dependent_predictor_impulse, recover_time_dependent_predictor_impulse_carry, - recover_trait_plus_state_lagged_covariance, recover_trait_plus_state_latent_variance, - recover_within_residual_event_time_log_rate, + recover_trait_plus_state_expected_autocorrelation, recover_trait_plus_state_lagged_covariance, + recover_trait_plus_state_latent_variance, recover_within_residual_event_time_log_rate, refuse_after_extra_process_contribution_as_observed_mean, refuse_after_extra_process_latent_mean_as_observed_mean, refuse_asymptotic_continuous_intercept_as_asymptotic_time_independent_effect, @@ -167,6 +166,8 @@ use psychometric_core::{ refuse_time_independent_effect_as_time_varying_discrete_effect, refuse_time_independent_observed_mean_as_initial_time_dependent_observed_mean, refuse_time_independent_observed_mean_as_initial_time_independent_observed_mean, + refuse_trait_plus_state_expected_autocorrelation_as_discrete_drift, + refuse_trait_plus_state_expected_autocorrelation_as_standardised_discrete_drift, refuse_trait_plus_state_lagged_covariance_as_stationary_lagged_latent_covariance, refuse_trait_scaled_continuous_intercept_as_standardised_continuous_intercept, refuse_trait_variance_as_process_noise, refuse_trait_variance_as_stationary_within_subject, @@ -181,6 +182,7 @@ use psychometric_core::{ refuse_unstandardised_manifest_variance_as_standardised_manifest_variance, refuse_unstandardised_trait_variance_as_standardised_trait_variance, refuse_within_subject_scaled_initial_latent_mean_as_standardised_initial_latent_mean, + ClusteredEventScore, ClusteredScore, IndicatorKind, LagClock, LaggedWithinResidual, }; #[test] @@ -3782,3 +3784,95 @@ fn standardised_manifest_variance_is_not_unstandardised_traitstd_or_observed_var ) ); } + +#[test] +fn trait_plus_state_expected_autocorrelation_is_not_discrete_drift_or_discrete_drift_std() { + let trait_variance = 0.8_f64; + let state_variance = 0.4_f64; + let added = 0.2_f64; + let drift = -0.5_f64; + let delta = 1.0_f64; + let recovered = recover_trait_plus_state_expected_autocorrelation( + trait_variance, + state_variance, + added, + drift, + delta, + LagClock::EventTime, + ) + .expect("ρ"); + assert!( + (recovered - 0.887_580_188_5).abs() < 1e-9, + "Driver et al. (2017, §7.1): (trait + e^{{a Δt}} p + added) / (trait + p + added)" + ); + let discrete_drift = drift.exp(); + assert!( + (discrete_drift - recovered).abs() > 0.28, + "Driver et al. (2017, Eq. 3 / §7.1): e^{{a Δt}} is not the trait-plus-state autocorrelation" + ); + assert_eq!( + recover_trait_plus_state_expected_autocorrelation( + 0.0, + state_variance, + 0.0, + drift, + delta, + LagClock::EventTime, + ) + .expect("zero trait+added"), + discrete_drift + ); + assert_eq!( + refuse_trait_plus_state_expected_autocorrelation_as_discrete_drift( + recovered, + discrete_drift + ), + Err( + psychometric_core::PsychometricError::TraitPlusStateExpectedAutocorrelationIsNotDiscreteDrift + ) + ); + assert_eq!( + refuse_trait_plus_state_expected_autocorrelation_as_standardised_discrete_drift( + recovered, + discrete_drift + ), + Err( + psychometric_core::PsychometricError::TraitPlusStateExpectedAutocorrelationIsNotStandardisedDiscreteDrift + ) + ); + assert_eq!( + recover_trait_plus_state_expected_autocorrelation( + 0.0, + 0.0, + 0.0, + drift, + delta, + LagClock::EventTime, + ), + Err( + psychometric_core::PsychometricError::TraitPlusStateExpectedAutocorrelationRequiresPositiveTotalVariance + ) + ); + assert_eq!( + recover_trait_plus_state_expected_autocorrelation( + trait_variance, + 0.0, + added, + drift, + delta, + LagClock::EventTime, + ), + Ok(1.0) + ); + assert_eq!( + recover_trait_plus_state_expected_autocorrelation( + trait_variance, + state_variance, + added, + drift, + delta, + LagClock::DocumentTime, + ), + Err(psychometric_core::PsychometricError::EventTimeRequired) + ); +} diff --git a/docs/TRACEABILITY.md b/docs/TRACEABILITY.md index 2b783c2ab..b159c431f 100644 --- a/docs/TRACEABILITY.md +++ b/docs/TRACEABILITY.md @@ -76,7 +76,7 @@ The full APA 7th standards/literature register remains `docs/research/standards- | report template/section/copied/style/modality method effects | ADR 0004/0012; PRD/TRD | simulation truth factors implemented; `prompt_source` prompt-versus-unique-content identity on the active PR; estimator-side method model remains future | partial | | candidate K statistical/Pareto gates | ADR 0012; research | `model_selection` fits each candidate `K` with the CPU `f64` reference and scores the actual mixture likelihood plus Schwarz's (1978) `ℓ − (p ln N)/2` penalty before the Pareto gate; candidate blinding, blinded LLM review, GPU, and backend comparison remain accepted-target | active-PR | | compositional topic correlation / stable clustering | ADR 0005/0012; research | future `network_analysis` | accepted-target | -| posterior ESEM / longitudinal invariance / DSEM | ADR 0005 | `psychometric_core` construct/input gates, true-loading OLS recovery, posterior-draw point-estimate averaging, Rubin `T` on draw-level OLS loadings, CWC within/between OLS plus the contextual effect, event-time log-rate, constant- and time-varying-predictor discrete effects (Voelkle Eqs. 12 and 14), exact scalar discrete process noise (Driver et al., 2017, Eq. 3), lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; `asymDIFFUSION`), trait-plus-state variance (Driver et al., 2017, §4.3 `TRAITVAR`; not process noise), observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5; Table 2 `MANIFESTVAR` is `Θ`, not `Var(y)`; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; `Θ` does not enter lagged observed covariance; observed-indicator mean is `τ + λ μ`; `MANIFESTMEANS` is not `E(y)`; `CINT` is not `MANIFESTMEANS`; discrete latent mean is `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ`; `T0MEANS` is not `μ_t`; evolved observed mean is `τ + λ μ_t`; `τ + λ μ_0` is not `E(y_t)`; contemporaneous `TDPREDEFFECT` impulse is `m x`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that contemporaneous impulse is `τ + λ(μ_t + m x)`, and `τ + λ μ_t` is not that observed mean; time-independent `TIPREDEFFECT` increment is `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, not `M x`, not Voelkle Eq. 14, and not the coefficient `B`; Eq. 5 of that increment is `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`; within-interval `TDPREDEFFECT` carry is `e^{A(t−u)} M x` for `t0 < u < t`, not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that carry is `τ + λ(μ_t + e^{a(t−u)} m x)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that carried observed mean when `u ≠ t`; §7.2 level-change `CINT` is `κ = −a m x` (`a < 0`; not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`; Eq. 3 of that setting is `(1 − e^{a Δt}) m x`); §7.2 extra-process contribution is `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, not the dissipating Dirac; `ε ≥ 0` fails closed; Eq. 5 of that contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)`; extra `LAMBDA` is 0; `τ + λ μ_t` is not that observed mean; after-t0 extra-process `TDPREDEFFECT` uses `t − u` with `t0 < u < t` while `μ_t` uses `Δt`; that after-t0 observed mean is not the first-occasion extra-process observed mean; §7.2 `asymTIPREDEFFECT` is `-B z / a` for `a < 0` and is not `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; §7.2 `addedTIPREDVAR` is `(B / a)² v` and is not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`; Table 2 `asymCINT` is `-κ / a` for `a < 0` and is not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; p. 16 stationary `T0MEANS` is `-κ / a + −B z / a` and is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean; Eq. 5 of that constrained mean is `τ + λ(−κ / a + −B z / a)`; `τ + λ μ_0` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`; stationary `T0VAR` is `trait + −q / (2 a) + (B / a)² v` (not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`)); lagged stationary `T0VAR` is `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (trait and `addedTIPREDVAR` do not decay; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map; Eq. 5 of that lagged covariance is `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ`; `Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance); later-occasion stationary `T0VAR` is `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt`; `Q_Δt` is not that later map; Eq. 5 of that later-occasion variance is `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ`; lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not `Var(y_t)`; the later-occasion latent variance is not `Var(y_t)`); predetermined later-occasion `T0VAR` is `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (free `T0VAR` `p_0` is not that later map; setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map; stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map; Eq. 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ`; `MANIFESTVAR` is not `Var(y_t)`; the predetermined later-occasion latent variance is not `Var(y_t)`; stationary later observed variance is not that observed variance when `p_0` is free); predetermined lagged `T0VAR` is `trait + e^{a Δt} p_0 + (B / a)² v` (free `T0VAR` `p_0` is not that lagged map; setting `p_0 = −q / (2 a)` recovers the stationary lagged map; stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map; later-occasion variance includes `Q_Δt` and is not that lagged map; Eq. 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ`; `MANIFESTVAR` does not enter; the predetermined lagged latent covariance is not that observed covariance; predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance; stationary lagged observed covariance is not that observed covariance when `p_0` is free; the predetermined first-occasion variance of §4.3 predetermined `T0VAR` is `trait + p_0 + (B / a)² v`; free `p_0` is not that map; stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free; lagged covariance decays the state and is not that map; later-occasion variance includes `Q_Δt` and is not that map; Eq. 5 of that predetermined first-occasion variance is `λ²(trait + p_0 + (B / a)² v) + θ + ψ`; `MANIFESTVAR` is not that first-occasion observed variance; the predetermined first-occasion latent variance is not that observed variance; stationary first-occasion observed variance is not that observed variance when `p_0` is free; predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance; later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z; first-occasion lagged omits `e^{a s} Q_u`; later-occasion variance does not lag; stationary lagged uses `−q / (2 a)`; decaying the later total is not that map; Eq. 5 of that later-start lagged covariance is `λ²` of it plus `ψ`; `Θ` does not enter; first-occasion lagged observed omits `e^{a s} Q_u`; later observed variance includes `Q_u` and `θ`; later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z; later-occasion variance at `u` omits `Q_s`; later-start lagged covariance omits `Q_s`; stationary later uses `−q / (2 a)`; evolving the later total as if it were all state is not that map; ignoring `startoffset` omits `e^{2 a s} Q_u`; Eq. 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`; `MANIFESTVAR` is not that observed variance; p. 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4; unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`; the §7.1 trait-plus-state autocorrelation uses `TRAITVAR` and is not `discreteDRIFTstd`; p. 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4; unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`; the continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`; `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z; unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`; the discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`; `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z); unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`; the discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`; `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z; unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`; `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z; unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`; the asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`; `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z; the affected variance is free `T0VAR`, not `asymDIFFUSION`; unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`; `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`; `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`; `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); 2017-era `addedT0TIPREDVAR` is `t0_b² v` (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z; `T0TIPREDEFFECT %*% TIPREDVAR %*% t(T0TIPREDEFFECT)` immediately after `T0TIPREDEFFECTstd`; form `t0_b` first, then square, then multiply by `v`; a zero coefficient or zero predictor variance is exactly zero; free `T0TIPREDEFFECT` does not require `a < 0`; `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map; `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance; free `T0VAR` is not this extra TI variance; `TRAITVAR` is not this extra TI variance; Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z; form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; `t0_b² v` is the latent extra, not the observed extra; `λ² p_0 + θ` is first-occasion observed variance, not this extra; `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra; `MANIFESTVAR` `θ` is not this extra; Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v`; form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; lasting asymptotic extra requires `a < 0`; `(B / a)² v` is the latent extra, not the observed extra; `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra; `λ² p + θ` is stationary observed variance, not this extra; `MANIFESTVAR` `θ` is not this extra; p. 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance; unstandardised `M` is not `TDPREDEFFECTstd`; `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`; intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`; `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`; Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance; unstandardised `t0_m` is not `T0TDPREDEFFECTstd`; `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`; `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`; `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`; free `T0VAR` does not require `a < 0`; p. 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; default ridge is 0); unstandardised `T0VAR` is not `T0VARstd`; `T0TDPREDEFFECTstd` is not `T0VARstd`; `addedT0TIPREDVAR` is not `T0VARstd`; p. 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend); unstandardised `TRAITVAR` is not `TRAITVARstd`; `T0VARstd` is not `TRAITVARstd` even when both equal 1; `addedT0TIPREDVAR` is not `TRAITVARstd`; p. 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0); unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`; `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1; `MANIFESTVAR` is not `MANIFESTTRAITVARstd`; p. 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug); unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`; `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1; Equation 5 `Var(y)` is not `MANIFESTVARstd`; p. 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`); unstandardised `TIPREDVAR` is not `TIPREDVARstd`; `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1; §7.2 `addedTIPREDVAR` is not `TIPREDVARstd`; p. 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`); unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`; `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1; `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`; p. 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `discreteCINT` is not `discreteCINTstd`; `κ / √p` is not `discreteCINTstd`; `(-κ / a) / √p` is not `discreteCINTstd`; `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`; unstandardised `asymCINT` is not `asymCINTstd`; `κ / √p` is not `asymCINTstd`; `discreteCINTstd` is not `asymCINTstd`; `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`; unstandardised `T0MEANS` is not `T0MEANSstd`; `T0VARstd` is not `T0MEANSstd`; `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`; `MANIFESTMEANSstd` is `τ / √θ` after strictly positive `MANIFESTVAR`; unstandardised `MANIFESTMEANS` is not `MANIFESTMEANSstd`; `MANIFESTVARstd` is not `MANIFESTMEANSstd`; `τ / √(λ² Var(η) + θ)` is not `MANIFESTMEANSstd`; p. 16 `CINTstd` is `κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `CINT` is not `CINTstd`; `asymCINTstd` is not `CINTstd`; `discreteCINTstd` is not `CINTstd`; `κ / √(trait + p + added)` is not `CINTstd`;))))), irregular already-centered residual lag, and strong/strict-gated latent means on the stacked psychometric PR (two-observation residual variance is identically `0` and caps at strong/scalar; Putnick & Bornstein, 2016, PMC5145197 opened 2026-08-19T22:15Z); full ESEM/DSEM remaining | partial | +| posterior ESEM / longitudinal invariance / DSEM | ADR 0005 | `psychometric_core` construct/input gates, true-loading OLS recovery, posterior-draw point-estimate averaging, Rubin `T` on draw-level OLS loadings, CWC within/between OLS plus the contextual effect, event-time log-rate, constant- and time-varying-predictor discrete effects (Voelkle Eqs. 12 and 14), exact scalar discrete process noise (Driver et al., 2017, Eq. 3), lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; `asymDIFFUSION`), trait-plus-state variance and expected autocorrelation (Driver et al., 2017, §4.3 `TRAITVAR`; §7.1; not process noise, not discreteDRIFT, not discreteDRIFTstd), observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5; Table 2 `MANIFESTVAR` is `Θ`, not `Var(y)`; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; `Θ` does not enter lagged observed covariance; observed-indicator mean is `τ + λ μ`; `MANIFESTMEANS` is not `E(y)`; `CINT` is not `MANIFESTMEANS`; discrete latent mean is `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ`; `T0MEANS` is not `μ_t`; evolved observed mean is `τ + λ μ_t`; `τ + λ μ_0` is not `E(y_t)`; contemporaneous `TDPREDEFFECT` impulse is `m x`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that contemporaneous impulse is `τ + λ(μ_t + m x)`, and `τ + λ μ_t` is not that observed mean; time-independent `TIPREDEFFECT` increment is `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, not `M x`, not Voelkle Eq. 14, and not the coefficient `B`; Eq. 5 of that increment is `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`; within-interval `TDPREDEFFECT` carry is `e^{A(t−u)} M x` for `t0 < u < t`, not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that carry is `τ + λ(μ_t + e^{a(t−u)} m x)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that carried observed mean when `u ≠ t`; §7.2 level-change `CINT` is `κ = −a m x` (`a < 0`; not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`; Eq. 3 of that setting is `(1 − e^{a Δt}) m x`); §7.2 extra-process contribution is `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, not the dissipating Dirac; `ε ≥ 0` fails closed; Eq. 5 of that contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)`; extra `LAMBDA` is 0; `τ + λ μ_t` is not that observed mean; after-t0 extra-process `TDPREDEFFECT` uses `t − u` with `t0 < u < t` while `μ_t` uses `Δt`; that after-t0 observed mean is not the first-occasion extra-process observed mean; §7.2 `asymTIPREDEFFECT` is `-B z / a` for `a < 0` and is not `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; §7.2 `addedTIPREDVAR` is `(B / a)² v` and is not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`; Table 2 `asymCINT` is `-κ / a` for `a < 0` and is not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; p. 16 stationary `T0MEANS` is `-κ / a + −B z / a` and is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean; Eq. 5 of that constrained mean is `τ + λ(−κ / a + −B z / a)`; `τ + λ μ_0` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`; stationary `T0VAR` is `trait + −q / (2 a) + (B / a)² v` (not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`)); lagged stationary `T0VAR` is `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (trait and `addedTIPREDVAR` do not decay; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map; Eq. 5 of that lagged covariance is `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ`; `Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance); later-occasion stationary `T0VAR` is `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt`; `Q_Δt` is not that later map; Eq. 5 of that later-occasion variance is `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ`; lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not `Var(y_t)`; the later-occasion latent variance is not `Var(y_t)`); predetermined later-occasion `T0VAR` is `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (free `T0VAR` `p_0` is not that later map; setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map; stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map; Eq. 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ`; `MANIFESTVAR` is not `Var(y_t)`; the predetermined later-occasion latent variance is not `Var(y_t)`; stationary later observed variance is not that observed variance when `p_0` is free); predetermined lagged `T0VAR` is `trait + e^{a Δt} p_0 + (B / a)² v` (free `T0VAR` `p_0` is not that lagged map; setting `p_0 = −q / (2 a)` recovers the stationary lagged map; stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map; later-occasion variance includes `Q_Δt` and is not that lagged map; Eq. 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ`; `MANIFESTVAR` does not enter; the predetermined lagged latent covariance is not that observed covariance; predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance; stationary lagged observed covariance is not that observed covariance when `p_0` is free; the predetermined first-occasion variance of §4.3 predetermined `T0VAR` is `trait + p_0 + (B / a)² v`; free `p_0` is not that map; stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free; lagged covariance decays the state and is not that map; later-occasion variance includes `Q_Δt` and is not that map; Eq. 5 of that predetermined first-occasion variance is `λ²(trait + p_0 + (B / a)² v) + θ + ψ`; `MANIFESTVAR` is not that first-occasion observed variance; the predetermined first-occasion latent variance is not that observed variance; stationary first-occasion observed variance is not that observed variance when `p_0` is free; predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance; later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z; first-occasion lagged omits `e^{a s} Q_u`; later-occasion variance does not lag; stationary lagged uses `−q / (2 a)`; decaying the later total is not that map; Eq. 5 of that later-start lagged covariance is `λ²` of it plus `ψ`; `Θ` does not enter; first-occasion lagged observed omits `e^{a s} Q_u`; later observed variance includes `Q_u` and `θ`; later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z; later-occasion variance at `u` omits `Q_s`; later-start lagged covariance omits `Q_s`; stationary later uses `−q / (2 a)`; evolving the later total as if it were all state is not that map; ignoring `startoffset` omits `e^{2 a s} Q_u`; Eq. 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`; `MANIFESTVAR` is not that observed variance; p. 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4; unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`; the §7.1 trait-plus-state autocorrelation uses `TRAITVAR` and is not `discreteDRIFTstd`; p. 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4; unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`; the continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`; `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z; unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`; the discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`; `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z); unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`; the discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`; `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z; unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`; `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z; unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`; the asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`; `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z; the affected variance is free `T0VAR`, not `asymDIFFUSION`; unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`; `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`; `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`; `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); 2017-era `addedT0TIPREDVAR` is `t0_b² v` (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z; `T0TIPREDEFFECT %*% TIPREDVAR %*% t(T0TIPREDEFFECT)` immediately after `T0TIPREDEFFECTstd`; form `t0_b` first, then square, then multiply by `v`; a zero coefficient or zero predictor variance is exactly zero; free `T0TIPREDEFFECT` does not require `a < 0`; `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map; `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance; free `T0VAR` is not this extra TI variance; `TRAITVAR` is not this extra TI variance; Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z; form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; `t0_b² v` is the latent extra, not the observed extra; `λ² p_0 + θ` is first-occasion observed variance, not this extra; `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra; `MANIFESTVAR` `θ` is not this extra; Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v`; form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; lasting asymptotic extra requires `a < 0`; `(B / a)² v` is the latent extra, not the observed extra; `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra; `λ² p + θ` is stationary observed variance, not this extra; `MANIFESTVAR` `θ` is not this extra; p. 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance; unstandardised `M` is not `TDPREDEFFECTstd`; `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`; intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`; `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`; Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance; unstandardised `t0_m` is not `T0TDPREDEFFECTstd`; `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`; `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`; `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`; free `T0VAR` does not require `a < 0`; p. 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; default ridge is 0); unstandardised `T0VAR` is not `T0VARstd`; `T0TDPREDEFFECTstd` is not `T0VARstd`; `addedT0TIPREDVAR` is not `T0VARstd`; p. 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend); unstandardised `TRAITVAR` is not `TRAITVARstd`; `T0VARstd` is not `TRAITVARstd` even when both equal 1; `addedT0TIPREDVAR` is not `TRAITVARstd`; p. 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0); unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`; `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1; `MANIFESTVAR` is not `MANIFESTTRAITVARstd`; p. 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug); unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`; `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1; Equation 5 `Var(y)` is not `MANIFESTVARstd`; p. 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`); unstandardised `TIPREDVAR` is not `TIPREDVARstd`; `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1; §7.2 `addedTIPREDVAR` is not `TIPREDVARstd`; p. 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`); unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`; `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1; `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`; p. 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `discreteCINT` is not `discreteCINTstd`; `κ / √p` is not `discreteCINTstd`; `(-κ / a) / √p` is not `discreteCINTstd`; `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`; unstandardised `asymCINT` is not `asymCINTstd`; `κ / √p` is not `asymCINTstd`; `discreteCINTstd` is not `asymCINTstd`; `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`; unstandardised `T0MEANS` is not `T0MEANSstd`; `T0VARstd` is not `T0MEANSstd`; `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`; `MANIFESTMEANSstd` is `τ / √θ` after strictly positive `MANIFESTVAR`; unstandardised `MANIFESTMEANS` is not `MANIFESTMEANSstd`; `MANIFESTVARstd` is not `MANIFESTMEANSstd`; `τ / √(λ² Var(η) + θ)` is not `MANIFESTMEANSstd`; p. 16 `CINTstd` is `κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `CINT` is not `CINTstd`; `asymCINTstd` is not `CINTstd`; `discreteCINTstd` is not `CINTstd`; `κ / √(trait + p + added)` is not `CINTstd`;))))), irregular already-centered residual lag, and strong/strict-gated latent means on the stacked psychometric PR (two-observation residual variance is identically `0` and caps at strong/scalar; Putnick & Bornstein, 2016, PMC5145197 opened 2026-08-19T22:15Z); full ESEM/DSEM remaining | partial | | CPU bounded multithreading + GPU/VRAM streaming/parity | ADR 0001/0006 | future `compute_backend` | accepted-target | | CPU bounded multithreading + GPU/VRAM streaming/parity | ADR 0001/0006 | future `compute_backend` | accepted-target | | TDT detection/tracking vs CHRONOS schema/prediction/temporal consistency | ADR 0016; PRD/research | `event_core` bounded reasoner plus canonical digest-bound JSON and provenance-bearing GraphML successor artifact; ADR 0013 `model_artifact` persists the immutable object reference/type/digest; JSON-LD remains accepted-target | active-PR | diff --git a/docs/adr/0005-posterior-esem-dsem.md b/docs/adr/0005-posterior-esem-dsem.md index ee1e6cf0d..166aaaca6 100644 --- a/docs/adr/0005-posterior-esem-dsem.md +++ b/docs/adr/0005-posterior-esem-dsem.md @@ -1,8 +1,8 @@ # ADR 0005 — Posterior-aware ESEM/DSEM and structural interpretation **Decision status:** Accepted -**Implementation maturity:** partial — construct classification, valid log-ratio/logistic-normal indicator gates, CPU `f64` OLS and posterior-draw loading point-estimate averaging, Rubin `T_m = Ū_m + (1+1/m) B_m` on draw-level OLS loadings, cluster-mean within/between OLS with the CWC contextual effect and Kish ESS WLS, event-time discrete lag-1 and exact scalar local log-rate, exact scalar forward map and unequal-interval remapping, exact scalar discrete effect of a constant predictor, first-order discrete effect of a time-varying predictor with matched sampling and constancy intervals (Voelkle et al., 2012, Eq. 14), exact scalar discrete process noise (Driver, Oud, & Voelkle, 2017, Eq. 3), exact scalar lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), exact scalar stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; §4.3; p. 16 `asymDIFFUSION`), exact scalar trait-plus-state variance and lagged covariance (Driver et al., 2017, §4.3 `TRAITVAR`; not process noise and not `asymDIFFUSION`), exact scalar observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5, p. 5; Table 2, p. 12; `λ² Var(η) + θ` when `MANIFESTTRAITVAR` is zero, else `λ² Var(η) + θ + ψ`; lagged `λ² cov(η_t, η_{t-1}) + ψ`; `MANIFESTVAR` is `Θ`, not `Var(y)`; `Θ` does not enter lagged observed covariance; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; observed-indicator mean is `τ + λ μ` (`MANIFESTMEANS` is `τ`, not `E(y)`; `CINT` is not `MANIFESTMEANS`; `T0MEANS` is not `E(y)`; Equation 1 is the SDE; not a Kalman filter), exact scalar discrete latent mean `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ` (Driver et al., 2017, Eq. 3; `T0MEANS` is not `μ_t`; `CINT` is not the discrete increment), exact scalar evolved observed-indicator mean `τ + λ μ_t` (Driver et al., 2017, Eq. 5 of that Eq. 3 map; the first-occasion map `τ + λ μ_0` is not `E(y_t)`), exact scalar contemporaneous `TDPREDEFFECT` impulse `m x` (Driver et al., 2017, Eq. 3 fourth summand; Table 2 `TDPREDEFFECT` is `M`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; the §7.2 level-change form is not that impulse), exact scalar observed mean of that contemporaneous impulse `τ + λ(μ_t + m x)` (Driver et al., 2017, Eq. 5 of that Eq. 3 composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`), exact scalar time-independent `TIPREDEFFECT` increment `A^{-1}[e^{A Δt} − I] B z` (Driver et al., 2017, Eq. 3 second summand; Table 2 `TIPREDEFFECT` is `B`, not `κ`, not `M`, and not Voelkle Eq. 14; `B` is not that discrete increment), exact scalar observed mean of that increment `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` (Driver et al., 2017, Eq. 5 of that Eq. 3 printed addend after the `T0MEANS` carry and the `CINT` increment; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`), exact scalar within-interval `TDPREDEFFECT` carry `e^{A(t−u)} M x` for `t0 < u < t` (Driver et al., 2017, Eq. 1–2 Green-function integral of Eq. 2; §7.2 dissipation; not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14), exact scalar observed mean of that carry `τ + λ(μ_t + e^{a(t−u)} m x)` (Driver et al., 2017, Eq. 5 of that carried latent mean; `τ + λ μ_t` is not that observed mean), exact scalar first-occasion `T0TIPREDEFFECT` shift `t0_b z` and Eq. 3 first-summand carry `e^{A Δt} t0_b z` (Driver et al., 2017, Table 3, p. 13; Eq. 3, p. 5; `T0TIPREDEFFECT` is not `TIPREDEFFECT` `B`; `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`; `e^{A Δt} t0_b z` is not `t0_b z`), exact scalar observed mean of that first-occasion carry `τ + λ(μ_t + e^{a Δt} t0_b z)` (Driver et al., 2017, Eq. 5 of that Table 3 / Eq. 3 first-summand composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean), exact scalar first-occasion `T0TDPREDEFFECT` shift `t0_m x0` and Eq. 3 first-summand carry `e^{A Δt} t0_m x0` (Driver et al., 2017, Table 3, p. 13; Eq. 3, p. 5; JSS PDF re-opened 2026-08-20T19:10Z; `T0TDPREDEFFECT` is not `TDPREDEFFECT` `M`; `t0_m x0` is not `M x`; `e^{A Δt} t0_m x0` is not `t0_m x0`; `e^{A Δt} t0_m x0` is not `e^{A(t−u)} M x` for `t0 < u < t`; `t0_m x0` is not `t0_b z`; an impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`), exact scalar observed mean of that first-occasion TD carry `τ + λ(μ_t + e^{a Δt} t0_m x0)` (Driver et al., 2017, Eq. 5 of that Table 3 / Eq. 3 first-summand TD composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean; `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean), exact scalar §7.2 level-change `CINT` `κ = −a m x` (Driver et al., 2017, §7.2, pp. 20–21; `a < 0` so `−κ / a = m x`; not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`, and not the extra near-zero-drift latent process also named in §7.2), exact scalar Eq. 3 increment of that setting `(1 − e^{a Δt}) m x` (not `m x`, not `κ`, and not `TIPREDEFFECT`), exact scalar §7.2 extra near-zero-drift latent process contribution `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-20T23:10Z; identification `TDPREDEFFECT` on the extra process is 1; extra `DRIFT` printed as `−0.000001`; precisely 0 causes computational problems; `ε = a` is `a_{ηξ} x Δt e^{a Δt}`; not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`; `ε ≥ 0` fails closed), exact scalar observed mean of that extra-process contribution `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a))` (Driver et al., 2017, Eq. 5, p. 5; §7.2, pp. 22–23; JSS PDF re-opened 2026-08-21T06:12Z; the extra process has `LAMBDA` 0 and is not an observed indicator; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; the contribution is not `E(y_t)`; the evolved-plus-contribution latent mean is not `E(y_t)`), exact scalar after-t0 extra-process contribution `a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)` for `t0 < u < t` and Eq. 5 observed mean `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a))` (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-21T06:32Z; `T0TDPREDEFFECT` uses `Δt` for both the evolution and the extra drive; `TDPREDEFFECT` after `t0` uses `t − u`; `e^{a(t−u)} m x` is a Dirac on the original process, not this `DRIFT` drive), exact scalar §7.2 `asymTIPREDEFFECT` `-B z / a` (Driver et al., 2017, §7.2, pp. 20–21; JSS PDF opened 2026-08-21T13:08Z; expected total change in process means given a time-independent predictor; `a < 0`; not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; `a ≥ 0` fails closed), exact scalar §7.2 `addedTIPREDVAR` `(B / a)² v` (Driver et al., 2017, §7.2, pp. 20–21; stable between-subject variance accounted for by a time-independent predictor; not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`), exact scalar Table 2 `asymCINT` `-κ / a` (Driver et al., 2017, Table 2, p. 12; Eq. 3 as `Δt → ∞`; `a < 0`; not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; `a ≥ 0` fails closed), exact scalar p. 16 stationary `T0MEANS` `-κ / a + −B z / a` (Driver et al., 2017, p. 16; constrained first-occasion mean using `T0MEANSbase` / `T0MEANSfree`; not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean), exact scalar Eq. 5 of that constrained mean `τ + λ(−κ / a + −B z / a)` (Driver et al., 2017, §4.3, pp. 9–10; Eq. 5, p. 5; JSS PDF re-opened 2026-08-21T20:07Z; form the stationary latent mean first, then `τ + λ` of that mean; `τ + λ μ_0` is not that observed mean; `τ + λ(−κ / a)` is not that observed mean when `B z ≠ 0`; `τ + λ μ_t` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`), exact scalar §4.3 / p. 16 stationary `T0VAR` `trait + −q / (2 a) + (B / a)² v` (Driver et al., 2017, §4.3, pp. 9–10; p. 16; JSS PDF re-opened 2026-08-22T03:07Z; not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`), exact scalar lagged covariance of that constrained process `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T19:13Z; trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map), exact scalar Eq. 5 of that lagged covariance `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ` (`Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance), exact scalar later-occasion variance of that constrained process `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T23:12Z; trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt` and is not that later map; `Q_Δt` is not that later map), exact scalar Eq. 5 of that later-occasion variance `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ` (the lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not that later observed variance; the later-occasion latent variance is not that observed variance)), exact scalar later-occasion variance of §4.3 predetermined `T0VAR` `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T05:12Z; trait and `addedTIPREDVAR` do not enter `Q_Δt`; free `T0VAR` `p_0` is not that later map; setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map; stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map; as `Δt → ∞` with stable `a < 0` the composition approaches contemporaneous stationary `T0VAR`; as `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`; nonzero diffusion with `a ≥ 0` is a growing process and is kept. Eq. 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ` (`MANIFESTVAR` is not that later observed variance; the predetermined later-occasion latent variance is not that observed variance; stationary later observed variance is not that observed variance when `p_0` is free)), exact scalar lagged covariance of §4.3 predetermined `T0VAR` `trait + e^{a Δt} p_0 + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T09:04Z; trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`; free `T0VAR` `p_0` is not that lagged map; setting `p_0 = −q / (2 a)` recovers the stationary lagged map; stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map; later-occasion variance includes `Q_Δt` and is not that lagged map. Eq. 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ` (`MANIFESTVAR` does not enter; the predetermined lagged latent covariance is not that observed covariance; predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance; stationary lagged observed covariance is not that observed covariance when `p_0` is free)), exact scalar first-occasion variance of §4.3 predetermined `T0VAR` `trait + p_0 + (B / a)² v` (free `p_0` is not that map; stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free; lagged covariance decays the state and is not that map; later-occasion variance includes `Q_Δt` and is not that map), exact scalar Eq. 5 of that predetermined first-occasion variance `λ²(trait + p_0 + (B / a)² v) + θ + ψ` (`MANIFESTVAR` is not that first-occasion observed variance; the predetermined first-occasion latent variance is not that observed variance; stationary first-occasion observed variance is not that observed variance when `p_0` is free; predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance; later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z; first-occasion lagged omits `e^{a s} Q_u`; later-occasion variance does not lag; stationary lagged uses `−q / (2 a)`; decaying the later total is not that map; Eq. 5 of that later-start lagged covariance is `λ²` of it plus `ψ`; `Θ` does not enter; first-occasion lagged observed omits `e^{a s} Q_u`; later observed variance includes `Q_u` and `θ`; later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z; later-occasion variance at `u` omits `Q_s`; later-start lagged covariance omits `Q_s`; stationary later uses `−q / (2 a)`; evolving the later total as if it were all state is not that map; ignoring `startoffset` omits `e^{2 a s} Q_u`; Eq. 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`; `MANIFESTVAR` is not that observed variance; p. 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; footnote 4; §7.1; JSS PDF re-opened 2026-08-23T11:40Z; unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`; the trait-plus-state autocorrelation uses `TRAITVAR` and is not `discreteDRIFTstd`; p. 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:06Z; unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`; the continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`; `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z; unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`; the discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`; `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z); unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`; the discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`; `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z; unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`; `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z; unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`; the asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`; `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z; the affected variance is free `T0VAR`, not `asymDIFFUSION`; unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`; `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`; `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`; `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); 2017-era `addedT0TIPREDVAR` is `t0_b² v` (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z; `T0TIPREDEFFECT %*% TIPREDVAR %*% t(T0TIPREDEFFECT)` immediately after `T0TIPREDEFFECTstd`; form `t0_b` first, then square, then multiply by `v`; a zero coefficient or zero predictor variance is exactly zero; free `T0TIPREDEFFECT` does not require `a < 0`; `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map; `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance; free `T0VAR` is not this extra TI variance; `TRAITVAR` is not this extra TI variance; Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z; form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; `t0_b² v` is the latent extra, not the observed extra; `λ² p_0 + θ` is first-occasion observed variance, not this extra; `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra; `MANIFESTVAR` `θ` is not this extra; Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v`; form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; lasting asymptotic extra requires `a < 0`; `(B / a)² v` is the latent extra, not the observed extra; `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra; `λ² p + θ` is stationary observed variance, not this extra; `MANIFESTVAR` `θ` is not this extra; p. 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance; unstandardised `M` is not `TDPREDEFFECTstd`; `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`; intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`; `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`; Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance; unstandardised `t0_m` is not `T0TDPREDEFFECTstd`; `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`; `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`; `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`; free `T0VAR` does not require `a < 0`; p. 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; default ridge is 0); unstandardised `T0VAR` is not `T0VARstd`; `T0TDPREDEFFECTstd` is not `T0VARstd`; `addedT0TIPREDVAR` is not `T0VARstd`; p. 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend); unstandardised `TRAITVAR` is not `TRAITVARstd`; `T0VARstd` is not `TRAITVARstd` even when both equal 1; `addedT0TIPREDVAR` is not `TRAITVARstd`; p. 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0); unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`; `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1; `MANIFESTVAR` is not `MANIFESTTRAITVARstd`; p. 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug); unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`; `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1; Equation 5 `Var(y)` is not `MANIFESTVARstd`; p. 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`); unstandardised `TIPREDVAR` is not `TIPREDVARstd`; `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1; §7.2 `addedTIPREDVAR` is not `TIPREDVARstd`; p. 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`); unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`; `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1; `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`; p. 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `discreteCINT` is not `discreteCINTstd`; `κ / √p` is not `discreteCINTstd`; `(-κ / a) / √p` is not `discreteCINTstd`; `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`; unstandardised `asymCINT` is not `asymCINTstd`; `κ / √p` is not `asymCINTstd`; `discreteCINTstd` is not `asymCINTstd`; `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`; unstandardised `T0MEANS` is not `T0MEANSstd`; `T0VARstd` is not `T0MEANSstd`; `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`; `MANIFESTMEANSstd` is `τ / √θ` after strictly positive `MANIFESTVAR`; unstandardised `MANIFESTMEANS` is not `MANIFESTMEANSstd`; `MANIFESTVARstd` is not `MANIFESTMEANSstd`; `τ / √(λ² Var(η) + θ)` is not `MANIFESTMEANSstd`;))))), CWC-then-event-time residual lag, irregular already-centered residual log-rate, and strong/strict-gated two-group OLS latent-mean difference are implemented on the stacked psychometric PR and are not implemented-main until exact-head checks, review, and protected-main integration complete; full ESEM/set-ESEM, formative composites, DSEM, and matrix continuous-time dynamics remain accepted-target -**Implementation maturity:** partial — construct classification, valid log-ratio/logistic-normal indicator gates, CPU `f64` OLS and posterior-draw loading point-estimate averaging, Rubin `T_m = Ū_m + (1+1/m) B_m` on draw-level OLS loadings, cluster-mean within/between OLS with the CWC contextual effect and Kish ESS WLS, event-time discrete lag-1 and exact scalar local log-rate, exact scalar forward map and unequal-interval remapping, exact scalar discrete effect of a constant predictor, first-order discrete effect of a time-varying predictor with matched sampling and constancy intervals (Voelkle et al., 2012, Eq. 14), exact scalar discrete process noise (Driver, Oud, & Voelkle, 2017, Eq. 3), exact scalar lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), exact scalar stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; §4.3; p. 16 `asymDIFFUSION`), exact scalar trait-plus-state variance and lagged covariance (Driver et al., 2017, §4.3 `TRAITVAR`; not process noise and not `asymDIFFUSION`), exact scalar observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5, p. 5; Table 2, p. 12; `λ² Var(η) + θ` when `MANIFESTTRAITVAR` is zero, else `λ² Var(η) + θ + ψ`; lagged `λ² cov(η_t, η_{t-1}) + ψ`; `MANIFESTVAR` is `Θ`, not `Var(y)`; `Θ` does not enter lagged observed covariance; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; observed-indicator mean is `τ + λ μ` (`MANIFESTMEANS` is `τ`, not `E(y)`; `CINT` is not `MANIFESTMEANS`; `T0MEANS` is not `E(y)`; Equation 1 is the SDE; not a Kalman filter), exact scalar discrete latent mean `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ` (Driver et al., 2017, Eq. 3; `T0MEANS` is not `μ_t`; `CINT` is not the discrete increment), exact scalar evolved observed-indicator mean `τ + λ μ_t` (Driver et al., 2017, Eq. 5 of that Eq. 3 map; the first-occasion map `τ + λ μ_0` is not `E(y_t)`), exact scalar contemporaneous `TDPREDEFFECT` impulse `m x` (Driver et al., 2017, Eq. 3 fourth summand; Table 2 `TDPREDEFFECT` is `M`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; the §7.2 level-change form is not that impulse), exact scalar observed mean of that contemporaneous impulse `τ + λ(μ_t + m x)` (Driver et al., 2017, Eq. 5 of that Eq. 3 composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`), exact scalar time-independent `TIPREDEFFECT` increment `A^{-1}[e^{A Δt} − I] B z` (Driver et al., 2017, Eq. 3 second summand; Table 2 `TIPREDEFFECT` is `B`, not `κ`, not `M`, and not Voelkle Eq. 14; `B` is not that discrete increment), exact scalar observed mean of that increment `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` (Driver et al., 2017, Eq. 5 of that Eq. 3 printed addend after the `T0MEANS` carry and the `CINT` increment; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`), exact scalar within-interval `TDPREDEFFECT` carry `e^{A(t−u)} M x` for `t0 < u < t` (Driver et al., 2017, Eq. 1–2 Green-function integral of Eq. 2; §7.2 dissipation; not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14), exact scalar observed mean of that carry `τ + λ(μ_t + e^{a(t−u)} m x)` (Driver et al., 2017, Eq. 5 of that carried latent mean; `τ + λ μ_t` is not that observed mean), exact scalar first-occasion `T0TIPREDEFFECT` shift `t0_b z` and Eq. 3 first-summand carry `e^{A Δt} t0_b z` (Driver et al., 2017, Table 3, p. 13; Eq. 3, p. 5; `T0TIPREDEFFECT` is not `TIPREDEFFECT` `B`; `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`; `e^{A Δt} t0_b z` is not `t0_b z`), exact scalar observed mean of that first-occasion carry `τ + λ(μ_t + e^{a Δt} t0_b z)` (Driver et al., 2017, Eq. 5 of that Table 3 / Eq. 3 first-summand composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean), exact scalar first-occasion `T0TDPREDEFFECT` shift `t0_m x0` and Eq. 3 first-summand carry `e^{A Δt} t0_m x0` (Driver et al., 2017, Table 3, p. 13; Eq. 3, p. 5; JSS PDF re-opened 2026-08-20T19:10Z; `T0TDPREDEFFECT` is not `TDPREDEFFECT` `M`; `t0_m x0` is not `M x`; `e^{A Δt} t0_m x0` is not `t0_m x0`; `e^{A Δt} t0_m x0` is not `e^{A(t−u)} M x` for `t0 < u < t`; `t0_m x0` is not `t0_b z`; an impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`), exact scalar observed mean of that first-occasion TD carry `τ + λ(μ_t + e^{a Δt} t0_m x0)` (Driver et al., 2017, Eq. 5 of that Table 3 / Eq. 3 first-summand TD composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean; `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean), exact scalar §7.2 level-change `CINT` `κ = −a m x` (Driver et al., 2017, §7.2, pp. 20–21; `a < 0` so `−κ / a = m x`; not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`, and not the extra near-zero-drift latent process also named in §7.2), exact scalar Eq. 3 increment of that setting `(1 − e^{a Δt}) m x` (not `m x`, not `κ`, and not `TIPREDEFFECT`), exact scalar §7.2 extra near-zero-drift latent process contribution `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-20T23:10Z; identification `TDPREDEFFECT` on the extra process is 1; extra `DRIFT` printed as `−0.000001`; precisely 0 causes computational problems; `ε = a` is `a_{ηξ} x Δt e^{a Δt}`; not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`; `ε ≥ 0` fails closed), exact scalar observed mean of that extra-process contribution `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a))` (Driver et al., 2017, Eq. 5, p. 5; §7.2, pp. 22–23; JSS PDF re-opened 2026-08-21T06:12Z; the extra process has `LAMBDA` 0 and is not an observed indicator; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; the contribution is not `E(y_t)`; the evolved-plus-contribution latent mean is not `E(y_t)`), exact scalar after-t0 extra-process contribution `a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)` for `t0 < u < t` and Eq. 5 observed mean `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a))` (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-21T06:32Z; `T0TDPREDEFFECT` uses `Δt` for both the evolution and the extra drive; `TDPREDEFFECT` after `t0` uses `t − u`; `e^{a(t−u)} m x` is a Dirac on the original process, not this `DRIFT` drive), exact scalar §7.2 `asymTIPREDEFFECT` `-B z / a` (Driver et al., 2017, §7.2, pp. 20–21; JSS PDF opened 2026-08-21T13:08Z; expected total change in process means given a time-independent predictor; `a < 0`; not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; `a ≥ 0` fails closed), exact scalar §7.2 `addedTIPREDVAR` `(B / a)² v` (Driver et al., 2017, §7.2, pp. 20–21; stable between-subject variance accounted for by a time-independent predictor; not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`), exact scalar Table 2 `asymCINT` `-κ / a` (Driver et al., 2017, Table 2, p. 12; Eq. 3 as `Δt → ∞`; `a < 0`; not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; `a ≥ 0` fails closed), exact scalar p. 16 stationary `T0MEANS` `-κ / a + −B z / a` (Driver et al., 2017, p. 16; constrained first-occasion mean using `T0MEANSbase` / `T0MEANSfree`; not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean), exact scalar Eq. 5 of that constrained mean `τ + λ(−κ / a + −B z / a)` (Driver et al., 2017, §4.3, pp. 9–10; Eq. 5, p. 5; JSS PDF re-opened 2026-08-21T20:07Z; form the stationary latent mean first, then `τ + λ` of that mean; `τ + λ μ_0` is not that observed mean; `τ + λ(−κ / a)` is not that observed mean when `B z ≠ 0`; `τ + λ μ_t` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`), exact scalar §4.3 / p. 16 stationary `T0VAR` `trait + −q / (2 a) + (B / a)² v` (Driver et al., 2017, §4.3, pp. 9–10; p. 16; JSS PDF re-opened 2026-08-22T03:07Z; not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`), exact scalar lagged covariance of that constrained process `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T19:13Z; trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map), exact scalar Eq. 5 of that lagged covariance `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ` (`Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance), exact scalar later-occasion variance of that constrained process `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T23:12Z; trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt` and is not that later map; `Q_Δt` is not that later map), exact scalar Eq. 5 of that later-occasion variance `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ` (the lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not that later observed variance; the later-occasion latent variance is not that observed variance)), exact scalar later-occasion variance of §4.3 predetermined `T0VAR` `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T05:12Z; trait and `addedTIPREDVAR` do not enter `Q_Δt`; free `T0VAR` `p_0` is not that later map; setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map; stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map; as `Δt → ∞` with stable `a < 0` the composition approaches contemporaneous stationary `T0VAR`; as `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`; nonzero diffusion with `a ≥ 0` is a growing process and is kept. Eq. 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ` (`MANIFESTVAR` is not that later observed variance; the predetermined later-occasion latent variance is not that observed variance; stationary later observed variance is not that observed variance when `p_0` is free)), exact scalar lagged covariance of §4.3 predetermined `T0VAR` `trait + e^{a Δt} p_0 + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T09:04Z; trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`; free `T0VAR` `p_0` is not that lagged map; setting `p_0 = −q / (2 a)` recovers the stationary lagged map; stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map; later-occasion variance includes `Q_Δt` and is not that lagged map. Eq. 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ` (`MANIFESTVAR` does not enter; the predetermined lagged latent covariance is not that observed covariance; predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance; stationary lagged observed covariance is not that observed covariance when `p_0` is free)), exact scalar first-occasion variance of §4.3 predetermined `T0VAR` `trait + p_0 + (B / a)² v` (free `p_0` is not that map; stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free; lagged covariance decays the state and is not that map; later-occasion variance includes `Q_Δt` and is not that map), exact scalar Eq. 5 of that predetermined first-occasion variance `λ²(trait + p_0 + (B / a)² v) + θ + ψ` (`MANIFESTVAR` is not that first-occasion observed variance; the predetermined first-occasion latent variance is not that observed variance; stationary first-occasion observed variance is not that observed variance when `p_0` is free; predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance; later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z; first-occasion lagged omits `e^{a s} Q_u`; later-occasion variance does not lag; stationary lagged uses `−q / (2 a)`; decaying the later total is not that map; Eq. 5 of that later-start lagged covariance is `λ²` of it plus `ψ`; `Θ` does not enter; first-occasion lagged observed omits `e^{a s} Q_u`; later observed variance includes `Q_u` and `θ`; later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z; later-occasion variance at `u` omits `Q_s`; later-start lagged covariance omits `Q_s`; stationary later uses `−q / (2 a)`; evolving the later total as if it were all state is not that map; ignoring `startoffset` omits `e^{2 a s} Q_u`; Eq. 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`; `MANIFESTVAR` is not that observed variance; p. 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; footnote 4; §7.1; JSS PDF re-opened 2026-08-23T11:40Z; unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`; the trait-plus-state autocorrelation uses `TRAITVAR` and is not `discreteDRIFTstd`; p. 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:06Z; unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`; the continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`; `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z; unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`; the discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`; `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z); unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`; the discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`; `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z; unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`; `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z; unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`; the asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`; `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z; the affected variance is free `T0VAR`, not `asymDIFFUSION`; unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`; `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`; `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`; `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); 2017-era `addedT0TIPREDVAR` is `t0_b² v` (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z; `T0TIPREDEFFECT %*% TIPREDVAR %*% t(T0TIPREDEFFECT)` immediately after `T0TIPREDEFFECTstd`; form `t0_b` first, then square, then multiply by `v`; a zero coefficient or zero predictor variance is exactly zero; free `T0TIPREDEFFECT` does not require `a < 0`; `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map; `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance; free `T0VAR` is not this extra TI variance; `TRAITVAR` is not this extra TI variance; Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z; form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; `t0_b² v` is the latent extra, not the observed extra; `λ² p_0 + θ` is first-occasion observed variance, not this extra; `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra; `MANIFESTVAR` `θ` is not this extra; Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v`; form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; lasting asymptotic extra requires `a < 0`; `(B / a)² v` is the latent extra, not the observed extra; `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra; `λ² p + θ` is stationary observed variance, not this extra; `MANIFESTVAR` `θ` is not this extra; p. 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance; unstandardised `M` is not `TDPREDEFFECTstd`; `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`; intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`; `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`; Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance; unstandardised `t0_m` is not `T0TDPREDEFFECTstd`; `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`; `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`; `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`; free `T0VAR` does not require `a < 0`; p. 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; default ridge is 0); unstandardised `T0VAR` is not `T0VARstd`; `T0TDPREDEFFECTstd` is not `T0VARstd`; `addedT0TIPREDVAR` is not `T0VARstd`; p. 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend); unstandardised `TRAITVAR` is not `TRAITVARstd`; `T0VARstd` is not `TRAITVARstd` even when both equal 1; `addedT0TIPREDVAR` is not `TRAITVARstd`; p. 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0); unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`; `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1; `MANIFESTVAR` is not `MANIFESTTRAITVARstd`; p. 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug); unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`; `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1; Equation 5 `Var(y)` is not `MANIFESTVARstd`; p. 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`); unstandardised `TIPREDVAR` is not `TIPREDVARstd`; `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1; §7.2 `addedTIPREDVAR` is not `TIPREDVARstd`; p. 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`); unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`; `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1; `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`; p. 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `discreteCINT` is not `discreteCINTstd`; `κ / √p` is not `discreteCINTstd`; `(-κ / a) / √p` is not `discreteCINTstd`; `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`; unstandardised `asymCINT` is not `asymCINTstd`; `κ / √p` is not `asymCINTstd`; `discreteCINTstd` is not `asymCINTstd`; `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`; unstandardised `T0MEANS` is not `T0MEANSstd`; `T0VARstd` is not `T0MEANSstd`; `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`;))))), CWC-then-event-time residual lag, irregular already-centered residual log-rate, and strong/strict-gated two-group OLS latent-mean difference are implemented on the consolidation vehicle PR `integration/psychometric-standardisation` (folding draft stack #181–#218) and are not implemented-main until exact-head checks, review, and protected-main integration complete; full ESEM/set-ESEM, formative composites, DSEM, and matrix continuous-time dynamics remain accepted-target +**Implementation maturity:** partial — construct classification, valid log-ratio/logistic-normal indicator gates, CPU `f64` OLS and posterior-draw loading point-estimate averaging, Rubin `T_m = Ū_m + (1+1/m) B_m` on draw-level OLS loadings, cluster-mean within/between OLS with the CWC contextual effect and Kish ESS WLS, event-time discrete lag-1 and exact scalar local log-rate, exact scalar forward map and unequal-interval remapping, exact scalar discrete effect of a constant predictor, first-order discrete effect of a time-varying predictor with matched sampling and constancy intervals (Voelkle et al., 2012, Eq. 14), exact scalar discrete process noise (Driver, Oud, & Voelkle, 2017, Eq. 3), exact scalar lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), exact scalar stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; §4.3; p. 16 `asymDIFFUSION`), exact scalar trait-plus-state variance, lagged covariance, and expected autocorrelation (Driver et al., 2017, §4.3 `TRAITVAR`; §7.1; not process noise, not `asymDIFFUSION`, not `discreteDRIFT`, and not `discreteDRIFTstd`), exact scalar observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5, p. 5; Table 2, p. 12; `λ² Var(η) + θ` when `MANIFESTTRAITVAR` is zero, else `λ² Var(η) + θ + ψ`; lagged `λ² cov(η_t, η_{t-1}) + ψ`; `MANIFESTVAR` is `Θ`, not `Var(y)`; `Θ` does not enter lagged observed covariance; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; observed-indicator mean is `τ + λ μ` (`MANIFESTMEANS` is `τ`, not `E(y)`; `CINT` is not `MANIFESTMEANS`; `T0MEANS` is not `E(y)`; Equation 1 is the SDE; not a Kalman filter), exact scalar discrete latent mean `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ` (Driver et al., 2017, Eq. 3; `T0MEANS` is not `μ_t`; `CINT` is not the discrete increment), exact scalar evolved observed-indicator mean `τ + λ μ_t` (Driver et al., 2017, Eq. 5 of that Eq. 3 map; the first-occasion map `τ + λ μ_0` is not `E(y_t)`), exact scalar contemporaneous `TDPREDEFFECT` impulse `m x` (Driver et al., 2017, Eq. 3 fourth summand; Table 2 `TDPREDEFFECT` is `M`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; the §7.2 level-change form is not that impulse), exact scalar observed mean of that contemporaneous impulse `τ + λ(μ_t + m x)` (Driver et al., 2017, Eq. 5 of that Eq. 3 composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`), exact scalar time-independent `TIPREDEFFECT` increment `A^{-1}[e^{A Δt} − I] B z` (Driver et al., 2017, Eq. 3 second summand; Table 2 `TIPREDEFFECT` is `B`, not `κ`, not `M`, and not Voelkle Eq. 14; `B` is not that discrete increment), exact scalar observed mean of that increment `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` (Driver et al., 2017, Eq. 5 of that Eq. 3 printed addend after the `T0MEANS` carry and the `CINT` increment; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`), exact scalar within-interval `TDPREDEFFECT` carry `e^{A(t−u)} M x` for `t0 < u < t` (Driver et al., 2017, Eq. 1–2 Green-function integral of Eq. 2; §7.2 dissipation; not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14), exact scalar observed mean of that carry `τ + λ(μ_t + e^{a(t−u)} m x)` (Driver et al., 2017, Eq. 5 of that carried latent mean; `τ + λ μ_t` is not that observed mean), exact scalar first-occasion `T0TIPREDEFFECT` shift `t0_b z` and Eq. 3 first-summand carry `e^{A Δt} t0_b z` (Driver et al., 2017, Table 3, p. 13; Eq. 3, p. 5; `T0TIPREDEFFECT` is not `TIPREDEFFECT` `B`; `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`; `e^{A Δt} t0_b z` is not `t0_b z`), exact scalar observed mean of that first-occasion carry `τ + λ(μ_t + e^{a Δt} t0_b z)` (Driver et al., 2017, Eq. 5 of that Table 3 / Eq. 3 first-summand composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean), exact scalar first-occasion `T0TDPREDEFFECT` shift `t0_m x0` and Eq. 3 first-summand carry `e^{A Δt} t0_m x0` (Driver et al., 2017, Table 3, p. 13; Eq. 3, p. 5; JSS PDF re-opened 2026-08-20T19:10Z; `T0TDPREDEFFECT` is not `TDPREDEFFECT` `M`; `t0_m x0` is not `M x`; `e^{A Δt} t0_m x0` is not `t0_m x0`; `e^{A Δt} t0_m x0` is not `e^{A(t−u)} M x` for `t0 < u < t`; `t0_m x0` is not `t0_b z`; an impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`), exact scalar observed mean of that first-occasion TD carry `τ + λ(μ_t + e^{a Δt} t0_m x0)` (Driver et al., 2017, Eq. 5 of that Table 3 / Eq. 3 first-summand TD composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean; `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean), exact scalar §7.2 level-change `CINT` `κ = −a m x` (Driver et al., 2017, §7.2, pp. 20–21; `a < 0` so `−κ / a = m x`; not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`, and not the extra near-zero-drift latent process also named in §7.2), exact scalar Eq. 3 increment of that setting `(1 − e^{a Δt}) m x` (not `m x`, not `κ`, and not `TIPREDEFFECT`), exact scalar §7.2 extra near-zero-drift latent process contribution `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-20T23:10Z; identification `TDPREDEFFECT` on the extra process is 1; extra `DRIFT` printed as `−0.000001`; precisely 0 causes computational problems; `ε = a` is `a_{ηξ} x Δt e^{a Δt}`; not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`; `ε ≥ 0` fails closed), exact scalar observed mean of that extra-process contribution `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a))` (Driver et al., 2017, Eq. 5, p. 5; §7.2, pp. 22–23; JSS PDF re-opened 2026-08-21T06:12Z; the extra process has `LAMBDA` 0 and is not an observed indicator; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; the contribution is not `E(y_t)`; the evolved-plus-contribution latent mean is not `E(y_t)`), exact scalar after-t0 extra-process contribution `a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)` for `t0 < u < t` and Eq. 5 observed mean `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a))` (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-21T06:32Z; `T0TDPREDEFFECT` uses `Δt` for both the evolution and the extra drive; `TDPREDEFFECT` after `t0` uses `t − u`; `e^{a(t−u)} m x` is a Dirac on the original process, not this `DRIFT` drive), exact scalar §7.2 `asymTIPREDEFFECT` `-B z / a` (Driver et al., 2017, §7.2, pp. 20–21; JSS PDF opened 2026-08-21T13:08Z; expected total change in process means given a time-independent predictor; `a < 0`; not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; `a ≥ 0` fails closed), exact scalar §7.2 `addedTIPREDVAR` `(B / a)² v` (Driver et al., 2017, §7.2, pp. 20–21; stable between-subject variance accounted for by a time-independent predictor; not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`), exact scalar Table 2 `asymCINT` `-κ / a` (Driver et al., 2017, Table 2, p. 12; Eq. 3 as `Δt → ∞`; `a < 0`; not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; `a ≥ 0` fails closed), exact scalar p. 16 stationary `T0MEANS` `-κ / a + −B z / a` (Driver et al., 2017, p. 16; constrained first-occasion mean using `T0MEANSbase` / `T0MEANSfree`; not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean), exact scalar Eq. 5 of that constrained mean `τ + λ(−κ / a + −B z / a)` (Driver et al., 2017, §4.3, pp. 9–10; Eq. 5, p. 5; JSS PDF re-opened 2026-08-21T20:07Z; form the stationary latent mean first, then `τ + λ` of that mean; `τ + λ μ_0` is not that observed mean; `τ + λ(−κ / a)` is not that observed mean when `B z ≠ 0`; `τ + λ μ_t` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`), exact scalar §4.3 / p. 16 stationary `T0VAR` `trait + −q / (2 a) + (B / a)² v` (Driver et al., 2017, §4.3, pp. 9–10; p. 16; JSS PDF re-opened 2026-08-22T03:07Z; not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`), exact scalar lagged covariance of that constrained process `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T19:13Z; trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map), exact scalar Eq. 5 of that lagged covariance `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ` (`Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance), exact scalar later-occasion variance of that constrained process `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T23:12Z; trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt` and is not that later map; `Q_Δt` is not that later map), exact scalar Eq. 5 of that later-occasion variance `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ` (the lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not that later observed variance; the later-occasion latent variance is not that observed variance)), exact scalar later-occasion variance of §4.3 predetermined `T0VAR` `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T05:12Z; trait and `addedTIPREDVAR` do not enter `Q_Δt`; free `T0VAR` `p_0` is not that later map; setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map; stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map; as `Δt → ∞` with stable `a < 0` the composition approaches contemporaneous stationary `T0VAR`; as `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`; nonzero diffusion with `a ≥ 0` is a growing process and is kept. Eq. 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ` (`MANIFESTVAR` is not that later observed variance; the predetermined later-occasion latent variance is not that observed variance; stationary later observed variance is not that observed variance when `p_0` is free)), exact scalar lagged covariance of §4.3 predetermined `T0VAR` `trait + e^{a Δt} p_0 + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T09:04Z; trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`; free `T0VAR` `p_0` is not that lagged map; setting `p_0 = −q / (2 a)` recovers the stationary lagged map; stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map; later-occasion variance includes `Q_Δt` and is not that lagged map. Eq. 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ` (`MANIFESTVAR` does not enter; the predetermined lagged latent covariance is not that observed covariance; predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance; stationary lagged observed covariance is not that observed covariance when `p_0` is free)), exact scalar first-occasion variance of §4.3 predetermined `T0VAR` `trait + p_0 + (B / a)² v` (free `p_0` is not that map; stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free; lagged covariance decays the state and is not that map; later-occasion variance includes `Q_Δt` and is not that map), exact scalar Eq. 5 of that predetermined first-occasion variance `λ²(trait + p_0 + (B / a)² v) + θ + ψ` (`MANIFESTVAR` is not that first-occasion observed variance; the predetermined first-occasion latent variance is not that observed variance; stationary first-occasion observed variance is not that observed variance when `p_0` is free; predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance; later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z; first-occasion lagged omits `e^{a s} Q_u`; later-occasion variance does not lag; stationary lagged uses `−q / (2 a)`; decaying the later total is not that map; Eq. 5 of that later-start lagged covariance is `λ²` of it plus `ψ`; `Θ` does not enter; first-occasion lagged observed omits `e^{a s} Q_u`; later observed variance includes `Q_u` and `θ`; later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z; later-occasion variance at `u` omits `Q_s`; later-start lagged covariance omits `Q_s`; stationary later uses `−q / (2 a)`; evolving the later total as if it were all state is not that map; ignoring `startoffset` omits `e^{2 a s} Q_u`; Eq. 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`; `MANIFESTVAR` is not that observed variance; p. 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; footnote 4; §7.1; JSS PDF re-opened 2026-08-23T11:40Z; unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`; the trait-plus-state autocorrelation uses `TRAITVAR` and is not `discreteDRIFTstd`; p. 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:06Z; unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`; the continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`; `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z; unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`; the discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`; `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z); unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`; the discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`; `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z; unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`; `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z; unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`; the asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`; `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z; the affected variance is free `T0VAR`, not `asymDIFFUSION`; unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`; `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`; `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`; `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); 2017-era `addedT0TIPREDVAR` is `t0_b² v` (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z; `T0TIPREDEFFECT %*% TIPREDVAR %*% t(T0TIPREDEFFECT)` immediately after `T0TIPREDEFFECTstd`; form `t0_b` first, then square, then multiply by `v`; a zero coefficient or zero predictor variance is exactly zero; free `T0TIPREDEFFECT` does not require `a < 0`; `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map; `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance; free `T0VAR` is not this extra TI variance; `TRAITVAR` is not this extra TI variance; Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z; form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; `t0_b² v` is the latent extra, not the observed extra; `λ² p_0 + θ` is first-occasion observed variance, not this extra; `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra; `MANIFESTVAR` `θ` is not this extra; Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v`; form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; lasting asymptotic extra requires `a < 0`; `(B / a)² v` is the latent extra, not the observed extra; `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra; `λ² p + θ` is stationary observed variance, not this extra; `MANIFESTVAR` `θ` is not this extra; p. 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance; unstandardised `M` is not `TDPREDEFFECTstd`; `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`; intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`; `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`; Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance; unstandardised `t0_m` is not `T0TDPREDEFFECTstd`; `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`; `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`; `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`; free `T0VAR` does not require `a < 0`; p. 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; default ridge is 0); unstandardised `T0VAR` is not `T0VARstd`; `T0TDPREDEFFECTstd` is not `T0VARstd`; `addedT0TIPREDVAR` is not `T0VARstd`; p. 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend); unstandardised `TRAITVAR` is not `TRAITVARstd`; `T0VARstd` is not `TRAITVARstd` even when both equal 1; `addedT0TIPREDVAR` is not `TRAITVARstd`; p. 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0); unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`; `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1; `MANIFESTVAR` is not `MANIFESTTRAITVARstd`; p. 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug); unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`; `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1; Equation 5 `Var(y)` is not `MANIFESTVARstd`; p. 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`); unstandardised `TIPREDVAR` is not `TIPREDVARstd`; `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1; §7.2 `addedTIPREDVAR` is not `TIPREDVARstd`; p. 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`); unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`; `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1; `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`; p. 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `discreteCINT` is not `discreteCINTstd`; `κ / √p` is not `discreteCINTstd`; `(-κ / a) / √p` is not `discreteCINTstd`; `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`; unstandardised `asymCINT` is not `asymCINTstd`; `κ / √p` is not `asymCINTstd`; `discreteCINTstd` is not `asymCINTstd`; `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`; unstandardised `T0MEANS` is not `T0MEANSstd`; `T0VARstd` is not `T0MEANSstd`; `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`; `MANIFESTMEANSstd` is `τ / √θ` after strictly positive `MANIFESTVAR`; unstandardised `MANIFESTMEANS` is not `MANIFESTMEANSstd`; `MANIFESTVARstd` is not `MANIFESTMEANSstd`; `τ / √(λ² Var(η) + θ)` is not `MANIFESTMEANSstd`;))))), CWC-then-event-time residual lag, irregular already-centered residual log-rate, and strong/strict-gated two-group OLS latent-mean difference are implemented on the stacked psychometric PR and are not implemented-main until exact-head checks, review, and protected-main integration complete; full ESEM/set-ESEM, formative composites, DSEM, and matrix continuous-time dynamics remain accepted-target +**Implementation maturity:** partial — construct classification, valid log-ratio/logistic-normal indicator gates, CPU `f64` OLS and posterior-draw loading point-estimate averaging, Rubin `T_m = Ū_m + (1+1/m) B_m` on draw-level OLS loadings, cluster-mean within/between OLS with the CWC contextual effect and Kish ESS WLS, event-time discrete lag-1 and exact scalar local log-rate, exact scalar forward map and unequal-interval remapping, exact scalar discrete effect of a constant predictor, first-order discrete effect of a time-varying predictor with matched sampling and constancy intervals (Voelkle et al., 2012, Eq. 14), exact scalar discrete process noise (Driver, Oud, & Voelkle, 2017, Eq. 3), exact scalar lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), exact scalar stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; §4.3; p. 16 `asymDIFFUSION`), exact scalar trait-plus-state variance, lagged covariance, and expected autocorrelation (Driver et al., 2017, §4.3 `TRAITVAR`; §7.1; not process noise, not `asymDIFFUSION`, not `discreteDRIFT`, and not `discreteDRIFTstd`), exact scalar observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5, p. 5; Table 2, p. 12; `λ² Var(η) + θ` when `MANIFESTTRAITVAR` is zero, else `λ² Var(η) + θ + ψ`; lagged `λ² cov(η_t, η_{t-1}) + ψ`; `MANIFESTVAR` is `Θ`, not `Var(y)`; `Θ` does not enter lagged observed covariance; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; observed-indicator mean is `τ + λ μ` (`MANIFESTMEANS` is `τ`, not `E(y)`; `CINT` is not `MANIFESTMEANS`; `T0MEANS` is not `E(y)`; Equation 1 is the SDE; not a Kalman filter), exact scalar discrete latent mean `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ` (Driver et al., 2017, Eq. 3; `T0MEANS` is not `μ_t`; `CINT` is not the discrete increment), exact scalar evolved observed-indicator mean `τ + λ μ_t` (Driver et al., 2017, Eq. 5 of that Eq. 3 map; the first-occasion map `τ + λ μ_0` is not `E(y_t)`), exact scalar contemporaneous `TDPREDEFFECT` impulse `m x` (Driver et al., 2017, Eq. 3 fourth summand; Table 2 `TDPREDEFFECT` is `M`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; the §7.2 level-change form is not that impulse), exact scalar observed mean of that contemporaneous impulse `τ + λ(μ_t + m x)` (Driver et al., 2017, Eq. 5 of that Eq. 3 composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`), exact scalar time-independent `TIPREDEFFECT` increment `A^{-1}[e^{A Δt} − I] B z` (Driver et al., 2017, Eq. 3 second summand; Table 2 `TIPREDEFFECT` is `B`, not `κ`, not `M`, and not Voelkle Eq. 14; `B` is not that discrete increment), exact scalar observed mean of that increment `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` (Driver et al., 2017, Eq. 5 of that Eq. 3 printed addend after the `T0MEANS` carry and the `CINT` increment; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`), exact scalar within-interval `TDPREDEFFECT` carry `e^{A(t−u)} M x` for `t0 < u < t` (Driver et al., 2017, Eq. 1–2 Green-function integral of Eq. 2; §7.2 dissipation; not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14), exact scalar observed mean of that carry `τ + λ(μ_t + e^{a(t−u)} m x)` (Driver et al., 2017, Eq. 5 of that carried latent mean; `τ + λ μ_t` is not that observed mean), exact scalar first-occasion `T0TIPREDEFFECT` shift `t0_b z` and Eq. 3 first-summand carry `e^{A Δt} t0_b z` (Driver et al., 2017, Table 3, p. 13; Eq. 3, p. 5; `T0TIPREDEFFECT` is not `TIPREDEFFECT` `B`; `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`; `e^{A Δt} t0_b z` is not `t0_b z`), exact scalar observed mean of that first-occasion carry `τ + λ(μ_t + e^{a Δt} t0_b z)` (Driver et al., 2017, Eq. 5 of that Table 3 / Eq. 3 first-summand composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean), exact scalar first-occasion `T0TDPREDEFFECT` shift `t0_m x0` and Eq. 3 first-summand carry `e^{A Δt} t0_m x0` (Driver et al., 2017, Table 3, p. 13; Eq. 3, p. 5; JSS PDF re-opened 2026-08-20T19:10Z; `T0TDPREDEFFECT` is not `TDPREDEFFECT` `M`; `t0_m x0` is not `M x`; `e^{A Δt} t0_m x0` is not `t0_m x0`; `e^{A Δt} t0_m x0` is not `e^{A(t−u)} M x` for `t0 < u < t`; `t0_m x0` is not `t0_b z`; an impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`), exact scalar observed mean of that first-occasion TD carry `τ + λ(μ_t + e^{a Δt} t0_m x0)` (Driver et al., 2017, Eq. 5 of that Table 3 / Eq. 3 first-summand TD composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean; `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean), exact scalar §7.2 level-change `CINT` `κ = −a m x` (Driver et al., 2017, §7.2, pp. 20–21; `a < 0` so `−κ / a = m x`; not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`, and not the extra near-zero-drift latent process also named in §7.2), exact scalar Eq. 3 increment of that setting `(1 − e^{a Δt}) m x` (not `m x`, not `κ`, and not `TIPREDEFFECT`), exact scalar §7.2 extra near-zero-drift latent process contribution `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-20T23:10Z; identification `TDPREDEFFECT` on the extra process is 1; extra `DRIFT` printed as `−0.000001`; precisely 0 causes computational problems; `ε = a` is `a_{ηξ} x Δt e^{a Δt}`; not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`; `ε ≥ 0` fails closed), exact scalar observed mean of that extra-process contribution `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a))` (Driver et al., 2017, Eq. 5, p. 5; §7.2, pp. 22–23; JSS PDF re-opened 2026-08-21T06:12Z; the extra process has `LAMBDA` 0 and is not an observed indicator; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; the contribution is not `E(y_t)`; the evolved-plus-contribution latent mean is not `E(y_t)`), exact scalar after-t0 extra-process contribution `a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)` for `t0 < u < t` and Eq. 5 observed mean `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a))` (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-21T06:32Z; `T0TDPREDEFFECT` uses `Δt` for both the evolution and the extra drive; `TDPREDEFFECT` after `t0` uses `t − u`; `e^{a(t−u)} m x` is a Dirac on the original process, not this `DRIFT` drive), exact scalar §7.2 `asymTIPREDEFFECT` `-B z / a` (Driver et al., 2017, §7.2, pp. 20–21; JSS PDF opened 2026-08-21T13:08Z; expected total change in process means given a time-independent predictor; `a < 0`; not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; `a ≥ 0` fails closed), exact scalar §7.2 `addedTIPREDVAR` `(B / a)² v` (Driver et al., 2017, §7.2, pp. 20–21; stable between-subject variance accounted for by a time-independent predictor; not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`), exact scalar Table 2 `asymCINT` `-κ / a` (Driver et al., 2017, Table 2, p. 12; Eq. 3 as `Δt → ∞`; `a < 0`; not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; `a ≥ 0` fails closed), exact scalar p. 16 stationary `T0MEANS` `-κ / a + −B z / a` (Driver et al., 2017, p. 16; constrained first-occasion mean using `T0MEANSbase` / `T0MEANSfree`; not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean), exact scalar Eq. 5 of that constrained mean `τ + λ(−κ / a + −B z / a)` (Driver et al., 2017, §4.3, pp. 9–10; Eq. 5, p. 5; JSS PDF re-opened 2026-08-21T20:07Z; form the stationary latent mean first, then `τ + λ` of that mean; `τ + λ μ_0` is not that observed mean; `τ + λ(−κ / a)` is not that observed mean when `B z ≠ 0`; `τ + λ μ_t` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`), exact scalar §4.3 / p. 16 stationary `T0VAR` `trait + −q / (2 a) + (B / a)² v` (Driver et al., 2017, §4.3, pp. 9–10; p. 16; JSS PDF re-opened 2026-08-22T03:07Z; not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`), exact scalar lagged covariance of that constrained process `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T19:13Z; trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map), exact scalar Eq. 5 of that lagged covariance `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ` (`Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance), exact scalar later-occasion variance of that constrained process `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T23:12Z; trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt` and is not that later map; `Q_Δt` is not that later map), exact scalar Eq. 5 of that later-occasion variance `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ` (the lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not that later observed variance; the later-occasion latent variance is not that observed variance)), exact scalar later-occasion variance of §4.3 predetermined `T0VAR` `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T05:12Z; trait and `addedTIPREDVAR` do not enter `Q_Δt`; free `T0VAR` `p_0` is not that later map; setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map; stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map; as `Δt → ∞` with stable `a < 0` the composition approaches contemporaneous stationary `T0VAR`; as `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`; nonzero diffusion with `a ≥ 0` is a growing process and is kept. Eq. 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ` (`MANIFESTVAR` is not that later observed variance; the predetermined later-occasion latent variance is not that observed variance; stationary later observed variance is not that observed variance when `p_0` is free)), exact scalar lagged covariance of §4.3 predetermined `T0VAR` `trait + e^{a Δt} p_0 + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T09:04Z; trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`; free `T0VAR` `p_0` is not that lagged map; setting `p_0 = −q / (2 a)` recovers the stationary lagged map; stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map; later-occasion variance includes `Q_Δt` and is not that lagged map. Eq. 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ` (`MANIFESTVAR` does not enter; the predetermined lagged latent covariance is not that observed covariance; predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance; stationary lagged observed covariance is not that observed covariance when `p_0` is free)), exact scalar first-occasion variance of §4.3 predetermined `T0VAR` `trait + p_0 + (B / a)² v` (free `p_0` is not that map; stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free; lagged covariance decays the state and is not that map; later-occasion variance includes `Q_Δt` and is not that map), exact scalar Eq. 5 of that predetermined first-occasion variance `λ²(trait + p_0 + (B / a)² v) + θ + ψ` (`MANIFESTVAR` is not that first-occasion observed variance; the predetermined first-occasion latent variance is not that observed variance; stationary first-occasion observed variance is not that observed variance when `p_0` is free; predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance; later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z; first-occasion lagged omits `e^{a s} Q_u`; later-occasion variance does not lag; stationary lagged uses `−q / (2 a)`; decaying the later total is not that map; Eq. 5 of that later-start lagged covariance is `λ²` of it plus `ψ`; `Θ` does not enter; first-occasion lagged observed omits `e^{a s} Q_u`; later observed variance includes `Q_u` and `θ`; later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z; later-occasion variance at `u` omits `Q_s`; later-start lagged covariance omits `Q_s`; stationary later uses `−q / (2 a)`; evolving the later total as if it were all state is not that map; ignoring `startoffset` omits `e^{2 a s} Q_u`; Eq. 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`; `MANIFESTVAR` is not that observed variance; p. 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; footnote 4; §7.1; JSS PDF re-opened 2026-08-23T11:40Z; unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`; the trait-plus-state autocorrelation uses `TRAITVAR` and is not `discreteDRIFTstd`; p. 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:06Z; unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`; the continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`; `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z; unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`; the discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`; `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z); unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`; the discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`; `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z; unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`; `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z; unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`; the asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`; `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z; the affected variance is free `T0VAR`, not `asymDIFFUSION`; unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`; `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`; `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`; `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); 2017-era `addedT0TIPREDVAR` is `t0_b² v` (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z; `T0TIPREDEFFECT %*% TIPREDVAR %*% t(T0TIPREDEFFECT)` immediately after `T0TIPREDEFFECTstd`; form `t0_b` first, then square, then multiply by `v`; a zero coefficient or zero predictor variance is exactly zero; free `T0TIPREDEFFECT` does not require `a < 0`; `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map; `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance; free `T0VAR` is not this extra TI variance; `TRAITVAR` is not this extra TI variance; Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z; form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; `t0_b² v` is the latent extra, not the observed extra; `λ² p_0 + θ` is first-occasion observed variance, not this extra; `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra; `MANIFESTVAR` `θ` is not this extra; Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v`; form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; lasting asymptotic extra requires `a < 0`; `(B / a)² v` is the latent extra, not the observed extra; `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra; `λ² p + θ` is stationary observed variance, not this extra; `MANIFESTVAR` `θ` is not this extra; p. 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance; unstandardised `M` is not `TDPREDEFFECTstd`; `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`; intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`; `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`; Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance; unstandardised `t0_m` is not `T0TDPREDEFFECTstd`; `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`; `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`; `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`; free `T0VAR` does not require `a < 0`; p. 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; default ridge is 0); unstandardised `T0VAR` is not `T0VARstd`; `T0TDPREDEFFECTstd` is not `T0VARstd`; `addedT0TIPREDVAR` is not `T0VARstd`; p. 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend); unstandardised `TRAITVAR` is not `TRAITVARstd`; `T0VARstd` is not `TRAITVARstd` even when both equal 1; `addedT0TIPREDVAR` is not `TRAITVARstd`; p. 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0); unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`; `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1; `MANIFESTVAR` is not `MANIFESTTRAITVARstd`; p. 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug); unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`; `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1; Equation 5 `Var(y)` is not `MANIFESTVARstd`; p. 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`); unstandardised `TIPREDVAR` is not `TIPREDVARstd`; `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1; §7.2 `addedTIPREDVAR` is not `TIPREDVARstd`; p. 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`); unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`; `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1; `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`; p. 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `discreteCINT` is not `discreteCINTstd`; `κ / √p` is not `discreteCINTstd`; `(-κ / a) / √p` is not `discreteCINTstd`; `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`; unstandardised `asymCINT` is not `asymCINTstd`; `κ / √p` is not `asymCINTstd`; `discreteCINTstd` is not `asymCINTstd`; `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`; unstandardised `T0MEANS` is not `T0MEANSstd`; `T0VARstd` is not `T0MEANSstd`; `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`;))))), CWC-then-event-time residual lag, irregular already-centered residual log-rate, and strong/strict-gated two-group OLS latent-mean difference are implemented on the consolidation vehicle PR `integration/psychometric-standardisation` (folding draft stack #181–#218) and are not implemented-main until exact-head checks, review, and protected-main integration complete; full ESEM/set-ESEM, formative composites, DSEM, and matrix continuous-time dynamics remain accepted-target **Date:** 2026-08-05 **Supersedes:** None. ADR 0012 governs upstream topic measurement/network coordinates; this ADR governs higher-order psychometric structure and longitudinal interpretation. diff --git a/docs/research/multilevel-event-time-recovery.md b/docs/research/multilevel-event-time-recovery.md index 3701dcb4b..819828dce 100644 --- a/docs/research/multilevel-event-time-recovery.md +++ b/docs/research/multilevel-event-time-recovery.md @@ -108,11 +108,13 @@ This slice stays inside `psychometric_core`. It does not add a second invariance 102. refuse unmatched sampling and constancy intervals for a time-varying predictor (Oud & Jansen, 2000, unread); 103. refuse the difference quotient as a continuous-time rate; 104. apply the same event-time map to CWC residuals (still not DSEM); -105. map already-centered lagged residuals with irregular event intervals without re-centering (Curran & Bauer, 2011, pp. 607–608). +105. map already-centered lagged residuals with irregular event intervals without re-centering (Curran & Bauer, 2011, pp. 607–608); +106. recover the exact scalar §7.1 trait-plus-state expected autocorrelation `(trait + e^{a Δt} p + added) / (trait + p + added)` (Driver et al., 2017, §7.1, pp. 18–19; Eq. 3–4; §4.3; §7.2 `addedTIPREDVAR`; JSS PDF re-opened 2026-09-01T06:30Z; form the lagged covariance first, then the contemporaneous total, then add `addedTIPREDVAR`, then the ratio; zero trait and zero added recovers `e^{a Δt}` numerically and remains a distinct named quantity; zero state with positive trait or extra is exactly 1; zero total variance fails closed; `a > 0` is a growing covariance ratio and is kept; a non-event clock fails closed; a non-positive event interval fails closed); +107. refuse treating that expected autocorrelation as unstandardised `discreteDRIFT` `e^{a Δt}` and refuse treating it as p. 16 `discreteDRIFTstd` (footnote 4 standardises `DRIFT` using only within-subject `asymDIFFUSION`, not the total; `TRAITVAR` is not the standardisation variance). ## Claim boundary -This is two-level OLS and a noiseless scalar continuous-time map. It is not DSEM, not RI-CLPM, not a random-effects sampler, not a Kalman filter, and not a matrix `expm` implementation. The CWC cluster-mean coefficient is the **contextual** effect, not the between-cluster effect. Discrete lags from different event intervals are not one coefficient. Equation 14 is not Equation 12. Discrete process noise \(Q_{\Delta t}\) is not the continuous diffusion \(GG^{\top}\). \(Q_{\Delta t}\) is the conditional residual variance, not \(\operatorname{Var}(\eta_{t})\). Finite-interval \(Q_{\Delta t}\) is not the stationary within-subject variance. Trait variance is not process noise and not the stationary within-subject variance. Measurement-error variance is not the observed-indicator variance. Latent variance is not the observed-indicator variance. Manifest means are not the observed-indicator mean. The latent mean is not the observed-indicator mean. The continuous intercept is not the manifest mean. The first-occasion latent mean is not the evolved latent mean. The continuous intercept is not the discrete mean increment. The first-occasion observed mean is not the evolved observed mean. The contemporaneous `TDPREDEFFECT` impulse is not the continuous intercept, not the time-independent discrete effect, and not Voelkle et al. (2012, Eq. 14). The time-independent `TIPREDEFFECT` increment is not the continuous intercept, not the contemporaneous impulse, not Voelkle et al. (2012, Eq. 14), and not the coefficient `B`. The within-interval `TDPREDEFFECT` carry `e^{A(t−u)} M x` for `t0 < u < t` is not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle et al. (2012, Eq. 14). The evolved observed mean `τ + λ μ_t` is not the contemporaneous-impulse observed mean `τ + λ(μ_t + m x)`. The contemporaneous composition `τ + λ(μ_t + m x)` is not the impulse-carry observed mean `τ + λ(μ_t + e^{a(t−u)} m x)` when `u ≠ t`. The evolved observed mean `τ + λ μ_t` is not the impulse-carry observed mean. The carried latent mean is not `E(y_t)`. The evolved-plus-impulse latent mean is not `E(y_t)`. The evolved observed mean `τ + λ μ_t` is not the time-independent-predictor observed mean `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)`. The contemporaneous composition `τ + λ(μ_t + m x)` is not that time-independent-predictor observed mean. The impulse-carry composition `τ + λ(μ_t + e^{a(t−u)} m x)` is not that time-independent-predictor observed mean when `u ≠ t`. The evolved-plus-increment latent mean is not `E(y_t)`. The Table 3 first-occasion `T0TIPREDEFFECT` shift `t0_b z` is not the Eq. 3 process increment `A^{-1}[e^{A Δt} − I] B z`, not `κ`, and not `M x`. The Eq. 3 first-summand carry `e^{A Δt} t0_b z` is not `t0_b z` and is not that process increment. `T0TIPREDEFFECT` is the coefficient, not the first-occasion shift. The evolved observed mean `τ + λ μ_t` is not the first-occasion TI-predictor observed mean `τ + λ(μ_t + e^{a Δt} t0_b z)`. The process-increment composition `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that first-occasion observed mean. The contemporaneous composition `τ + λ(μ_t + m x)` is not that first-occasion observed mean. The impulse-carry composition `τ + λ(μ_t + e^{a(t−u)} m x)` is not that first-occasion observed mean when `u ≠ t0`. The evolved-plus-T0TIPRED latent mean is not `E(y_t)`. The Table 3 first-occasion `T0TDPREDEFFECT` shift `t0_m x0` is not `M x`, not `e^{A(t−u)} M x` for `t0 < u < t`, not `t0_b z`, not `A^{-1}[e^{A Δt} − I] B z`, and not `κ`. The Eq. 3 first-summand carry `e^{A Δt} t0_m x0` is not `t0_m x0` and is not that within-interval impulse carry. `T0TDPREDEFFECT` is the coefficient, not the first-occasion shift. An impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`. The evolved observed mean `τ + λ μ_t` is not the first-occasion TD-predictor observed mean `τ + λ(μ_t + e^{a Δt} t0_m x0)`. The process-increment composition `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that first-occasion TD observed mean. The contemporaneous composition `τ + λ(μ_t + m x)` is not that first-occasion TD observed mean. The impulse-carry composition `τ + λ(μ_t + e^{a(t−u)} m x)` is not that first-occasion TD observed mean when `u ≠ t0`. The first-occasion TI composition `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that first-occasion TD observed mean. Same numbers as `T0TIPREDEFFECT` yield the same product; Table 3 names a different matrix. The evolved-plus-T0TDPRED latent mean is not `E(y_t)`. The §7.2 level-change `CINT` `κ = −a m x` is not the dissipating Dirac `m x`, not a free `CINT`, and not `A^{-1}[e^{A Δt} − I] B z`. Lasting level change via that `CINT` setting requires `a < 0`. The extra near-zero-drift latent process also named in §7.2 is a different specification and is not that `CINT` setting. The Eq. 3 increment of that setting `(1 − e^{a Δt}) m x` is not the dissipating Dirac `m x`, not `κ`, and not `A^{-1}[e^{A Δt} − I] B z`. Underflow of `e^{a Δt}` to `+0` keeps the equilibrium offset `m x`. The §7.2 extra-process contribution `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` is not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`. Lasting level change via that extra process requires `ε < 0`. Precisely `ε = 0` causes computational problems in the printed specification. The extra-process observed mean `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a))` is not `τ + λ μ_t`, not `τ + λ(μ_t + m x)`, not the contribution, and not the evolved-plus-contribution latent mean. The extra process has `LAMBDA` 0 and is not an observed indicator. `T0TDPREDEFFECT` on the extra process uses `Δt = t − t0` for both the original-process evolution and the extra drive. `TDPREDEFFECT` after `t0` uses `t − u` with `t0 < u < t` for the extra drive while `μ_t` still uses `Δt`. The after-t0 extra-process observed mean is not the first-occasion extra-process observed mean when `u ≠ t0`. The impulse-carry `e^{a(t−u)} m x` is a Dirac on the original process and is not that `DRIFT` drive. The §7.2 `asymTIPREDEFFECT` `-B z / a` is the expected total change in process means given a time-independent predictor. It is not the coefficient `B`, not the finite-interval increment `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`. Lasting asymptotic change via that map requires `a < 0`. The §7.2 `addedTIPREDVAR` `(B / a)² v` is the stable between-subject variance accounted for by that predictor. It is not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`. Table 2 `asymCINT` `-κ / a` is the intercept contribution to the stationary process mean. It is not `κ`, not the finite-interval increment `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`. Lasting asymptotic intercept change via that map requires `a < 0`. Page 16 notes that a `T0MEANS` stationarity constraint includes time-independent predictors; that composition is not this intercept-only map. The p. 16 constrained first-occasion mean `-κ / a + −B z / a` is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean. Equation 5 of that constrained mean `τ + λ(−κ / a + −B z / a)` is not `τ + λ μ_0`, not `τ + λ(−κ / a)` when `B z ≠ 0`, not `τ + λ μ_t`, not `MANIFESTMEANS`, and not the constrained latent mean. The p. 16 constrained first-occasion variance `trait + −q / (2 a) + (B / a)² v` is not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Equation 5 of that constrained variance `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` is not `λ² p_0 + θ`, not `λ²(−q / (2 a)) + θ` when `TRAITVAR` or `addedTIPREDVAR` is nonzero, not `λ² Var(η_t) + θ` when the first occasion is constrained, not `MANIFESTVAR`, and not the constrained latent variance. The lagged covariance of that constrained process `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` is not contemporaneous `T0VAR`, not `e^{a Δt}` of the constrained total, and not `trait + e^{a Δt} p` when `addedTIPREDVAR` is nonzero. Trait variance and `addedTIPREDVAR` do not decay with `e^{a Δt}`. Equation 5 of that lagged covariance `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ` is not `θ`, not contemporaneous `Var(y_0)`, and not the lagged latent covariance. Independent measurement error does not enter lagged observed covariance. The later-occasion variance of that constrained process `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` equals contemporaneous `T0VAR` under stationarity and is not the lagged covariance, not `e^{2 a Δt}` of the constrained total plus `Q_Δt`, and not `Q_Δt` alone. Trait variance and `addedTIPREDVAR` do not enter `Q_Δt`. Equation 5 of that later-occasion variance `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ` is not `θ`, not lagged `cov(y_t, y_{t-1})`, and not the later-occasion latent variance. The later-occasion variance of §4.3 predetermined `T0VAR` `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` is not stationary later-occasion variance when `p_0` is free, not `e^{2 a Δt}` of `trait + p_0 + (B / a)² v` plus `Q_Δt`, and not free `p_0`. Trait variance and `addedTIPREDVAR` do not enter `Q_Δt`. Setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map. As `Δt → ∞` with stable `a < 0` the composition approaches contemporaneous stationary `T0VAR`. Nonzero diffusion with `a ≥ 0` is a growing process and is kept. Equation 5 of that predetermined later-occasion variance `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ` is not `θ`, not the predetermined later-occasion latent variance, and not stationary later-occasion observed variance when `p_0` is free. The lagged covariance of §4.3 predetermined `T0VAR` `trait + e^{a Δt} p_0 + (B / a)² v` is not stationary lagged covariance when `p_0` is free, not later-occasion variance, not `e^{a Δt}` of `trait + p_0 + (B / a)² v`, and not free `p_0`. Trait variance and `addedTIPREDVAR` do not decay with `e^{a Δt}`. Setting `p_0 = −q / (2 a)` recovers the stationary lagged map. As `Δt → ∞` with stable `a < 0` the state term vanishes. A zero-diffusion carry with `a ≥ 0` is `e^{a Δt} p_0` and is kept. Equation 5 of that predetermined lagged covariance `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ` is not `θ`, not the predetermined lagged latent covariance, not predetermined later observed variance, and not stationary lagged observed covariance when `p_0` is free. Independent measurement error does not enter lagged observed covariance. The first-occasion variance of §4.3 predetermined `T0VAR` `trait + p_0 + (B / a)² v` is not stationary first-occasion variance when `p_0` is free, not free `p_0`, not lagged covariance, and not later-occasion variance. Trait variance and `addedTIPREDVAR` do not decay and do not enter `Q_Δt`. Setting `p_0 = −q / (2 a)` recovers the stationary first-occasion map. As `Δt → 0+` the lagged and later maps approach this composition. Trait-only variance does not require a stable drift. Equation 5 of that predetermined first-occasion variance `λ²(trait + p_0 + (B / a)² v) + θ + ψ` is not `θ`, not the predetermined first-occasion latent variance, not stationary first-occasion observed variance, and not predetermined later observed variance when `p_0` is free. The later-start lagged covariance of §4.3 predetermined `T0VAR` `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` is not first-occasion lagged covariance when `u > 0`, not later-occasion variance, not stationary lagged covariance when `p_0` is free, and not `e^{a s}` of the later total. Trait variance and `addedTIPREDVAR` do not decay with `e^{a s}`. Setting `p_0 = −q / (2 a)` recovers the stationary lagged map. As `u → 0+` the composition approaches first-occasion lagged covariance. As `s → 0+` the composition approaches later-occasion variance at `u`. Nonzero diffusion with `a ≥ 0` is a growing process and is kept. Equation 5 of that later-start lagged covariance `λ²(trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v) + ψ` is not `θ`, not the later-start lagged latent covariance, not first-occasion lagged observed covariance, not predetermined later observed variance, and not stationary lagged observed covariance when `p_0` is free. Independent measurement error does not enter lagged observed covariance. The later-start later-occasion variance of §4.3 predetermined `T0VAR` `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` is not later-occasion variance at `u` when `s > 0`, not later-start lagged covariance, not stationary later-occasion variance when `p_0` is free, not `e^{2 a s}` of the later total plus `Q_s`, and not later-occasion variance over the lag interval alone when `u > 0`. Trait variance and `addedTIPREDVAR` do not enter `Q_s`. Chapman–Kolmogorov writes `Q_{u+s} = e^{2 a s} Q_u + Q_s`. Setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map. As `u → 0+` the composition approaches later-occasion variance over `s`. As `s → 0+` the composition approaches later-occasion variance at `u`. Nonzero diffusion with `a ≥ 0` is a growing process and is kept. Equation 5 of that later-start later-occasion variance `λ²(trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v) + θ + ψ` is not `θ`, not the later-start later-occasion latent variance, not predetermined later observed variance, not later-start lagged observed covariance, and not stationary later-occasion observed variance when `p_0` is free. Page 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4: standardise `DRIFT` using only within-subject variance, not the total). Unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`. The §7.1 trait-plus-state autocorrelation `(trait + e^{a Δt} p + added) / (trait + p + added)` uses `TRAITVAR` and is not `discreteDRIFTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4: standardise process noise using only within-subject variance, not the total). Unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`. The continuous standardisation `q / (−q / (2 a)) = −2 a` is not `discreteDIFFUSIONstd`. `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`. `TRAITVAR` is not the standardisation variance. Page 16 `DIFFUSIONstd` is `q / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4: standardise process noise using only within-subject variance, not the total). Unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`. The discrete standardisation `Q_Δt / (−q / (2 a)) = 1 − exp(2 a Δt)` depends on the event interval and is not `DIFFUSIONstd`. `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`. `TRAITVAR` is not the standardisation variance. Page 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4: standardise `DRIFT` using only within-subject variance, not the total). Unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`. The discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`. `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (footnote 4: standardise using only the relevant variance, not the total; the affecting variance is `TIPREDVAR`; the affected variance is `asymDIFFUSION`). Unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`. The finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`. `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (footnote 4: standardise using only the relevant variance, not the total; the affecting variance is `TIPREDVAR`; the affected variance is `asymDIFFUSION`). Unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`. The asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`. The finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`. `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Table 3 / p. 16 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (footnote 4: standardise using only the relevant variance, not the total; the affecting variance is `TIPREDVAR`; the affected variance is free `T0VAR`, not `asymDIFFUSION`). Unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`. `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` uses `asymDIFFUSION` and is not `T0TIPREDEFFECTstd`. `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`. `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. 2017-era `addedT0TIPREDVAR` is `t0_b² v` (`T0TIPREDEFFECT %*% TIPREDVAR %*% t(T0TIPREDEFFECT)` immediately after `T0TIPREDEFFECTstd`). A zero coefficient or zero predictor variance is exactly zero. Free `T0TIPREDEFFECT` does not require `a < 0`. `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map. `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance. Free `T0VAR` is not this extra TI variance. `TRAITVAR` is not this extra TI variance. Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v`. Form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`. A zero loading or zero extra is exactly zero. `t0_b² v` is the latent extra, not the observed extra. `λ² p_0 + θ` is first-occasion observed variance, not this extra. `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra. `MANIFESTVAR` `θ` is not this extra. Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v`. Form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`. A zero loading or zero extra is exactly zero. Lasting asymptotic extra requires `a < 0`. `(B / a)² v` is the latent extra, not the observed extra. `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra. `λ² p + θ` is stationary observed variance, not this extra. `MANIFESTVAR` `θ` is not this extra. Page 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive time-dependent predictor variance `v` (footnote 4: standardise using only the relevant variance, not the total; the affecting variance is the TD predictor; the affected variance is `asymDIFFUSION`). Unstandardised `M` is defined for a zero coefficient and for zero predictor variance and is not `TDPREDEFFECTstd`. `TIPREDEFFECTstd` `B · √v / √p` is a different named matrix even when `M = B`. The finite-interval intercept-style standardisation `A^{-1}[e^{A Δt} − I] M · √v / √p` depends on the event interval and is not `TDPREDEFFECTstd`. `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive time-dependent predictor variance `v` (footnote 4: standardise using only the relevant variance, not the total; the affecting variance is the TD predictor, not `TIPREDVAR`; the affected variance is free `T0VAR`, not `asymDIFFUSION`). Unstandardised `t0_m` is defined for a zero coefficient and for zero predictor variance and is not `T0TDPREDEFFECTstd`. `TDPREDEFFECTstd` `m · √v / √(-q / (2 a))` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`. `T0TIPREDEFFECTstd` `t0_b · √v / √p_0` is a different named matrix even when `t0_m = t0_b`. `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Free `T0VAR` does not require `a < 0`. Page 16 `T0VARstd` is the correlation form `solve(sqrt(diag(T0VAR))) %&% T0VAR` after strictly positive free `T0VAR` `p_0` (2017-era `summary.ctsemFit.R`; OpenMx `%&%` is `t(A) %*% B %*% A`; the default ridge is 0; the scalar map is `p_0 / p_0 = 1`). Unstandardised `T0VAR` is defined for a zero first-occasion variance and is not `T0VARstd`. Zero `p_0` fails closed. Distinct positive `p_0` recover the same 1. `T0TDPREDEFFECTstd` `t0_m · √v / √p_0` depends on `p_0` and is not `T0VARstd`. `addedT0TIPREDVAR` `t0_b² v` is extra TI variance, not this correlation. `TRAITVAR` is not the standardisation variance. Page 16 `TRAITVARstd` is the correlation form `solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR` after strictly positive `TRAITVAR` (2017-era `summary.ctsemFit.R` forms it only when `TRAITVAR != 0`; OpenMx `%&%` is `t(A) %*% B %*% A`; unlike `T0VARstd` there is no ridge addend; the scalar map is `trait / trait = 1`). Unstandardised `TRAITVAR` is defined for a zero trait and is not `TRAITVARstd`. Zero `TRAITVAR` fails closed. Distinct positive `trait` recover the same 1. `T0VARstd` `p_0 / p_0 = 1` recovers the same number and remains a distinct named quantity. `addedT0TIPREDVAR` `t0_b² v` is extra TI variance, not this correlation. `TRAITVAR` does not require `a < 0`. Page 16 `MANIFESTTRAITVARstd` is the correlation form `solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR` after strictly positive `MANIFESTTRAITVAR` (2017-era `summary.ctsemFit.R` forms it only when `MANIFESTTRAITVAR != 0`; OpenMx `%&%` is `t(A) %*% B %*% A`; unlike `TRAITVARstd` the 2017-era source adds ridging; the default ridge is 0; the scalar map is `ψ / ψ = 1`). Unstandardised `MANIFESTTRAITVAR` is defined for a zero trait and is not `MANIFESTTRAITVARstd`. Zero `MANIFESTTRAITVAR` fails closed. Distinct positive `ψ` recover the same 1. `TRAITVARstd` `trait / trait = 1` recovers the same number and remains a distinct named quantity. `MANIFESTVAR` `θ` is measurement error, not this correlation. `MANIFESTTRAITVAR` does not require `a < 0`. Page 16 `MANIFESTVARstd` is the correlation form `solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR` after strictly positive `MANIFESTVAR` (2017-era `summary.ctsemFit.R` forms it whenever `verbose = TRUE`; OpenMx `%&%` is `t(A) %*% B %*% A`; unlike `TRAITVARstd` the 2017-era source adds ridging; the default ridge is 0; the 2017-era `dimnames` assignment to `latentNames` is a source bug; the scalar map is `θ / θ = 1`). Unstandardised `MANIFESTVAR` is defined for a zero residual and is not `MANIFESTVARstd`. Zero `MANIFESTVAR` makes `solve(sqrt(0))` fail and fails closed. Distinct positive `θ` recover the same 1. `MANIFESTTRAITVARstd` `ψ / ψ = 1` recovers the same number and remains a distinct named quantity. Equation 5 `λ² Var(η) + θ` is `Var(y)`, not this correlation. `MANIFESTVAR` does not require `a < 0`. Page 16 `TIPREDVARstd` is the correlation form `solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR` after strictly positive `TIPREDVAR` (2017-era `summary.ctsemFit.R` forms it whenever `verbose = TRUE` and `n.TIpred > 0`; OpenMx `%&%` is `t(A) %*% B %*% A`; unlike `TRAITVARstd` the 2017-era source adds ridging; the default ridge is 0; `dimnames` are `TIpredNames`; the scalar map is `v / v = 1`). Unstandardised `TIPREDVAR` is defined for a zero predictor and is not `TIPREDVARstd`. Zero `TIPREDVAR` makes `solve(sqrt(0))` fail and fails closed. Distinct positive `v` recover the same 1. `MANIFESTVARstd` `θ / θ = 1` recovers the same number and remains a distinct named quantity. Section 7.2 `addedTIPREDVAR` `(B / a)² v` is extra process variance, not this correlation. `TIPREDVAR` does not require `a < 0`. Page 16 `asymDIFFUSIONstd` is the correlation form `solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION` after strictly positive `asymDIFFUSION` `-q / (2 a)` (2017-era `summary.ctsemFit.R` forms it whenever `verbose = TRUE`; OpenMx `%&%` is `t(A) %*% B %*% A`; the 2017-era source adds ridging; the default ridge is 0; `dimnames` are `latentNames`; the scalar map is `p / p = 1`). Unstandardised `asymDIFFUSION` is defined for a zero process and is not `asymDIFFUSIONstd`. Zero `q` makes `solve(sqrt(0))` fail and fails closed. Distinct positive `p` recover the same 1. `TIPREDVARstd` `v / v = 1` recovers the same number and remains a distinct named quantity. `DIFFUSIONstd` `q / p = −2 a` is the continuous-diffusion ratio, not this correlation. Lasting `asymDIFFUSION` requires `a < 0`. Page 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION` `-q / (2 a)` (2017-era `summary.ctsemFit.R` forms unstandardised `discreteCINT` whenever `verbose = TRUE` as `solve(DRIFT) %*% (discreteDRIFT − I) %*% CINT`; that source does not form a `discreteCINTstd` matrix; the scalar map is the footnote 4 standardisation of that named discrete intercept). Unstandardised `discreteCINT` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteCINTstd`. Zero `q` has no positive process SD and fails closed. `κ / √p` does not depend on `Δt` and is not this finite-interval map. `(-κ / a) / √p` is the standardised asymptotic intercept and is not this map. Lasting `asymDIFFUSION` requires `a < 0`. Page 16 `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION` `-q / (2 a)` (2017-era `summary.ctsemFit.R` forms unstandardised `asymCINT` whenever `verbose = TRUE` as `-solve(DRIFT) %*% CINT`; that source does not form an `asymCINTstd` matrix; the scalar map is the footnote 4 standardisation of that named asymptotic intercept). Unstandardised `asymCINT` is defined for a zero process and is not `asymCINTstd`. Zero `q` has no positive process SD and fails closed. `κ / √p` is the continuous intercept standardisation and is not this total-change map. `A^{-1}[e^{A Δt} − I] κ / √p` depends on the event interval and is not this `Δt → ∞` map. Lasting `asymDIFFUSION` requires `a < 0`. Page 16 `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR` `p_0` (2017-era `summary.ctsemFit.R` forms unstandardised `T0MEANS` and does not form a `T0MEANSstd` matrix; the scalar map is the footnote 4 standardisation of that named first-occasion mean; relevant variance is free `T0VAR`, not `asymDIFFUSION`). Unstandardised `T0MEANS` is defined for a zero first-occasion variance and is not `T0MEANSstd`. Zero `p_0` has no positive SD and fails closed. `T0VARstd` `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. Free `T0MEANS` does not require `a < 0`. Page 16 `MANIFESTMEANSstd` is `τ / √θ` after strictly positive `MANIFESTVAR` `θ` (2017-era `summary.ctsemFit.R` forms unstandardised `MANIFESTMEANS` and does not form a `MANIFESTMEANSstd` matrix; the scalar map is the footnote 4 standardisation of that named measurement intercept; relevant variance is residual `MANIFESTVAR`, not total observed `Var(y)`). Unstandardised `MANIFESTMEANS` is defined for a zero residual and is not `MANIFESTMEANSstd`. Zero `θ` has no positive SD and fails closed. `MANIFESTVARstd` `θ / θ = 1` recovers the same number when `τ = √θ` and remains a distinct named quantity. `τ / √(λ² Var(η) + θ)` uses total observed variance and is not this residual map. `MANIFESTMEANS` does not require `a < 0`. +This is two-level OLS and a noiseless scalar continuous-time map. It is not DSEM, not RI-CLPM, not a random-effects sampler, not a Kalman filter, and not a matrix `expm` implementation. The CWC cluster-mean coefficient is the **contextual** effect, not the between-cluster effect. Discrete lags from different event intervals are not one coefficient. Equation 14 is not Equation 12. Discrete process noise \(Q_{\Delta t}\) is not the continuous diffusion \(GG^{\top}\). \(Q_{\Delta t}\) is the conditional residual variance, not \(\operatorname{Var}(\eta_{t})\). Finite-interval \(Q_{\Delta t}\) is not the stationary within-subject variance. Trait variance is not process noise and not the stationary within-subject variance. The §7.1 trait-plus-state expected autocorrelation `(trait + e^{a Δt} p + added) / (trait + p + added)` is not unstandardised `discreteDRIFT` `e^{a Δt}` and is not p. 16 `discreteDRIFTstd`. Measurement-error variance is not the observed-indicator variance. Latent variance is not the observed-indicator variance. Manifest means are not the observed-indicator mean. The latent mean is not the observed-indicator mean. The continuous intercept is not the manifest mean. The first-occasion latent mean is not the evolved latent mean. The continuous intercept is not the discrete mean increment. The first-occasion observed mean is not the evolved observed mean. The contemporaneous `TDPREDEFFECT` impulse is not the continuous intercept, not the time-independent discrete effect, and not Voelkle et al. (2012, Eq. 14). The time-independent `TIPREDEFFECT` increment is not the continuous intercept, not the contemporaneous impulse, not Voelkle et al. (2012, Eq. 14), and not the coefficient `B`. The within-interval `TDPREDEFFECT` carry `e^{A(t−u)} M x` for `t0 < u < t` is not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle et al. (2012, Eq. 14). The evolved observed mean `τ + λ μ_t` is not the contemporaneous-impulse observed mean `τ + λ(μ_t + m x)`. The contemporaneous composition `τ + λ(μ_t + m x)` is not the impulse-carry observed mean `τ + λ(μ_t + e^{a(t−u)} m x)` when `u ≠ t`. The evolved observed mean `τ + λ μ_t` is not the impulse-carry observed mean. The carried latent mean is not `E(y_t)`. The evolved-plus-impulse latent mean is not `E(y_t)`. The evolved observed mean `τ + λ μ_t` is not the time-independent-predictor observed mean `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)`. The contemporaneous composition `τ + λ(μ_t + m x)` is not that time-independent-predictor observed mean. The impulse-carry composition `τ + λ(μ_t + e^{a(t−u)} m x)` is not that time-independent-predictor observed mean when `u ≠ t`. The evolved-plus-increment latent mean is not `E(y_t)`. The Table 3 first-occasion `T0TIPREDEFFECT` shift `t0_b z` is not the Eq. 3 process increment `A^{-1}[e^{A Δt} − I] B z`, not `κ`, and not `M x`. The Eq. 3 first-summand carry `e^{A Δt} t0_b z` is not `t0_b z` and is not that process increment. `T0TIPREDEFFECT` is the coefficient, not the first-occasion shift. The evolved observed mean `τ + λ μ_t` is not the first-occasion TI-predictor observed mean `τ + λ(μ_t + e^{a Δt} t0_b z)`. The process-increment composition `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that first-occasion observed mean. The contemporaneous composition `τ + λ(μ_t + m x)` is not that first-occasion observed mean. The impulse-carry composition `τ + λ(μ_t + e^{a(t−u)} m x)` is not that first-occasion observed mean when `u ≠ t0`. The evolved-plus-T0TIPRED latent mean is not `E(y_t)`. The Table 3 first-occasion `T0TDPREDEFFECT` shift `t0_m x0` is not `M x`, not `e^{A(t−u)} M x` for `t0 < u < t`, not `t0_b z`, not `A^{-1}[e^{A Δt} − I] B z`, and not `κ`. The Eq. 3 first-summand carry `e^{A Δt} t0_m x0` is not `t0_m x0` and is not that within-interval impulse carry. `T0TDPREDEFFECT` is the coefficient, not the first-occasion shift. An impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`. The evolved observed mean `τ + λ μ_t` is not the first-occasion TD-predictor observed mean `τ + λ(μ_t + e^{a Δt} t0_m x0)`. The process-increment composition `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that first-occasion TD observed mean. The contemporaneous composition `τ + λ(μ_t + m x)` is not that first-occasion TD observed mean. The impulse-carry composition `τ + λ(μ_t + e^{a(t−u)} m x)` is not that first-occasion TD observed mean when `u ≠ t0`. The first-occasion TI composition `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that first-occasion TD observed mean. Same numbers as `T0TIPREDEFFECT` yield the same product; Table 3 names a different matrix. The evolved-plus-T0TDPRED latent mean is not `E(y_t)`. The §7.2 level-change `CINT` `κ = −a m x` is not the dissipating Dirac `m x`, not a free `CINT`, and not `A^{-1}[e^{A Δt} − I] B z`. Lasting level change via that `CINT` setting requires `a < 0`. The extra near-zero-drift latent process also named in §7.2 is a different specification and is not that `CINT` setting. The Eq. 3 increment of that setting `(1 − e^{a Δt}) m x` is not the dissipating Dirac `m x`, not `κ`, and not `A^{-1}[e^{A Δt} − I] B z`. Underflow of `e^{a Δt}` to `+0` keeps the equilibrium offset `m x`. The §7.2 extra-process contribution `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` is not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`. Lasting level change via that extra process requires `ε < 0`. Precisely `ε = 0` causes computational problems in the printed specification. The extra-process observed mean `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a))` is not `τ + λ μ_t`, not `τ + λ(μ_t + m x)`, not the contribution, and not the evolved-plus-contribution latent mean. The extra process has `LAMBDA` 0 and is not an observed indicator. `T0TDPREDEFFECT` on the extra process uses `Δt = t − t0` for both the original-process evolution and the extra drive. `TDPREDEFFECT` after `t0` uses `t − u` with `t0 < u < t` for the extra drive while `μ_t` still uses `Δt`. The after-t0 extra-process observed mean is not the first-occasion extra-process observed mean when `u ≠ t0`. The impulse-carry `e^{a(t−u)} m x` is a Dirac on the original process and is not that `DRIFT` drive. The §7.2 `asymTIPREDEFFECT` `-B z / a` is the expected total change in process means given a time-independent predictor. It is not the coefficient `B`, not the finite-interval increment `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`. Lasting asymptotic change via that map requires `a < 0`. The §7.2 `addedTIPREDVAR` `(B / a)² v` is the stable between-subject variance accounted for by that predictor. It is not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`. Table 2 `asymCINT` `-κ / a` is the intercept contribution to the stationary process mean. It is not `κ`, not the finite-interval increment `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`. Lasting asymptotic intercept change via that map requires `a < 0`. Page 16 notes that a `T0MEANS` stationarity constraint includes time-independent predictors; that composition is not this intercept-only map. The p. 16 constrained first-occasion mean `-κ / a + −B z / a` is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean. Equation 5 of that constrained mean `τ + λ(−κ / a + −B z / a)` is not `τ + λ μ_0`, not `τ + λ(−κ / a)` when `B z ≠ 0`, not `τ + λ μ_t`, not `MANIFESTMEANS`, and not the constrained latent mean. The p. 16 constrained first-occasion variance `trait + −q / (2 a) + (B / a)² v` is not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Equation 5 of that constrained variance `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` is not `λ² p_0 + θ`, not `λ²(−q / (2 a)) + θ` when `TRAITVAR` or `addedTIPREDVAR` is nonzero, not `λ² Var(η_t) + θ` when the first occasion is constrained, not `MANIFESTVAR`, and not the constrained latent variance. The lagged covariance of that constrained process `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` is not contemporaneous `T0VAR`, not `e^{a Δt}` of the constrained total, and not `trait + e^{a Δt} p` when `addedTIPREDVAR` is nonzero. Trait variance and `addedTIPREDVAR` do not decay with `e^{a Δt}`. Equation 5 of that lagged covariance `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ` is not `θ`, not contemporaneous `Var(y_0)`, and not the lagged latent covariance. Independent measurement error does not enter lagged observed covariance. The later-occasion variance of that constrained process `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` equals contemporaneous `T0VAR` under stationarity and is not the lagged covariance, not `e^{2 a Δt}` of the constrained total plus `Q_Δt`, and not `Q_Δt` alone. Trait variance and `addedTIPREDVAR` do not enter `Q_Δt`. Equation 5 of that later-occasion variance `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ` is not `θ`, not lagged `cov(y_t, y_{t-1})`, and not the later-occasion latent variance. The later-occasion variance of §4.3 predetermined `T0VAR` `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` is not stationary later-occasion variance when `p_0` is free, not `e^{2 a Δt}` of `trait + p_0 + (B / a)² v` plus `Q_Δt`, and not free `p_0`. Trait variance and `addedTIPREDVAR` do not enter `Q_Δt`. Setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map. As `Δt → ∞` with stable `a < 0` the composition approaches contemporaneous stationary `T0VAR`. Nonzero diffusion with `a ≥ 0` is a growing process and is kept. Equation 5 of that predetermined later-occasion variance `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ` is not `θ`, not the predetermined later-occasion latent variance, and not stationary later-occasion observed variance when `p_0` is free. The lagged covariance of §4.3 predetermined `T0VAR` `trait + e^{a Δt} p_0 + (B / a)² v` is not stationary lagged covariance when `p_0` is free, not later-occasion variance, not `e^{a Δt}` of `trait + p_0 + (B / a)² v`, and not free `p_0`. Trait variance and `addedTIPREDVAR` do not decay with `e^{a Δt}`. Setting `p_0 = −q / (2 a)` recovers the stationary lagged map. As `Δt → ∞` with stable `a < 0` the state term vanishes. A zero-diffusion carry with `a ≥ 0` is `e^{a Δt} p_0` and is kept. Equation 5 of that predetermined lagged covariance `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ` is not `θ`, not the predetermined lagged latent covariance, not predetermined later observed variance, and not stationary lagged observed covariance when `p_0` is free. Independent measurement error does not enter lagged observed covariance. The first-occasion variance of §4.3 predetermined `T0VAR` `trait + p_0 + (B / a)² v` is not stationary first-occasion variance when `p_0` is free, not free `p_0`, not lagged covariance, and not later-occasion variance. Trait variance and `addedTIPREDVAR` do not decay and do not enter `Q_Δt`. Setting `p_0 = −q / (2 a)` recovers the stationary first-occasion map. As `Δt → 0+` the lagged and later maps approach this composition. Trait-only variance does not require a stable drift. Equation 5 of that predetermined first-occasion variance `λ²(trait + p_0 + (B / a)² v) + θ + ψ` is not `θ`, not the predetermined first-occasion latent variance, not stationary first-occasion observed variance, and not predetermined later observed variance when `p_0` is free. The later-start lagged covariance of §4.3 predetermined `T0VAR` `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` is not first-occasion lagged covariance when `u > 0`, not later-occasion variance, not stationary lagged covariance when `p_0` is free, and not `e^{a s}` of the later total. Trait variance and `addedTIPREDVAR` do not decay with `e^{a s}`. Setting `p_0 = −q / (2 a)` recovers the stationary lagged map. As `u → 0+` the composition approaches first-occasion lagged covariance. As `s → 0+` the composition approaches later-occasion variance at `u`. Nonzero diffusion with `a ≥ 0` is a growing process and is kept. Equation 5 of that later-start lagged covariance `λ²(trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v) + ψ` is not `θ`, not the later-start lagged latent covariance, not first-occasion lagged observed covariance, not predetermined later observed variance, and not stationary lagged observed covariance when `p_0` is free. Independent measurement error does not enter lagged observed covariance. The later-start later-occasion variance of §4.3 predetermined `T0VAR` `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` is not later-occasion variance at `u` when `s > 0`, not later-start lagged covariance, not stationary later-occasion variance when `p_0` is free, not `e^{2 a s}` of the later total plus `Q_s`, and not later-occasion variance over the lag interval alone when `u > 0`. Trait variance and `addedTIPREDVAR` do not enter `Q_s`. Chapman–Kolmogorov writes `Q_{u+s} = e^{2 a s} Q_u + Q_s`. Setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map. As `u → 0+` the composition approaches later-occasion variance over `s`. As `s → 0+` the composition approaches later-occasion variance at `u`. Nonzero diffusion with `a ≥ 0` is a growing process and is kept. Equation 5 of that later-start later-occasion variance `λ²(trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v) + θ + ψ` is not `θ`, not the later-start later-occasion latent variance, not predetermined later observed variance, not later-start lagged observed covariance, and not stationary later-occasion observed variance when `p_0` is free. Page 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4: standardise `DRIFT` using only within-subject variance, not the total). Unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`. The §7.1 trait-plus-state autocorrelation `(trait + e^{a Δt} p + added) / (trait + p + added)` uses `TRAITVAR` and is not `discreteDRIFTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4: standardise process noise using only within-subject variance, not the total). Unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`. The continuous standardisation `q / (−q / (2 a)) = −2 a` is not `discreteDIFFUSIONstd`. `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`. `TRAITVAR` is not the standardisation variance. Page 16 `DIFFUSIONstd` is `q / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4: standardise process noise using only within-subject variance, not the total). Unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`. The discrete standardisation `Q_Δt / (−q / (2 a)) = 1 − exp(2 a Δt)` depends on the event interval and is not `DIFFUSIONstd`. `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`. `TRAITVAR` is not the standardisation variance. Page 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4: standardise `DRIFT` using only within-subject variance, not the total). Unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`. The discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`. `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (footnote 4: standardise using only the relevant variance, not the total; the affecting variance is `TIPREDVAR`; the affected variance is `asymDIFFUSION`). Unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`. The finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`. `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (footnote 4: standardise using only the relevant variance, not the total; the affecting variance is `TIPREDVAR`; the affected variance is `asymDIFFUSION`). Unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`. The asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`. The finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`. `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Table 3 / p. 16 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (footnote 4: standardise using only the relevant variance, not the total; the affecting variance is `TIPREDVAR`; the affected variance is free `T0VAR`, not `asymDIFFUSION`). Unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`. `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` uses `asymDIFFUSION` and is not `T0TIPREDEFFECTstd`. `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`. `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. 2017-era `addedT0TIPREDVAR` is `t0_b² v` (`T0TIPREDEFFECT %*% TIPREDVAR %*% t(T0TIPREDEFFECT)` immediately after `T0TIPREDEFFECTstd`). A zero coefficient or zero predictor variance is exactly zero. Free `T0TIPREDEFFECT` does not require `a < 0`. `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map. `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance. Free `T0VAR` is not this extra TI variance. `TRAITVAR` is not this extra TI variance. Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v`. Form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`. A zero loading or zero extra is exactly zero. `t0_b² v` is the latent extra, not the observed extra. `λ² p_0 + θ` is first-occasion observed variance, not this extra. `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra. `MANIFESTVAR` `θ` is not this extra. Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v`. Form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`. A zero loading or zero extra is exactly zero. Lasting asymptotic extra requires `a < 0`. `(B / a)² v` is the latent extra, not the observed extra. `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra. `λ² p + θ` is stationary observed variance, not this extra. `MANIFESTVAR` `θ` is not this extra. Page 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive time-dependent predictor variance `v` (footnote 4: standardise using only the relevant variance, not the total; the affecting variance is the TD predictor; the affected variance is `asymDIFFUSION`). Unstandardised `M` is defined for a zero coefficient and for zero predictor variance and is not `TDPREDEFFECTstd`. `TIPREDEFFECTstd` `B · √v / √p` is a different named matrix even when `M = B`. The finite-interval intercept-style standardisation `A^{-1}[e^{A Δt} − I] M · √v / √p` depends on the event interval and is not `TDPREDEFFECTstd`. `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive time-dependent predictor variance `v` (footnote 4: standardise using only the relevant variance, not the total; the affecting variance is the TD predictor, not `TIPREDVAR`; the affected variance is free `T0VAR`, not `asymDIFFUSION`). Unstandardised `t0_m` is defined for a zero coefficient and for zero predictor variance and is not `T0TDPREDEFFECTstd`. `TDPREDEFFECTstd` `m · √v / √(-q / (2 a))` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`. `T0TIPREDEFFECTstd` `t0_b · √v / √p_0` is a different named matrix even when `t0_m = t0_b`. `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Free `T0VAR` does not require `a < 0`. Page 16 `T0VARstd` is the correlation form `solve(sqrt(diag(T0VAR))) %&% T0VAR` after strictly positive free `T0VAR` `p_0` (2017-era `summary.ctsemFit.R`; OpenMx `%&%` is `t(A) %*% B %*% A`; the default ridge is 0; the scalar map is `p_0 / p_0 = 1`). Unstandardised `T0VAR` is defined for a zero first-occasion variance and is not `T0VARstd`. Zero `p_0` fails closed. Distinct positive `p_0` recover the same 1. `T0TDPREDEFFECTstd` `t0_m · √v / √p_0` depends on `p_0` and is not `T0VARstd`. `addedT0TIPREDVAR` `t0_b² v` is extra TI variance, not this correlation. `TRAITVAR` is not the standardisation variance. Page 16 `TRAITVARstd` is the correlation form `solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR` after strictly positive `TRAITVAR` (2017-era `summary.ctsemFit.R` forms it only when `TRAITVAR != 0`; OpenMx `%&%` is `t(A) %*% B %*% A`; unlike `T0VARstd` there is no ridge addend; the scalar map is `trait / trait = 1`). Unstandardised `TRAITVAR` is defined for a zero trait and is not `TRAITVARstd`. Zero `TRAITVAR` fails closed. Distinct positive `trait` recover the same 1. `T0VARstd` `p_0 / p_0 = 1` recovers the same number and remains a distinct named quantity. `addedT0TIPREDVAR` `t0_b² v` is extra TI variance, not this correlation. `TRAITVAR` does not require `a < 0`. Page 16 `MANIFESTTRAITVARstd` is the correlation form `solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR` after strictly positive `MANIFESTTRAITVAR` (2017-era `summary.ctsemFit.R` forms it only when `MANIFESTTRAITVAR != 0`; OpenMx `%&%` is `t(A) %*% B %*% A`; unlike `TRAITVARstd` the 2017-era source adds ridging; the default ridge is 0; the scalar map is `ψ / ψ = 1`). Unstandardised `MANIFESTTRAITVAR` is defined for a zero trait and is not `MANIFESTTRAITVARstd`. Zero `MANIFESTTRAITVAR` fails closed. Distinct positive `ψ` recover the same 1. `TRAITVARstd` `trait / trait = 1` recovers the same number and remains a distinct named quantity. `MANIFESTVAR` `θ` is measurement error, not this correlation. `MANIFESTTRAITVAR` does not require `a < 0`. Page 16 `MANIFESTVARstd` is the correlation form `solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR` after strictly positive `MANIFESTVAR` (2017-era `summary.ctsemFit.R` forms it whenever `verbose = TRUE`; OpenMx `%&%` is `t(A) %*% B %*% A`; unlike `TRAITVARstd` the 2017-era source adds ridging; the default ridge is 0; the 2017-era `dimnames` assignment to `latentNames` is a source bug; the scalar map is `θ / θ = 1`). Unstandardised `MANIFESTVAR` is defined for a zero residual and is not `MANIFESTVARstd`. Zero `MANIFESTVAR` makes `solve(sqrt(0))` fail and fails closed. Distinct positive `θ` recover the same 1. `MANIFESTTRAITVARstd` `ψ / ψ = 1` recovers the same number and remains a distinct named quantity. Equation 5 `λ² Var(η) + θ` is `Var(y)`, not this correlation. `MANIFESTVAR` does not require `a < 0`. Page 16 `TIPREDVARstd` is the correlation form `solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR` after strictly positive `TIPREDVAR` (2017-era `summary.ctsemFit.R` forms it whenever `verbose = TRUE` and `n.TIpred > 0`; OpenMx `%&%` is `t(A) %*% B %*% A`; unlike `TRAITVARstd` the 2017-era source adds ridging; the default ridge is 0; `dimnames` are `TIpredNames`; the scalar map is `v / v = 1`). Unstandardised `TIPREDVAR` is defined for a zero predictor and is not `TIPREDVARstd`. Zero `TIPREDVAR` makes `solve(sqrt(0))` fail and fails closed. Distinct positive `v` recover the same 1. `MANIFESTVARstd` `θ / θ = 1` recovers the same number and remains a distinct named quantity. Section 7.2 `addedTIPREDVAR` `(B / a)² v` is extra process variance, not this correlation. `TIPREDVAR` does not require `a < 0`. Page 16 `asymDIFFUSIONstd` is the correlation form `solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION` after strictly positive `asymDIFFUSION` `-q / (2 a)` (2017-era `summary.ctsemFit.R` forms it whenever `verbose = TRUE`; OpenMx `%&%` is `t(A) %*% B %*% A`; the 2017-era source adds ridging; the default ridge is 0; `dimnames` are `latentNames`; the scalar map is `p / p = 1`). Unstandardised `asymDIFFUSION` is defined for a zero process and is not `asymDIFFUSIONstd`. Zero `q` makes `solve(sqrt(0))` fail and fails closed. Distinct positive `p` recover the same 1. `TIPREDVARstd` `v / v = 1` recovers the same number and remains a distinct named quantity. `DIFFUSIONstd` `q / p = −2 a` is the continuous-diffusion ratio, not this correlation. Lasting `asymDIFFUSION` requires `a < 0`. Page 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION` `-q / (2 a)` (2017-era `summary.ctsemFit.R` forms unstandardised `discreteCINT` whenever `verbose = TRUE` as `solve(DRIFT) %*% (discreteDRIFT − I) %*% CINT`; that source does not form a `discreteCINTstd` matrix; the scalar map is the footnote 4 standardisation of that named discrete intercept). Unstandardised `discreteCINT` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteCINTstd`. Zero `q` has no positive process SD and fails closed. `κ / √p` does not depend on `Δt` and is not this finite-interval map. `(-κ / a) / √p` is the standardised asymptotic intercept and is not this map. Lasting `asymDIFFUSION` requires `a < 0`. Page 16 `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION` `-q / (2 a)` (2017-era `summary.ctsemFit.R` forms unstandardised `asymCINT` whenever `verbose = TRUE` as `-solve(DRIFT) %*% CINT`; that source does not form an `asymCINTstd` matrix; the scalar map is the footnote 4 standardisation of that named asymptotic intercept). Unstandardised `asymCINT` is defined for a zero process and is not `asymCINTstd`. Zero `q` has no positive process SD and fails closed. `κ / √p` is the continuous intercept standardisation and is not this total-change map. `A^{-1}[e^{A Δt} − I] κ / √p` depends on the event interval and is not this `Δt → ∞` map. Lasting `asymDIFFUSION` requires `a < 0`. Page 16 `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR` `p_0` (2017-era `summary.ctsemFit.R` forms unstandardised `T0MEANS` and does not form a `T0MEANSstd` matrix; the scalar map is the footnote 4 standardisation of that named first-occasion mean; relevant variance is free `T0VAR`, not `asymDIFFUSION`). Unstandardised `T0MEANS` is defined for a zero first-occasion variance and is not `T0MEANSstd`. Zero `p_0` has no positive SD and fails closed. `T0VARstd` `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. Free `T0MEANS` does not require `a < 0`. Page 16 `MANIFESTMEANSstd` is `τ / √θ` after strictly positive `MANIFESTVAR` `θ` (2017-era `summary.ctsemFit.R` forms unstandardised `MANIFESTMEANS` and does not form a `MANIFESTMEANSstd` matrix; the scalar map is the footnote 4 standardisation of that named measurement intercept; relevant variance is residual `MANIFESTVAR`, not total observed `Var(y)`). Unstandardised `MANIFESTMEANS` is defined for a zero residual and is not `MANIFESTMEANSstd`. Zero `θ` has no positive SD and fails closed. `MANIFESTVARstd` `θ / θ = 1` recovers the same number when `τ = √θ` and remains a distinct named quantity. `τ / √(λ² Var(η) + θ)` uses total observed variance and is not this residual map. `MANIFESTMEANS` does not require `a < 0`. ## Authoritative sources @@ -146,6 +148,7 @@ The Voelkle et al. (2012) ZORA accepted manuscript was re-opened 2026-08-18T21:0 - **Discrete latent variance.** Equations 3–4 write \(Q_{\Delta t}\) as the covariance of the stochastic integral, so \(Q_{\Delta t}=\operatorname{cov}(\eta_{ti}\mid\eta_{t-1,i})\) when \(\xi\) and \(z\) are given. The law of total variance on that pair is \(\operatorname{Var}(\eta_{ti})=A_{\Delta t}\operatorname{Var}(\eta_{t-1,i})A_{\Delta t}^{\top}+Q_{\Delta t}\). The scalar map is \(\mathrm{e}^{2a\Delta t}p+Q_{\Delta t}\). This is not a Kalman measurement update. A zero prior variance is exactly \(Q_{\Delta t}\). Binary64 underflow of \(\mathrm{e}^{2a\Delta t}\) keeps \(Q_{\Delta t}\). Finite-\(z\) exponential overflow rewrites as \(\exp(\ln p+z)+Q_{\Delta t}\). A zero diffusion is exactly \(Q_{\Delta t}=0\); that skip does not license a non-finite carried term when \(2(a\Delta t)\) overflows to \(+\infty\). Treating \(Q_{\Delta t}\) as \(\operatorname{Var}(\eta_{t})\) fails closed. - **Stationary within-subject variance.** Driver et al. (2017, Eq. 4 as \(\Delta t\to\infty\); §4.3 pp. 9–10; p. 16 `asymDIFFUSION`; JSS PDF re-opened 2026-08-19T04:10Z, p. 5): for stable \(a<0\), \(\lim_{\Delta t\to\infty}Q_{\Delta t}=-q/(2a)\). When \(2a\) is finite, form \(q/-(2a)\) so \(q/a\) overflow does not lose a finite Lyapunov solution (`q=MAX`, `a=-0.75` → `MAX/1.5`; CodeRabbit on `75ecdd3`). When \(2a\) overflows, form \((q/a)\times-0.5\). Forming \(2a\) as the only path overflows when \(|a|\) is at the binary64 extreme (`a=-1e308`, `q=1e308` → `0.5`). Forming \(0.5q\) first underflows at the minimum subnormal (`q=from_bits(1)`, `a=-from_bits(1)` → naive `+0`; representable solution `0.5`). Starting from that variance, \(\operatorname{Var}(\eta_{t})\) is invariant across finite event intervals. A zero diffusion is exactly zero. \(a\ge 0\) has no finite stationary variance (Brownian \(a=0\) grows as \(q\Delta t\)). An overflowing Lyapunov solution fails closed. Finite-interval \(Q_{\Delta t}\) is not that limit. This is not ctsem estimation and not a Kalman filter. - **Trait-plus-state variance.** Driver et al. (2017, §4.3, p. 9; JSS PDF re-opened 2026-08-18T21:07Z): a stable trait process has `DRIFT` and `DIFFUSION` fixed to zero. The scalar maps are \(\operatorname{Var}=\mathrm{trait}+\mathrm{state}\) and \(\operatorname{cov}(t,t-1)=\mathrm{trait}+\mathrm{e}^{a\Delta t}p\). The ctsem `TRAITVAR` rewrite that adds the trait to `DIFFUSION` does not license treating trait variance as \(Q_{\Delta t}\). Trait variance is not `asymDIFFUSION`. Evolving the summed variance as if it were all state is not this map. A zero trait is exactly the state. A zero state is exactly the trait. An overflowing sum fails closed. This is not RI-CLPM and not ctsem estimation. +- **Trait-plus-state expected autocorrelation.** Driver et al. (2017, §7.1, pp. 18–19; Eq. 3–4; §4.3; §7.2 `addedTIPREDVAR`; JSS PDF re-opened 2026-09-01T06:30Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104): traits are unit-level unobserved heterogeneity. Omitting `TRAITVAR` mixes between- and within-person information; Figure 4 shows auto-effects reduced after traits. The paper does not print this ratio; it is the covariance ratio of the §4.3 maps. Form `trait + e^{a Δt} p` first, then `trait + p`, then add `addedTIPREDVAR`, then \((\mathrm{trait}+e^{a\Delta t}p+\mathrm{added})/(\mathrm{trait}+p+\mathrm{added})\). Zero trait and zero added recovers \(e^{a\Delta t}\) numerically and remains a distinct named quantity. Zero state with positive trait or extra is exactly 1. Zero total variance fails closed. Growing \(a>0\) is a covariance ratio and is kept. Unstandardised `discreteDRIFT` \(e^{a\Delta t}\) is not this map. Page 16 `discreteDRIFTstd` uses only within-subject `asymDIFFUSION` (footnote 4) and is not this map. `TRAITVAR` is not the standardisation variance. The interval must be event time and strictly positive. This is not RI-CLPM, not a Kalman filter, and not ctsem estimation. - **Observed-indicator variance.** Driver et al. (2017, Eq. 5, p. 5; Table 2, p. 12; JSS PDF re-opened 2026-08-19T04:18Z): \(y_i(t)=\tau_i+\Lambda\eta_i(t)+\varepsilon_i(t)\) with \(\varepsilon\sim N(0,\Theta)\) and \(\tau_i\sim N(\mu_{\tau},\Psi_{\tau})\). Equation 1 (p. 4) is the latent SDE. Table 2 names \(\Theta\) `MANIFESTVAR` and \(\Psi_{\tau}\) `MANIFESTTRAITVAR`; p. 16 restates those names. The scalar map is \(\operatorname{Var}(y)=\lambda^{2}\operatorname{Var}(\eta)+\theta\) when \(\Psi_{\tau}=0\) and \(\lambda^{2}\operatorname{Var}(\eta)+\theta+\psi\) otherwise. Form \((\lambda p)\lambda\) then add \(\theta\), then add \(\psi\). Forming \(\lambda^{2}\) first overflows at \(\lambda=10^{308}\), \(p=10^{-308}\). A zero loading or zero latent variance is exactly \(\theta\) (\(\Psi_{\tau}=0\)) or \(\theta+\psi\). A zero measurement error is exactly \(\lambda^{2}p+\psi\). A zero manifest trait is exactly \(\lambda^{2}p+\theta\). `MANIFESTVAR` is not \(\operatorname{Var}(y)\). `MANIFESTTRAITVAR` is not `MANIFESTVAR`. `TRAITVAR` is latent additional variance and is scaled by \(\lambda^{2}\); `MANIFESTTRAITVAR` is not. \(\operatorname{Var}(\eta)\) is not \(\operatorname{Var}(y)\). An overflowing product or sum fails closed. This is not a Kalman filter and not ctsem estimation. - **Lagged observed-indicator covariance.** Driver et al. (2017, Eq. 5 with Eq. 3–4; JSS PDF re-opened 2026-08-19T04:18Z): independent \(\varepsilon_t\) does not enter \(\operatorname{cov}(y_t,y_{t-1})\). The scalar map is \(\lambda^{2}\operatorname{cov}(\eta_t,\eta_{t-1})+\psi\). Form \((\lambda c)\lambda\) then add \(\psi\). A zero loading or zero latent lagged covariance is exactly \(\psi\). A zero manifest trait is exactly \(\lambda^{2}c\). `MANIFESTVAR` is not lagged observed covariance. Lagged latent covariance is not lagged observed covariance. An overflowing product or sum fails closed. - **Observed-indicator mean.** Driver et al. (2017, Eq. 5, p. 5; Table 2, p. 12; JSS PDF re-opened 2026-08-19T14:08Z): \(y_i(t)=\Gamma+\Lambda\eta_i(t)+\zeta_i(t)\) with \(\zeta\sim N(0,\Theta)\) and \(\Gamma\sim N(\tau,\Psi)\). Table 2 names \(\tau\) `MANIFESTMEANS`, \(\kappa\) `CINT`, and the first-occasion latent mean `T0MEANS`. The scalar map is \(E(y)=\tau+\lambda\mu\). Form \(\lambda\mu\) then add \(\tau\). A zero loading or zero latent mean is exactly \(\tau\). A zero intercept is exactly \(\lambda\mu\). `MANIFESTMEANS` is not \(E(y)\). \(E(\eta)\) is not \(E(y)\). `CINT` is not `MANIFESTMEANS`. `T0MEANS` is not \(E(y)\). An overflowing product or sum fails closed. This is not a Kalman filter and not ctsem estimation. From c1aeed3bc2ca5f801f3baa748a4a3dde9f948338 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 18:07:57 +0900 Subject: [PATCH 008/296] fix(longitudinal): retire invalid one-sided autocorrelation claim Replace the unmerged psychometric_core covariance/initial-variance ratio with a Longitudinal Modeling boundary that only standardizes lagged covariance when both marginal variances are known. Preserve the invalid predecessor commit as RED lineage, remove its public autocorrelation API and docs from the final diff, and add nonstationary/covariance-bound/overflow-safe regressions. --- ARCHITECTURE.md | 6 +- CHANGELOG.md | 2 - CLAUDE.md | 2 +- crates/longitudinal_core/Cargo.toml | 2 +- crates/longitudinal_core/src/association.rs | 137 +++++++ crates/longitudinal_core/src/error.rs | 38 +- crates/longitudinal_core/src/lib.rs | 12 +- crates/psychometric_core/src/error.rs | 44 --- crates/psychometric_core/src/event_time.rs | 347 +----------------- crates/psychometric_core/src/lib.rs | 16 - ...multilevel_event_time_recovery_contract.rs | 147 +------- .../scientific_claim_boundary_contract.rs | 100 +---- docs/TRACEABILITY.md | 2 +- docs/adr/0005-posterior-esem-dsem.md | 4 +- .../multilevel-event-time-recovery.md | 7 +- .../trait-state-lagged-association.md | 49 +++ 16 files changed, 273 insertions(+), 642 deletions(-) create mode 100644 crates/longitudinal_core/src/association.rs create mode 100644 docs/research/trait-state-lagged-association.md diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 078717fe6..e6dafbcc2 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -114,11 +114,11 @@ boundaries above remain the target modular MSA architecture. | `membership_target` | language, episode, template, department, and opportunity-pool targets cannot collapse into entity or project | | `topic_measurement` | logistic-normal ALR/ILR coordinates and the CPU `f64` TRSL-TM reference estimator | | `analysis_engine` | bounded cutoff-safe temporal evidence readiness execution and digest-bound terminal artifacts | -| `psychometric_core` | posterior-aware structural input gates, CWC within/between OLS plus the contextual effect, event-time log-rate, unequal-interval discrete-lag remapping, constant-predictor discrete effect, time-varying-predictor discrete effect (Eq. 14), exact scalar discrete process noise (Driver et al., 2017, Eq. 3), lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; `asymDIFFUSION`), trait-plus-state variance and expected autocorrelation (Driver et al., 2017, §4.3 `TRAITVAR`; §7.1; not process noise, not discreteDRIFT, not discreteDRIFTstd), observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5; Table 2 `MANIFESTVAR` is `Θ`, not `Var(y)`; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; `Θ` does not enter lagged observed covariance; observed-indicator mean is `τ + λ μ`; `MANIFESTMEANS` is not `E(y)`; `CINT` is not `MANIFESTMEANS`; discrete latent mean is `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ`; `T0MEANS` is not `μ_t`; `CINT` is not the discrete increment; evolved observed mean is `τ + λ μ_t`; `τ + λ μ_0` is not `E(y_t)`; contemporaneous `TDPREDEFFECT` impulse is `m x`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that contemporaneous impulse is `τ + λ(μ_t + m x)`, and `τ + λ μ_t` is not that observed mean; time-independent `TIPREDEFFECT` increment is `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, not `M x`, not Voelkle Eq. 14, and not the coefficient `B`; Eq. 5 of that increment is `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`; within-interval `TDPREDEFFECT` carry is `e^{A(t−u)} M x` for `t0 < u < t`, not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that carry is `τ + λ(μ_t + e^{a(t−u)} m x)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that carried observed mean when `u ≠ t`; first-occasion `T0TIPREDEFFECT` shift is `t0_b z` and Eq. 3 first-summand carry is `e^{A Δt} t0_b z` (`T0TIPREDEFFECT` is not `TIPREDEFFECT` `B`; `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`; `e^{A Δt} t0_b z` is not `t0_b z`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_b z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean), first-occasion `T0TDPREDEFFECT` shift is `t0_m x0` and Eq. 3 first-summand carry is `e^{A Δt} t0_m x0` (`T0TDPREDEFFECT` is not `TDPREDEFFECT` `M`; `t0_m x0` is not `M x`; `e^{A Δt} t0_m x0` is not `t0_m x0`; `e^{A Δt} t0_m x0` is not `e^{A(t−u)} M x` for `t0 < u < t`; `t0_m x0` is not `t0_b z`; an impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_m x0)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean; `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean; §7.2 level-change `CINT` is `κ = −a m x` with `a < 0` so `−κ / a = m x` (`−a m x` is not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`, and not the extra near-zero-drift latent process also named in §7.2; Eq. 3 of that setting is `(1 − e^{a Δt}) m x`, which is not `m x`, not `κ`, and not `TIPREDEFFECT`; §7.2 extra-process contribution is `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (`ε = a` is `a_{ηξ} x Δt e^{a Δt}`; identification `TDPREDEFFECT` on the extra process is 1; printed extra `DRIFT` is `−0.000001`; not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`; `ε ≥ 0` fails closed; Eq. 5 of that contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)`; the extra process has `LAMBDA` 0 and is not an observed indicator; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; the contribution is not `E(y_t)`; the evolved-plus-contribution latent mean is not `E(y_t)`; after-t0 extra-process `TDPREDEFFECT` is `a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)` for `t0 < u < t` while `μ_t` uses `Δt`; Eq. 5 of that after-t0 contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)`; the first-occasion extra-process observed mean is not that observed mean when `u ≠ t0`; `e^{a(t−u)} m x` is a Dirac on the original process, not this `DRIFT` drive; §7.2 `asymTIPREDEFFECT` is `-B z / a` for `a < 0` (`-B z / a` is not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; §7.2 `addedTIPREDVAR` is `(B / a)² v`, not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`; Table 2 `asymCINT` is `-κ / a` for `a < 0` and is not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; p. 16 stationary `T0MEANS` is `-κ / a + −B z / a` and is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean; Eq. 5 of that constrained mean is `τ + λ(−κ / a + −B z / a)`; `τ + λ μ_0` is not that observed mean; `τ + λ(−κ / a)` is not that observed mean when `B z ≠ 0`; `τ + λ μ_t` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`; stationary `T0VAR` is `trait + −q / (2 a) + (B / a)² v` (not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`); lagged stationary `T0VAR` is `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (trait and `addedTIPREDVAR` do not decay; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map; Eq. 5 of that lagged covariance is `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ`; `Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance); later-occasion stationary `T0VAR` is `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt`; `Q_Δt` is not that later map; Eq. 5 of that later-occasion variance is `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ`; lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not `Var(y_t)`; the later-occasion latent variance is not `Var(y_t)`))), irregular already-centered residual lag, Rubin `T` on OLS loadings, and strong-gated latent means (two-observation residual variance is identically `0` and caps at strong/scalar; Putnick & Bornstein, 2016) | +| `psychometric_core` | posterior-aware structural input gates, CWC within/between OLS plus the contextual effect, event-time log-rate, unequal-interval discrete-lag remapping, constant-predictor discrete effect, time-varying-predictor discrete effect (Eq. 14), exact scalar discrete process noise (Driver et al., 2017, Eq. 3), lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; `asymDIFFUSION`), trait-plus-state variance (Driver et al., 2017, §4.3 `TRAITVAR`; not process noise), observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5; Table 2 `MANIFESTVAR` is `Θ`, not `Var(y)`; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; `Θ` does not enter lagged observed covariance; observed-indicator mean is `τ + λ μ`; `MANIFESTMEANS` is not `E(y)`; `CINT` is not `MANIFESTMEANS`; discrete latent mean is `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ`; `T0MEANS` is not `μ_t`; `CINT` is not the discrete increment; evolved observed mean is `τ + λ μ_t`; `τ + λ μ_0` is not `E(y_t)`; contemporaneous `TDPREDEFFECT` impulse is `m x`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that contemporaneous impulse is `τ + λ(μ_t + m x)`, and `τ + λ μ_t` is not that observed mean; time-independent `TIPREDEFFECT` increment is `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, not `M x`, not Voelkle Eq. 14, and not the coefficient `B`; Eq. 5 of that increment is `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`; within-interval `TDPREDEFFECT` carry is `e^{A(t−u)} M x` for `t0 < u < t`, not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that carry is `τ + λ(μ_t + e^{a(t−u)} m x)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that carried observed mean when `u ≠ t`; first-occasion `T0TIPREDEFFECT` shift is `t0_b z` and Eq. 3 first-summand carry is `e^{A Δt} t0_b z` (`T0TIPREDEFFECT` is not `TIPREDEFFECT` `B`; `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`; `e^{A Δt} t0_b z` is not `t0_b z`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_b z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean), first-occasion `T0TDPREDEFFECT` shift is `t0_m x0` and Eq. 3 first-summand carry is `e^{A Δt} t0_m x0` (`T0TDPREDEFFECT` is not `TDPREDEFFECT` `M`; `t0_m x0` is not `M x`; `e^{A Δt} t0_m x0` is not `t0_m x0`; `e^{A Δt} t0_m x0` is not `e^{A(t−u)} M x` for `t0 < u < t`; `t0_m x0` is not `t0_b z`; an impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_m x0)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean; `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean; §7.2 level-change `CINT` is `κ = −a m x` with `a < 0` so `−κ / a = m x` (`−a m x` is not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`, and not the extra near-zero-drift latent process also named in §7.2; Eq. 3 of that setting is `(1 − e^{a Δt}) m x`, which is not `m x`, not `κ`, and not `TIPREDEFFECT`; §7.2 extra-process contribution is `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (`ε = a` is `a_{ηξ} x Δt e^{a Δt}`; identification `TDPREDEFFECT` on the extra process is 1; printed extra `DRIFT` is `−0.000001`; not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`; `ε ≥ 0` fails closed; Eq. 5 of that contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)`; the extra process has `LAMBDA` 0 and is not an observed indicator; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; the contribution is not `E(y_t)`; the evolved-plus-contribution latent mean is not `E(y_t)`; after-t0 extra-process `TDPREDEFFECT` is `a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)` for `t0 < u < t` while `μ_t` uses `Δt`; Eq. 5 of that after-t0 contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)`; the first-occasion extra-process observed mean is not that observed mean when `u ≠ t0`; `e^{a(t−u)} m x` is a Dirac on the original process, not this `DRIFT` drive; §7.2 `asymTIPREDEFFECT` is `-B z / a` for `a < 0` (`-B z / a` is not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; §7.2 `addedTIPREDVAR` is `(B / a)² v`, not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`; Table 2 `asymCINT` is `-κ / a` for `a < 0` and is not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; p. 16 stationary `T0MEANS` is `-κ / a + −B z / a` and is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean; Eq. 5 of that constrained mean is `τ + λ(−κ / a + −B z / a)`; `τ + λ μ_0` is not that observed mean; `τ + λ(−κ / a)` is not that observed mean when `B z ≠ 0`; `τ + λ μ_t` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`; stationary `T0VAR` is `trait + −q / (2 a) + (B / a)² v` (not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`); lagged stationary `T0VAR` is `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (trait and `addedTIPREDVAR` do not decay; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map; Eq. 5 of that lagged covariance is `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ`; `Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance); later-occasion stationary `T0VAR` is `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt`; `Q_Δt` is not that later map; Eq. 5 of that later-occasion variance is `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ`; lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not `Var(y_t)`; the later-occasion latent variance is not `Var(y_t)`))), irregular already-centered residual lag, Rubin `T` on OLS loadings, and strong-gated latent means (two-observation residual variance is identically `0` and caps at strong/scalar; Putnick & Bornstein, 2016) | | `validation_core` | RMSE, bias, coverage, graph, and Monte Carlo metrics | | `tepp_api` | versioned DTO, schema, and export contracts | -| `psychometric_core` | posterior-aware structural input gates, CWC within/between OLS plus the contextual effect, event-time log-rate, unequal-interval discrete-lag remapping, constant-predictor discrete effect, time-varying-predictor discrete effect (Eq. 14), exact scalar discrete process noise (Driver et al., 2017, Eq. 3), lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; `asymDIFFUSION`), trait-plus-state variance and expected autocorrelation (Driver et al., 2017, §4.3 `TRAITVAR`; §7.1; not process noise, not discreteDRIFT, not discreteDRIFTstd), observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5; Table 2 `MANIFESTVAR` is `Θ`, not `Var(y)`; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; `Θ` does not enter lagged observed covariance; observed-indicator mean is `τ + λ μ`; `MANIFESTMEANS` is not `E(y)`; `CINT` is not `MANIFESTMEANS`; discrete latent mean is `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ`; `T0MEANS` is not `μ_t`; `CINT` is not the discrete increment; evolved observed mean is `τ + λ μ_t`; `τ + λ μ_0` is not `E(y_t)`; contemporaneous `TDPREDEFFECT` impulse is `m x`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that contemporaneous impulse is `τ + λ(μ_t + m x)`, and `τ + λ μ_t` is not that observed mean; time-independent `TIPREDEFFECT` increment is `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, not `M x`, not Voelkle Eq. 14, and not the coefficient `B`; Eq. 5 of that increment is `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`; within-interval `TDPREDEFFECT` carry is `e^{A(t−u)} M x` for `t0 < u < t`, not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that carry is `τ + λ(μ_t + e^{a(t−u)} m x)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that carried observed mean when `u ≠ t`; first-occasion `T0TIPREDEFFECT` shift is `t0_b z` and Eq. 3 first-summand carry is `e^{A Δt} t0_b z` (`T0TIPREDEFFECT` is not `TIPREDEFFECT` `B`; `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`; `e^{A Δt} t0_b z` is not `t0_b z`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_b z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean), first-occasion `T0TDPREDEFFECT` shift is `t0_m x0` and Eq. 3 first-summand carry is `e^{A Δt} t0_m x0` (`T0TDPREDEFFECT` is not `TDPREDEFFECT` `M`; `t0_m x0` is not `M x`; `e^{A Δt} t0_m x0` is not `t0_m x0`; `e^{A Δt} t0_m x0` is not `e^{A(t−u)} M x` for `t0 < u < t`; `t0_m x0` is not `t0_b z`; an impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_m x0)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean; `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean; §7.2 level-change `CINT` is `κ = −a m x` with `a < 0` so `−κ / a = m x` (`−a m x` is not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`, and not the extra near-zero-drift latent process also named in §7.2; Eq. 3 of that setting is `(1 − e^{a Δt}) m x`, which is not `m x`, not `κ`, and not `TIPREDEFFECT`; §7.2 extra-process contribution is `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (`ε = a` is `a_{ηξ} x Δt e^{a Δt}`; identification `TDPREDEFFECT` on the extra process is 1; printed extra `DRIFT` is `−0.000001`; not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`; `ε ≥ 0` fails closed; Eq. 5 of that contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)`; the extra process has `LAMBDA` 0 and is not an observed indicator; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; the contribution is not `E(y_t)`; the evolved-plus-contribution latent mean is not `E(y_t)`; after-t0 extra-process `TDPREDEFFECT` is `a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)` for `t0 < u < t` while `μ_t` uses `Δt`; Eq. 5 of that after-t0 contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)`; the first-occasion extra-process observed mean is not that observed mean when `u ≠ t0`; `e^{a(t−u)} m x` is a Dirac on the original process, not this `DRIFT` drive; §7.2 `asymTIPREDEFFECT` is `-B z / a` for `a < 0` (`-B z / a` is not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; §7.2 `addedTIPREDVAR` is `(B / a)² v`, not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`; Table 2 `asymCINT` is `-κ / a` for `a < 0` and is not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; p. 16 stationary `T0MEANS` is `-κ / a + −B z / a` and is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean; Eq. 5 of that constrained mean is `τ + λ(−κ / a + −B z / a)`; `τ + λ μ_0` is not that observed mean; `τ + λ(−κ / a)` is not that observed mean when `B z ≠ 0`; `τ + λ μ_t` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`; stationary `T0VAR` is `trait + −q / (2 a) + (B / a)² v` (not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`); lagged stationary `T0VAR` is `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (trait and `addedTIPREDVAR` do not decay; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map; Eq. 5 of that lagged covariance is `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ`; `Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance); later-occasion stationary `T0VAR` is `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt`; `Q_Δt` is not that later map; Eq. 5 of that later-occasion variance is `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ`; lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not `Var(y_t)`; the later-occasion latent variance is not `Var(y_t)`); predetermined later-occasion `T0VAR` is `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (free `T0VAR` `p_0` is not that later map; setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map; stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map; Eq. 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ`; `MANIFESTVAR` is not `Var(y_t)`; the predetermined later-occasion latent variance is not `Var(y_t)`; stationary later observed variance is not that observed variance when `p_0` is free); predetermined lagged `T0VAR` is `trait + e^{a Δt} p_0 + (B / a)² v` (free `T0VAR` `p_0` is not that lagged map; setting `p_0 = −q / (2 a)` recovers the stationary lagged map; stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map; later-occasion variance includes `Q_Δt` and is not that lagged map; Eq. 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ`; `MANIFESTVAR` does not enter; the predetermined lagged latent covariance is not that observed covariance; predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance; stationary lagged observed covariance is not that observed covariance when `p_0` is free; the predetermined first-occasion variance of §4.3 predetermined `T0VAR` is `trait + p_0 + (B / a)² v`; free `p_0` is not that map; stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free; lagged covariance decays the state and is not that map; later-occasion variance includes `Q_Δt` and is not that map; Eq. 5 of that predetermined first-occasion variance is `λ²(trait + p_0 + (B / a)² v) + θ + ψ`; `MANIFESTVAR` is not that first-occasion observed variance; the predetermined first-occasion latent variance is not that observed variance; stationary first-occasion observed variance is not that observed variance when `p_0` is free; predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance; later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z; first-occasion lagged omits `e^{a s} Q_u`; later-occasion variance does not lag; stationary lagged uses `−q / (2 a)`; decaying the later total is not that map; Eq. 5 of that later-start lagged covariance is `λ²` of it plus `ψ`; `Θ` does not enter; first-occasion lagged observed omits `e^{a s} Q_u`; later observed variance includes `Q_u` and `θ`; later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z; later-occasion variance at `u` omits `Q_s`; later-start lagged covariance omits `Q_s`; stationary later uses `−q / (2 a)`; evolving the later total as if it were all state is not that map; ignoring `startoffset` omits `e^{2 a s} Q_u`; Eq. 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`; `MANIFESTVAR` is not that observed variance; p. 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4; unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`; the §7.1 trait-plus-state autocorrelation uses `TRAITVAR` and is not `discreteDRIFTstd`; `TRAITVAR` is not the standardisation variance; p. 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4; unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`; the continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`; `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z; unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`; the discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`; `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z); unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`; the discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`; `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z; unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`; `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z; unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`; the asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`; `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z; the affected variance is free `T0VAR`, not `asymDIFFUSION`; unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`; `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`; `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`; `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); 2017-era `addedT0TIPREDVAR` is `t0_b² v` (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z; `T0TIPREDEFFECT %*% TIPREDVAR %*% t(T0TIPREDEFFECT)` immediately after `T0TIPREDEFFECTstd`; form `t0_b` first, then square, then multiply by `v`; a zero coefficient or zero predictor variance is exactly zero; free `T0TIPREDEFFECT` does not require `a < 0`; `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map; `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance; free `T0VAR` is not this extra TI variance; `TRAITVAR` is not this extra TI variance; Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z; form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; `t0_b² v` is the latent extra, not the observed extra; `λ² p_0 + θ` is first-occasion observed variance, not this extra; `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra; `MANIFESTVAR` `θ` is not this extra; Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v`; form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; lasting asymptotic extra requires `a < 0`; `(B / a)² v` is the latent extra, not the observed extra; `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra; `λ² p + θ` is stationary observed variance, not this extra; `MANIFESTVAR` `θ` is not this extra; p. 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance; unstandardised `M` is not `TDPREDEFFECTstd`; `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`; intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`; `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`; Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance; unstandardised `t0_m` is not `T0TDPREDEFFECTstd`; `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`; `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`; `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`; free `T0VAR` does not require `a < 0`; p. 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; default ridge is 0); unstandardised `T0VAR` is not `T0VARstd`; `T0TDPREDEFFECTstd` is not `T0VARstd`; `addedT0TIPREDVAR` is not `T0VARstd`; p. 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend); unstandardised `TRAITVAR` is not `TRAITVARstd`; `T0VARstd` is not `TRAITVARstd` even when both equal 1; `addedT0TIPREDVAR` is not `TRAITVARstd`; p. 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0); unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`; `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1; `MANIFESTVAR` is not `MANIFESTTRAITVARstd`; p. 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug); unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`; `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1; Equation 5 `Var(y)` is not `MANIFESTVARstd`; p. 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`); unstandardised `TIPREDVAR` is not `TIPREDVARstd`; `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1; §7.2 `addedTIPREDVAR` is not `TIPREDVARstd`; p. 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`); unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`; `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1; `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`; p. 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `discreteCINT` is not `discreteCINTstd`; `κ / √p` is not `discreteCINTstd`; `(-κ / a) / √p` is not `discreteCINTstd`; `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`; unstandardised `asymCINT` is not `asymCINTstd`; `κ / √p` is not `asymCINTstd`; `discreteCINTstd` is not `asymCINTstd`; `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`; unstandardised `T0MEANS` is not `T0MEANSstd`; `T0VARstd` is not `T0MEANSstd`; `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`; `MANIFESTMEANSstd` is `τ / √θ` after strictly positive `MANIFESTVAR`; unstandardised `MANIFESTMEANS` is not `MANIFESTMEANSstd`; `MANIFESTVARstd` is not `MANIFESTMEANSstd`; `τ / √(λ² Var(η) + θ)` is not `MANIFESTMEANSstd`; p. 16 `CINTstd` is `κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `CINT` is not `CINTstd`; `asymCINTstd` is not `CINTstd`; `discreteCINTstd` is not `CINTstd`; `κ / √(trait + p + added)` is not `CINTstd`;))))), irregular already-centered residual lag, Rubin `T` on OLS loadings, and strong-gated latent means (two-observation residual variance is identically `0` and caps at strong/scalar; Putnick & Bornstein, 2016) | -| `psychometric_core` | posterior-aware structural input gates, CWC within/between OLS plus the contextual effect, event-time log-rate, unequal-interval discrete-lag remapping, constant-predictor discrete effect, time-varying-predictor discrete effect (Eq. 14), exact scalar discrete process noise (Driver et al., 2017, Eq. 3), lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; `asymDIFFUSION`), trait-plus-state variance and expected autocorrelation (Driver et al., 2017, §4.3 `TRAITVAR`; §7.1; not process noise, not discreteDRIFT, not discreteDRIFTstd), observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5; Table 2 `MANIFESTVAR` is `Θ`, not `Var(y)`; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; `Θ` does not enter lagged observed covariance; observed-indicator mean is `τ + λ μ`; `MANIFESTMEANS` is not `E(y)`; `CINT` is not `MANIFESTMEANS`; discrete latent mean is `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ`; `T0MEANS` is not `μ_t`; `CINT` is not the discrete increment; evolved observed mean is `τ + λ μ_t`; `τ + λ μ_0` is not `E(y_t)`; contemporaneous `TDPREDEFFECT` impulse is `m x`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that contemporaneous impulse is `τ + λ(μ_t + m x)`, and `τ + λ μ_t` is not that observed mean; time-independent `TIPREDEFFECT` increment is `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, not `M x`, not Voelkle Eq. 14, and not the coefficient `B`; Eq. 5 of that increment is `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`; within-interval `TDPREDEFFECT` carry is `e^{A(t−u)} M x` for `t0 < u < t`, not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that carry is `τ + λ(μ_t + e^{a(t−u)} m x)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that carried observed mean when `u ≠ t`; first-occasion `T0TIPREDEFFECT` shift is `t0_b z` and Eq. 3 first-summand carry is `e^{A Δt} t0_b z` (`T0TIPREDEFFECT` is not `TIPREDEFFECT` `B`; `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`; `e^{A Δt} t0_b z` is not `t0_b z`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_b z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean), first-occasion `T0TDPREDEFFECT` shift is `t0_m x0` and Eq. 3 first-summand carry is `e^{A Δt} t0_m x0` (`T0TDPREDEFFECT` is not `TDPREDEFFECT` `M`; `t0_m x0` is not `M x`; `e^{A Δt} t0_m x0` is not `t0_m x0`; `e^{A Δt} t0_m x0` is not `e^{A(t−u)} M x` for `t0 < u < t`; `t0_m x0` is not `t0_b z`; an impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_m x0)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean; `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean; §7.2 level-change `CINT` is `κ = −a m x` with `a < 0` so `−κ / a = m x` (`−a m x` is not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`, and not the extra near-zero-drift latent process also named in §7.2; Eq. 3 of that setting is `(1 − e^{a Δt}) m x`, which is not `m x`, not `κ`, and not `TIPREDEFFECT`; §7.2 extra-process contribution is `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (`ε = a` is `a_{ηξ} x Δt e^{a Δt}`; identification `TDPREDEFFECT` on the extra process is 1; printed extra `DRIFT` is `−0.000001`; not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`; `ε ≥ 0` fails closed; Eq. 5 of that contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)`; the extra process has `LAMBDA` 0 and is not an observed indicator; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; the contribution is not `E(y_t)`; the evolved-plus-contribution latent mean is not `E(y_t)`; after-t0 extra-process `TDPREDEFFECT` is `a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)` for `t0 < u < t` while `μ_t` uses `Δt`; Eq. 5 of that after-t0 contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)`; the first-occasion extra-process observed mean is not that observed mean when `u ≠ t0`; `e^{a(t−u)} m x` is a Dirac on the original process, not this `DRIFT` drive; §7.2 `asymTIPREDEFFECT` is `-B z / a` for `a < 0` (`-B z / a` is not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; §7.2 `addedTIPREDVAR` is `(B / a)² v`, not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`; Table 2 `asymCINT` is `-κ / a` for `a < 0` and is not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; p. 16 stationary `T0MEANS` is `-κ / a + −B z / a` and is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean; Eq. 5 of that constrained mean is `τ + λ(−κ / a + −B z / a)`; `τ + λ μ_0` is not that observed mean; `τ + λ(−κ / a)` is not that observed mean when `B z ≠ 0`; `τ + λ μ_t` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`; stationary `T0VAR` is `trait + −q / (2 a) + (B / a)² v` (not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`); lagged stationary `T0VAR` is `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (trait and `addedTIPREDVAR` do not decay; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map; Eq. 5 of that lagged covariance is `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ`; `Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance); later-occasion stationary `T0VAR` is `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt`; `Q_Δt` is not that later map; Eq. 5 of that later-occasion variance is `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ`; lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not `Var(y_t)`; the later-occasion latent variance is not `Var(y_t)`); predetermined later-occasion `T0VAR` is `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (free `T0VAR` `p_0` is not that later map; setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map; stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map; Eq. 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ`; `MANIFESTVAR` is not `Var(y_t)`; the predetermined later-occasion latent variance is not `Var(y_t)`; stationary later observed variance is not that observed variance when `p_0` is free); predetermined lagged `T0VAR` is `trait + e^{a Δt} p_0 + (B / a)² v` (free `T0VAR` `p_0` is not that lagged map; setting `p_0 = −q / (2 a)` recovers the stationary lagged map; stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map; later-occasion variance includes `Q_Δt` and is not that lagged map; Eq. 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ`; `MANIFESTVAR` does not enter; the predetermined lagged latent covariance is not that observed covariance; predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance; stationary lagged observed covariance is not that observed covariance when `p_0` is free; the predetermined first-occasion variance of §4.3 predetermined `T0VAR` is `trait + p_0 + (B / a)² v`; free `p_0` is not that map; stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free; lagged covariance decays the state and is not that map; later-occasion variance includes `Q_Δt` and is not that map; Eq. 5 of that predetermined first-occasion variance is `λ²(trait + p_0 + (B / a)² v) + θ + ψ`; `MANIFESTVAR` is not that first-occasion observed variance; the predetermined first-occasion latent variance is not that observed variance; stationary first-occasion observed variance is not that observed variance when `p_0` is free; predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance; later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z; first-occasion lagged omits `e^{a s} Q_u`; later-occasion variance does not lag; stationary lagged uses `−q / (2 a)`; decaying the later total is not that map; Eq. 5 of that later-start lagged covariance is `λ²` of it plus `ψ`; `Θ` does not enter; first-occasion lagged observed omits `e^{a s} Q_u`; later observed variance includes `Q_u` and `θ`; later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z; later-occasion variance at `u` omits `Q_s`; later-start lagged covariance omits `Q_s`; stationary later uses `−q / (2 a)`; evolving the later total as if it were all state is not that map; ignoring `startoffset` omits `e^{2 a s} Q_u`; Eq. 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`; `MANIFESTVAR` is not that observed variance; p. 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4; unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`; the §7.1 trait-plus-state autocorrelation uses `TRAITVAR` and is not `discreteDRIFTstd`; `TRAITVAR` is not the standardisation variance; p. 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4; unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`; the continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`; `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z; unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`; the discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`; `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z); unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`; the discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`; `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z; unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`; `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z; unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`; the asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`; `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z; the affected variance is free `T0VAR`, not `asymDIFFUSION`; unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`; `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`; `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`; `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); 2017-era `addedT0TIPREDVAR` is `t0_b² v` (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z; `T0TIPREDEFFECT %*% TIPREDVAR %*% t(T0TIPREDEFFECT)` immediately after `T0TIPREDEFFECTstd`; form `t0_b` first, then square, then multiply by `v`; a zero coefficient or zero predictor variance is exactly zero; free `T0TIPREDEFFECT` does not require `a < 0`; `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map; `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance; free `T0VAR` is not this extra TI variance; `TRAITVAR` is not this extra TI variance; Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z; form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; `t0_b² v` is the latent extra, not the observed extra; `λ² p_0 + θ` is first-occasion observed variance, not this extra; `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra; `MANIFESTVAR` `θ` is not this extra; Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v`; form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; lasting asymptotic extra requires `a < 0`; `(B / a)² v` is the latent extra, not the observed extra; `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra; `λ² p + θ` is stationary observed variance, not this extra; `MANIFESTVAR` `θ` is not this extra; p. 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance; unstandardised `M` is not `TDPREDEFFECTstd`; `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`; intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`; `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`; Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance; unstandardised `t0_m` is not `T0TDPREDEFFECTstd`; `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`; `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`; `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`; free `T0VAR` does not require `a < 0`; p. 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; default ridge is 0); unstandardised `T0VAR` is not `T0VARstd`; `T0TDPREDEFFECTstd` is not `T0VARstd`; `addedT0TIPREDVAR` is not `T0VARstd`; p. 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend); unstandardised `TRAITVAR` is not `TRAITVARstd`; `T0VARstd` is not `TRAITVARstd` even when both equal 1; `addedT0TIPREDVAR` is not `TRAITVARstd`; p. 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0); unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`; `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1; `MANIFESTVAR` is not `MANIFESTTRAITVARstd`; p. 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug); unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`; `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1; Equation 5 `Var(y)` is not `MANIFESTVARstd`; p. 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`); unstandardised `TIPREDVAR` is not `TIPREDVARstd`; `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1; §7.2 `addedTIPREDVAR` is not `TIPREDVARstd`; p. 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`); unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`; `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1; `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`; p. 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `discreteCINT` is not `discreteCINTstd`; `κ / √p` is not `discreteCINTstd`; `(-κ / a) / √p` is not `discreteCINTstd`; `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`; unstandardised `asymCINT` is not `asymCINTstd`; `κ / √p` is not `asymCINTstd`; `discreteCINTstd` is not `asymCINTstd`; `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`; unstandardised `T0MEANS` is not `T0MEANSstd`; `T0VARstd` is not `T0MEANSstd`; `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`;))))), irregular already-centered residual lag, Rubin `T` on OLS loadings, and strong-gated latent means (two-observation residual variance is identically `0` and caps at strong/scalar; Putnick & Bornstein, 2016) | +| `psychometric_core` | posterior-aware structural input gates, CWC within/between OLS plus the contextual effect, event-time log-rate, unequal-interval discrete-lag remapping, constant-predictor discrete effect, time-varying-predictor discrete effect (Eq. 14), exact scalar discrete process noise (Driver et al., 2017, Eq. 3), lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; `asymDIFFUSION`), trait-plus-state variance (Driver et al., 2017, §4.3 `TRAITVAR`; not process noise), observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5; Table 2 `MANIFESTVAR` is `Θ`, not `Var(y)`; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; `Θ` does not enter lagged observed covariance; observed-indicator mean is `τ + λ μ`; `MANIFESTMEANS` is not `E(y)`; `CINT` is not `MANIFESTMEANS`; discrete latent mean is `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ`; `T0MEANS` is not `μ_t`; `CINT` is not the discrete increment; evolved observed mean is `τ + λ μ_t`; `τ + λ μ_0` is not `E(y_t)`; contemporaneous `TDPREDEFFECT` impulse is `m x`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that contemporaneous impulse is `τ + λ(μ_t + m x)`, and `τ + λ μ_t` is not that observed mean; time-independent `TIPREDEFFECT` increment is `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, not `M x`, not Voelkle Eq. 14, and not the coefficient `B`; Eq. 5 of that increment is `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`; within-interval `TDPREDEFFECT` carry is `e^{A(t−u)} M x` for `t0 < u < t`, not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that carry is `τ + λ(μ_t + e^{a(t−u)} m x)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that carried observed mean when `u ≠ t`; first-occasion `T0TIPREDEFFECT` shift is `t0_b z` and Eq. 3 first-summand carry is `e^{A Δt} t0_b z` (`T0TIPREDEFFECT` is not `TIPREDEFFECT` `B`; `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`; `e^{A Δt} t0_b z` is not `t0_b z`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_b z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean), first-occasion `T0TDPREDEFFECT` shift is `t0_m x0` and Eq. 3 first-summand carry is `e^{A Δt} t0_m x0` (`T0TDPREDEFFECT` is not `TDPREDEFFECT` `M`; `t0_m x0` is not `M x`; `e^{A Δt} t0_m x0` is not `t0_m x0`; `e^{A Δt} t0_m x0` is not `e^{A(t−u)} M x` for `t0 < u < t`; `t0_m x0` is not `t0_b z`; an impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_m x0)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean; `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean; §7.2 level-change `CINT` is `κ = −a m x` with `a < 0` so `−κ / a = m x` (`−a m x` is not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`, and not the extra near-zero-drift latent process also named in §7.2; Eq. 3 of that setting is `(1 − e^{a Δt}) m x`, which is not `m x`, not `κ`, and not `TIPREDEFFECT`; §7.2 extra-process contribution is `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (`ε = a` is `a_{ηξ} x Δt e^{a Δt}`; identification `TDPREDEFFECT` on the extra process is 1; printed extra `DRIFT` is `−0.000001`; not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`; `ε ≥ 0` fails closed; Eq. 5 of that contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)`; the extra process has `LAMBDA` 0 and is not an observed indicator; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; the contribution is not `E(y_t)`; the evolved-plus-contribution latent mean is not `E(y_t)`; after-t0 extra-process `TDPREDEFFECT` is `a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)` for `t0 < u < t` while `μ_t` uses `Δt`; Eq. 5 of that after-t0 contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)`; the first-occasion extra-process observed mean is not that observed mean when `u ≠ t0`; `e^{a(t−u)} m x` is a Dirac on the original process, not this `DRIFT` drive; §7.2 `asymTIPREDEFFECT` is `-B z / a` for `a < 0` (`-B z / a` is not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; §7.2 `addedTIPREDVAR` is `(B / a)² v`, not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`; Table 2 `asymCINT` is `-κ / a` for `a < 0` and is not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; p. 16 stationary `T0MEANS` is `-κ / a + −B z / a` and is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean; Eq. 5 of that constrained mean is `τ + λ(−κ / a + −B z / a)`; `τ + λ μ_0` is not that observed mean; `τ + λ(−κ / a)` is not that observed mean when `B z ≠ 0`; `τ + λ μ_t` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`; stationary `T0VAR` is `trait + −q / (2 a) + (B / a)² v` (not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`); lagged stationary `T0VAR` is `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (trait and `addedTIPREDVAR` do not decay; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map; Eq. 5 of that lagged covariance is `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ`; `Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance); later-occasion stationary `T0VAR` is `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt`; `Q_Δt` is not that later map; Eq. 5 of that later-occasion variance is `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ`; lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not `Var(y_t)`; the later-occasion latent variance is not `Var(y_t)`); predetermined later-occasion `T0VAR` is `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (free `T0VAR` `p_0` is not that later map; setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map; stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map; Eq. 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ`; `MANIFESTVAR` is not `Var(y_t)`; the predetermined later-occasion latent variance is not `Var(y_t)`; stationary later observed variance is not that observed variance when `p_0` is free); predetermined lagged `T0VAR` is `trait + e^{a Δt} p_0 + (B / a)² v` (free `T0VAR` `p_0` is not that lagged map; setting `p_0 = −q / (2 a)` recovers the stationary lagged map; stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map; later-occasion variance includes `Q_Δt` and is not that lagged map; Eq. 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ`; `MANIFESTVAR` does not enter; the predetermined lagged latent covariance is not that observed covariance; predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance; stationary lagged observed covariance is not that observed covariance when `p_0` is free; the predetermined first-occasion variance of §4.3 predetermined `T0VAR` is `trait + p_0 + (B / a)² v`; free `p_0` is not that map; stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free; lagged covariance decays the state and is not that map; later-occasion variance includes `Q_Δt` and is not that map; Eq. 5 of that predetermined first-occasion variance is `λ²(trait + p_0 + (B / a)² v) + θ + ψ`; `MANIFESTVAR` is not that first-occasion observed variance; the predetermined first-occasion latent variance is not that observed variance; stationary first-occasion observed variance is not that observed variance when `p_0` is free; predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance; later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z; first-occasion lagged omits `e^{a s} Q_u`; later-occasion variance does not lag; stationary lagged uses `−q / (2 a)`; decaying the later total is not that map; Eq. 5 of that later-start lagged covariance is `λ²` of it plus `ψ`; `Θ` does not enter; first-occasion lagged observed omits `e^{a s} Q_u`; later observed variance includes `Q_u` and `θ`; later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z; later-occasion variance at `u` omits `Q_s`; later-start lagged covariance omits `Q_s`; stationary later uses `−q / (2 a)`; evolving the later total as if it were all state is not that map; ignoring `startoffset` omits `e^{2 a s} Q_u`; Eq. 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`; `MANIFESTVAR` is not that observed variance; p. 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4; unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`; the §7.1 trait-plus-state autocorrelation uses `TRAITVAR` and is not `discreteDRIFTstd`; `TRAITVAR` is not the standardisation variance; p. 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4; unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`; the continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`; `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z; unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`; the discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`; `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z); unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`; the discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`; `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z; unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`; `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z; unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`; the asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`; `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z; the affected variance is free `T0VAR`, not `asymDIFFUSION`; unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`; `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`; `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`; `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); 2017-era `addedT0TIPREDVAR` is `t0_b² v` (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z; `T0TIPREDEFFECT %*% TIPREDVAR %*% t(T0TIPREDEFFECT)` immediately after `T0TIPREDEFFECTstd`; form `t0_b` first, then square, then multiply by `v`; a zero coefficient or zero predictor variance is exactly zero; free `T0TIPREDEFFECT` does not require `a < 0`; `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map; `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance; free `T0VAR` is not this extra TI variance; `TRAITVAR` is not this extra TI variance; Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z; form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; `t0_b² v` is the latent extra, not the observed extra; `λ² p_0 + θ` is first-occasion observed variance, not this extra; `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra; `MANIFESTVAR` `θ` is not this extra; Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v`; form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; lasting asymptotic extra requires `a < 0`; `(B / a)² v` is the latent extra, not the observed extra; `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra; `λ² p + θ` is stationary observed variance, not this extra; `MANIFESTVAR` `θ` is not this extra; p. 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance; unstandardised `M` is not `TDPREDEFFECTstd`; `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`; intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`; `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`; Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance; unstandardised `t0_m` is not `T0TDPREDEFFECTstd`; `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`; `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`; `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`; free `T0VAR` does not require `a < 0`; p. 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; default ridge is 0); unstandardised `T0VAR` is not `T0VARstd`; `T0TDPREDEFFECTstd` is not `T0VARstd`; `addedT0TIPREDVAR` is not `T0VARstd`; p. 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend); unstandardised `TRAITVAR` is not `TRAITVARstd`; `T0VARstd` is not `TRAITVARstd` even when both equal 1; `addedT0TIPREDVAR` is not `TRAITVARstd`; p. 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0); unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`; `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1; `MANIFESTVAR` is not `MANIFESTTRAITVARstd`; p. 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug); unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`; `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1; Equation 5 `Var(y)` is not `MANIFESTVARstd`; p. 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`); unstandardised `TIPREDVAR` is not `TIPREDVARstd`; `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1; §7.2 `addedTIPREDVAR` is not `TIPREDVARstd`; p. 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`); unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`; `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1; `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`; p. 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `discreteCINT` is not `discreteCINTstd`; `κ / √p` is not `discreteCINTstd`; `(-κ / a) / √p` is not `discreteCINTstd`; `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`; unstandardised `asymCINT` is not `asymCINTstd`; `κ / √p` is not `asymCINTstd`; `discreteCINTstd` is not `asymCINTstd`; `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`; unstandardised `T0MEANS` is not `T0MEANSstd`; `T0VARstd` is not `T0MEANSstd`; `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`; `MANIFESTMEANSstd` is `τ / √θ` after strictly positive `MANIFESTVAR`; unstandardised `MANIFESTMEANS` is not `MANIFESTMEANSstd`; `MANIFESTVARstd` is not `MANIFESTMEANSstd`; `τ / √(λ² Var(η) + θ)` is not `MANIFESTMEANSstd`; p. 16 `CINTstd` is `κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `CINT` is not `CINTstd`; `asymCINTstd` is not `CINTstd`; `discreteCINTstd` is not `CINTstd`; `κ / √(trait + p + added)` is not `CINTstd`;))))), irregular already-centered residual lag, Rubin `T` on OLS loadings, and strong-gated latent means (two-observation residual variance is identically `0` and caps at strong/scalar; Putnick & Bornstein, 2016) | +| `psychometric_core` | posterior-aware structural input gates, CWC within/between OLS plus the contextual effect, event-time log-rate, unequal-interval discrete-lag remapping, constant-predictor discrete effect, time-varying-predictor discrete effect (Eq. 14), exact scalar discrete process noise (Driver et al., 2017, Eq. 3), lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; `asymDIFFUSION`), trait-plus-state variance (Driver et al., 2017, §4.3 `TRAITVAR`; not process noise), observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5; Table 2 `MANIFESTVAR` is `Θ`, not `Var(y)`; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; `Θ` does not enter lagged observed covariance; observed-indicator mean is `τ + λ μ`; `MANIFESTMEANS` is not `E(y)`; `CINT` is not `MANIFESTMEANS`; discrete latent mean is `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ`; `T0MEANS` is not `μ_t`; `CINT` is not the discrete increment; evolved observed mean is `τ + λ μ_t`; `τ + λ μ_0` is not `E(y_t)`; contemporaneous `TDPREDEFFECT` impulse is `m x`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that contemporaneous impulse is `τ + λ(μ_t + m x)`, and `τ + λ μ_t` is not that observed mean; time-independent `TIPREDEFFECT` increment is `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, not `M x`, not Voelkle Eq. 14, and not the coefficient `B`; Eq. 5 of that increment is `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`; within-interval `TDPREDEFFECT` carry is `e^{A(t−u)} M x` for `t0 < u < t`, not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that carry is `τ + λ(μ_t + e^{a(t−u)} m x)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that carried observed mean when `u ≠ t`; first-occasion `T0TIPREDEFFECT` shift is `t0_b z` and Eq. 3 first-summand carry is `e^{A Δt} t0_b z` (`T0TIPREDEFFECT` is not `TIPREDEFFECT` `B`; `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`; `e^{A Δt} t0_b z` is not `t0_b z`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_b z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean), first-occasion `T0TDPREDEFFECT` shift is `t0_m x0` and Eq. 3 first-summand carry is `e^{A Δt} t0_m x0` (`T0TDPREDEFFECT` is not `TDPREDEFFECT` `M`; `t0_m x0` is not `M x`; `e^{A Δt} t0_m x0` is not `t0_m x0`; `e^{A Δt} t0_m x0` is not `e^{A(t−u)} M x` for `t0 < u < t`; `t0_m x0` is not `t0_b z`; an impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_m x0)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean; `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean; §7.2 level-change `CINT` is `κ = −a m x` with `a < 0` so `−κ / a = m x` (`−a m x` is not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`, and not the extra near-zero-drift latent process also named in §7.2; Eq. 3 of that setting is `(1 − e^{a Δt}) m x`, which is not `m x`, not `κ`, and not `TIPREDEFFECT`; §7.2 extra-process contribution is `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (`ε = a` is `a_{ηξ} x Δt e^{a Δt}`; identification `TDPREDEFFECT` on the extra process is 1; printed extra `DRIFT` is `−0.000001`; not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`; `ε ≥ 0` fails closed; Eq. 5 of that contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)`; the extra process has `LAMBDA` 0 and is not an observed indicator; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; the contribution is not `E(y_t)`; the evolved-plus-contribution latent mean is not `E(y_t)`; after-t0 extra-process `TDPREDEFFECT` is `a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)` for `t0 < u < t` while `μ_t` uses `Δt`; Eq. 5 of that after-t0 contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)`; the first-occasion extra-process observed mean is not that observed mean when `u ≠ t0`; `e^{a(t−u)} m x` is a Dirac on the original process, not this `DRIFT` drive; §7.2 `asymTIPREDEFFECT` is `-B z / a` for `a < 0` (`-B z / a` is not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; §7.2 `addedTIPREDVAR` is `(B / a)² v`, not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`; Table 2 `asymCINT` is `-κ / a` for `a < 0` and is not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; p. 16 stationary `T0MEANS` is `-κ / a + −B z / a` and is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean; Eq. 5 of that constrained mean is `τ + λ(−κ / a + −B z / a)`; `τ + λ μ_0` is not that observed mean; `τ + λ(−κ / a)` is not that observed mean when `B z ≠ 0`; `τ + λ μ_t` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`; stationary `T0VAR` is `trait + −q / (2 a) + (B / a)² v` (not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`); lagged stationary `T0VAR` is `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (trait and `addedTIPREDVAR` do not decay; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map; Eq. 5 of that lagged covariance is `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ`; `Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance); later-occasion stationary `T0VAR` is `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt`; `Q_Δt` is not that later map; Eq. 5 of that later-occasion variance is `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ`; lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not `Var(y_t)`; the later-occasion latent variance is not `Var(y_t)`); predetermined later-occasion `T0VAR` is `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (free `T0VAR` `p_0` is not that later map; setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map; stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map; Eq. 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ`; `MANIFESTVAR` is not `Var(y_t)`; the predetermined later-occasion latent variance is not `Var(y_t)`; stationary later observed variance is not that observed variance when `p_0` is free); predetermined lagged `T0VAR` is `trait + e^{a Δt} p_0 + (B / a)² v` (free `T0VAR` `p_0` is not that lagged map; setting `p_0 = −q / (2 a)` recovers the stationary lagged map; stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map; later-occasion variance includes `Q_Δt` and is not that lagged map; Eq. 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ`; `MANIFESTVAR` does not enter; the predetermined lagged latent covariance is not that observed covariance; predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance; stationary lagged observed covariance is not that observed covariance when `p_0` is free; the predetermined first-occasion variance of §4.3 predetermined `T0VAR` is `trait + p_0 + (B / a)² v`; free `p_0` is not that map; stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free; lagged covariance decays the state and is not that map; later-occasion variance includes `Q_Δt` and is not that map; Eq. 5 of that predetermined first-occasion variance is `λ²(trait + p_0 + (B / a)² v) + θ + ψ`; `MANIFESTVAR` is not that first-occasion observed variance; the predetermined first-occasion latent variance is not that observed variance; stationary first-occasion observed variance is not that observed variance when `p_0` is free; predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance; later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z; first-occasion lagged omits `e^{a s} Q_u`; later-occasion variance does not lag; stationary lagged uses `−q / (2 a)`; decaying the later total is not that map; Eq. 5 of that later-start lagged covariance is `λ²` of it plus `ψ`; `Θ` does not enter; first-occasion lagged observed omits `e^{a s} Q_u`; later observed variance includes `Q_u` and `θ`; later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z; later-occasion variance at `u` omits `Q_s`; later-start lagged covariance omits `Q_s`; stationary later uses `−q / (2 a)`; evolving the later total as if it were all state is not that map; ignoring `startoffset` omits `e^{2 a s} Q_u`; Eq. 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`; `MANIFESTVAR` is not that observed variance; p. 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4; unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`; the §7.1 trait-plus-state autocorrelation uses `TRAITVAR` and is not `discreteDRIFTstd`; `TRAITVAR` is not the standardisation variance; p. 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4; unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`; the continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`; `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z; unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`; the discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`; `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z); unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`; the discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`; `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z; unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`; `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z; unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`; the asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`; `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z; the affected variance is free `T0VAR`, not `asymDIFFUSION`; unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`; `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`; `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`; `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); 2017-era `addedT0TIPREDVAR` is `t0_b² v` (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z; `T0TIPREDEFFECT %*% TIPREDVAR %*% t(T0TIPREDEFFECT)` immediately after `T0TIPREDEFFECTstd`; form `t0_b` first, then square, then multiply by `v`; a zero coefficient or zero predictor variance is exactly zero; free `T0TIPREDEFFECT` does not require `a < 0`; `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map; `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance; free `T0VAR` is not this extra TI variance; `TRAITVAR` is not this extra TI variance; Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z; form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; `t0_b² v` is the latent extra, not the observed extra; `λ² p_0 + θ` is first-occasion observed variance, not this extra; `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra; `MANIFESTVAR` `θ` is not this extra; Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v`; form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; lasting asymptotic extra requires `a < 0`; `(B / a)² v` is the latent extra, not the observed extra; `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra; `λ² p + θ` is stationary observed variance, not this extra; `MANIFESTVAR` `θ` is not this extra; p. 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance; unstandardised `M` is not `TDPREDEFFECTstd`; `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`; intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`; `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`; Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance; unstandardised `t0_m` is not `T0TDPREDEFFECTstd`; `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`; `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`; `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`; free `T0VAR` does not require `a < 0`; p. 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; default ridge is 0); unstandardised `T0VAR` is not `T0VARstd`; `T0TDPREDEFFECTstd` is not `T0VARstd`; `addedT0TIPREDVAR` is not `T0VARstd`; p. 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend); unstandardised `TRAITVAR` is not `TRAITVARstd`; `T0VARstd` is not `TRAITVARstd` even when both equal 1; `addedT0TIPREDVAR` is not `TRAITVARstd`; p. 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0); unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`; `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1; `MANIFESTVAR` is not `MANIFESTTRAITVARstd`; p. 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug); unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`; `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1; Equation 5 `Var(y)` is not `MANIFESTVARstd`; p. 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`); unstandardised `TIPREDVAR` is not `TIPREDVARstd`; `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1; §7.2 `addedTIPREDVAR` is not `TIPREDVARstd`; p. 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`); unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`; `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1; `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`; p. 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `discreteCINT` is not `discreteCINTstd`; `κ / √p` is not `discreteCINTstd`; `(-κ / a) / √p` is not `discreteCINTstd`; `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`; unstandardised `asymCINT` is not `asymCINTstd`; `κ / √p` is not `asymCINTstd`; `discreteCINTstd` is not `asymCINTstd`; `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`; unstandardised `T0MEANS` is not `T0MEANSstd`; `T0VARstd` is not `T0MEANSstd`; `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`;))))), irregular already-centered residual lag, Rubin `T` on OLS loadings, and strong-gated latent means (two-observation residual variance is identically `0` and caps at strong/scalar; Putnick & Bornstein, 2016) | Foundation crates expose only tested contracts. Empty façades are not public diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b0f27efe..062a69412 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,8 +38,6 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang ## [Unreleased] -- `psychometric_core` recovers the Driver, Oud, and Voelkle (2017, §7.1, pp. 18–19; Eq. 3–4, pp. 4–5; §4.3, p. 9; §7.2 `addedTIPREDVAR`; JSS PDF re-opened 2026-09-01T06:30Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104) scalar trait-plus-state expected autocorrelation. Section 7.1 names traits the stable between-subject differences (unit-level unobserved heterogeneity) and warns that omitting `TRAITVAR` mixes between- and within-person information; Figure 4 shows auto-effects (persistence) reduced after traits. The paper does not print this ratio; it is the covariance ratio of the already-recovered §4.3 maps. Form the lagged covariance `trait + e^{a Δt} p` first, then the contemporaneous total `trait + p`, then include `addedTIPREDVAR`, then `(trait + e^{a Δt} p + added) / (trait + p + added)`. A zero trait and a zero TI extra recovers `e^{a Δt}` numerically and remains a distinct named quantity. A zero state with a positive trait or extra is exactly 1. A zero total variance fails closed. Growing `a > 0` is a covariance ratio of those named maps and is kept. Unstandardised `discreteDRIFT` `e^{a Δt}` is the state auto-effect, not this total-variance ratio. Page 16 `discreteDRIFTstd` standardises `DRIFT` using only within-subject `asymDIFFUSION` (footnote 4), not the total. `TRAITVAR` is not the standardisation variance. Meredith (1993) remains unread (Unpaywall historically `is_oa: false`; Springer `content/pdf` is an HTML stub). Mislevy (1991, *Psychometrika, 56*, 177–196) remains unread on the same terms (DOI `10.1007/bf02294457`). Still not a Kalman filter, not a matrix `expm`, not ESEM estimation, not DSEM, not RI-CLPM, and not ctsem estimation. - - `event_core` adds bounded Allen interval-consistency classification, atomic path-consistency closure, contradiction/resource refusals, and an explicit dependency-error fallback without claiming unrestricted global satisfiability. - `psychometric_core` recovers the Driver, Oud, and Voelkle (2017, Table 2, p. 12 `MANIFESTTRAITVAR`; §7.1, p. 19; p. 16 `MANIFESTTRAITVARstd`; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-27T14:20Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104) scalar standardised manifest-trait variance on current main after `0ce16e8` dropped the pre-consolidation code while research notes already named the map (register items 83–84). Table 2 names `MANIFESTTRAITVAR` `Ψ_τ` the additional time-invariant variance-covariance on the measurement level and sets it `NULL` when there is no manifest trait. Equation 5 writes `Γ ~ N(τ, Ψ)` and names that covariance the manifest traits. Section 7.1 names manifest traits stable individual differences in indicator levels, distinct from process-level `TRAITVAR` `φ_ξ`. Page 16 prints standardised matrices with the suffix `std` when appropriate. The printed example on p. 16 is `discreteDRIFTstd`, not `MANIFESTTRAITVARstd`. Footnote 4 standardises using only the relevant variance, not the total. The relevant variance for that named indicator-level correlation is `MANIFESTTRAITVAR`, not process-level `TRAITVAR` and not residual `MANIFESTVAR` `θ`. The 2017-era source forms `MANIFESTTRAITVARstd` only when `MANIFESTTRAITVAR != 0`, as `solve(sqrt(diag(MANIFESTTRAITVAR) + ridging)) %&% MANIFESTTRAITVAR` when `verbose = TRUE`. OpenMx `%&%` is `t(A) %*% B %*% A`. Unlike `TRAITVARstd`, that formation adds `diag(c(ridging), n.manifest)`. The default `ridging = FALSE` adds 0, not `0.0001`; that ridge is a numerical hack and is not this exact map. The scalar correlation is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR`. Form strictly positive `ψ` first, then `1 / √ψ`, then `(1 / √ψ) ψ (1 / √ψ)`. Unstandardised `MANIFESTTRAITVAR` is defined for a zero trait; standardised `MANIFESTTRAITVAR` is not. Zero `MANIFESTTRAITVAR` skips forming `MANIFESTTRAITVARstd` in the 2017-era source and fails closed here. Indicator-level trait variance is an event-time structural quantity, so a non-event clock fails closed. `MANIFESTTRAITVAR` does not require stable `a < 0`. Distinct positive `ψ` recover the same 1. `trait / trait = 1` is `TRAITVARstd` and recovers the same number and remains a distinct named quantity. `θ` is `MANIFESTVAR` and is measurement error, not this correlation. Meredith (1993) remains unread (web search 2026-08-27T14:20Z: Springer/Cambridge Core paywalled; Unpaywall historically `is_oa: false`; Springer `content/pdf` is an HTML stub). Mislevy (1991, *Psychometrika, 56*, 177–196) remains unread on the same terms (DOI `10.1007/bf02294457`). Still not a Kalman filter, not a matrix `expm`, not ESEM estimation, not DSEM, and not ctsem estimation. diff --git a/CLAUDE.md b/CLAUDE.md index 5096a3e74..339692cf0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -15,7 +15,7 @@ Read and follow `AGENTS.md` before changing this repository. The repository-wide - Do not remove repeated report language with global stopword lists or use TF-IDF/BM25 as inferential weights. Model template, section, copied-text, style, modality, and corpus-background sources explicitly. - Do not treat raw topic proportions as ordinary Euclidean indicators. Use logistic-normal coordinates or valid log-ratio coordinates and propagate posterior uncertainty into ESEM/DSEM. - Do not treat metric/weak invariance as a latent-mean license. Strong (equal loading and intercept) or strict is required; `#84` `metric` licenses shared metric meaning only. Putnick and Bornstein (2016, PMC5145197 opened 2026-08-19T22:15Z) require scalar invariance before latent-mean comparison; residual invariance is not a prerequisite. Two-observation series have no residual degrees of freedom (`ordinary_least_squares_fit` returns residual variance `0`) and cap at strong/scalar; they still license means. This is two-group OLS, not MGCFA. Meredith (1993) names remain unread labels (Unpaywall/OpenAlex 2026-08-25T11:32Z: closed). -- Do not use the difference quotient as a continuous-time rate. The scalar map is `a = ln(φ) / Δt` on event time. Discrete lags from unequal event intervals are not one coefficient; remap them through that log-rate. Binary64 `exp(a Δt) = 0` is not a discrete lag. A constant predictor's discrete effect is Voelkle et al. (2012, Eq. 12), evaluated as `a_yx (expm1(z) / a_xx)` with `z = a_xx Δt` so a finite result is not lost when `z` overflows to `-∞` or when `a_yx Δt` overflows. When `expm1(z)` overflows at a finite `z`, rewrite in log space; a zero continuous effect is exactly zero; an overflowing `a_yx/a_xx` rewrite term fails closed. The first-order product is the underflow limit of that equation, not the general constant-predictor discrete effect. A time-varying predictor whose sampling interval equals its constancy interval uses Voelkle et al. (2012, Eq. 14): `b* = a_yx Δt`. Unmatched intervals fail closed (Oud & Jansen, 2000, unread). Discrete process noise is Driver et al. (2017, Eq. 3): `Q_Δt = 0.5 q (expm1(z) / a)` with `z = 2 (a Δt)` and `q = G G⊤ ≥ 0`; do not form `2 a` first; `a = 0` and `z → 0` recover `q Δt`; a zero diffusion is exactly zero; an overflowing rewrite scale `0.5 q / a` fails closed; this is not a Kalman filter. `Q_Δt` is `cov(η_t | η_{t-1})`, not `Var(η_t)`. The lagged covariance is `exp(a Δt) p` and the unconditional variance is `exp(2 a Δt) p + Q_Δt` (Driver et al., 2017, Eq. 3–4, pp. 4–5; JSS has no numbered §2.2). A zero diffusion whose `2 (a Δt)` overflows to `+∞` is not a finite `Var(η_t)`. The stationary within-subject variance is the `Δt → ∞` limit of Eq. 4: `-q / (2 a)` for stable `a < 0` (JSS p. 16 `asymDIFFUSION`; §4.3). When `2 a` is finite, form `q / -(2 a)` so `q / a` overflow does not lose a finite result (`q = MAX`, `a = -0.75` → `MAX / 1.5`). When `2 a` overflows, form `(q / a) * -0.5`. Do not form `0.5 q` first (`q = from_bits(1)` underflows). `a ≥ 0` has no finite stationary variance. Finite-interval `Q_Δt` is not that limit. Trait-plus-state variance is `trait + state` and lagged covariance is `trait + exp(a Δt) p` (Driver et al., 2017, §4.3, p. 9). Trait variance is not process noise and not `asymDIFFUSION`. Evolving the summed variance as if it were all state is not that map. This is not RI-CLPM. Observed-indicator variance is `λ² Var(η) + θ` when `MANIFESTTRAITVAR` is zero and `λ² Var(η) + θ + ψ` otherwise (Driver et al., 2017, Eq. 5, p. 5; Table 2, p. 12). Lagged observed covariance is `λ² cov(η_t, η_{t-1}) + ψ`; `MANIFESTVAR` does not enter. Observed-indicator mean is `τ + λ μ` (Driver et al., 2017, Eq. 5; Table 2, p. 12). `MANIFESTMEANS` is `τ`, not `E(y)`. `E(η)` is not `E(y)`. `CINT` is not `MANIFESTMEANS`. `T0MEANS` is not `E(y)`. The discrete latent mean is `μ_t = exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ` (Driver et al., 2017, Eq. 3, p. 4; Table 2, p. 12). `T0MEANS` is not `μ_t`. `CINT` is not that discrete increment. A zero drift is `κ Δt`. Underflow of `exp(a Δt)` to `+0` drops the carried `T0MEANS` and keeps `−κ / a`. The evolved observed mean is `τ + λ μ_t` (Driver et al., 2017, Eq. 5 of that Eq. 3 map). The first-occasion map `τ + λ μ_0` is not `E(y_t)`. `μ_t` is not `E(y_t)`. The contemporaneous time-dependent predictor impulse is `m x` (Driver et al., 2017, Eq. 3 fourth summand; Table 2 `TDPREDEFFECT` is `M`). Form `μ_t` first, then add `m x`. `TDPREDEFFECT` is not `CINT`. `M x` is not `A^{-1}[e^{A Δt} − I] B z` and is not Voelkle et al. (2012, Eq. 14). The §7.2 level-change form is not that impulse. The observed mean of that contemporaneous impulse is `τ + λ(μ_t + m x)` (Driver et al., 2017, Eq. 5 of the Eq. 3 fourth-summand composition). The evolved map `τ + λ μ_t` is not that observed mean. The carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`. The evolved-plus-impulse latent mean is not `E(y_t)`. The time-independent predictor increment is `A^{-1}[e^{A Δt} − I] B z` (Driver et al., 2017, Eq. 3 second summand; Table 2 `TIPREDEFFECT` is `B`). Form `B z` first, then the discrete intercept map. A zero drift is `B z Δt`. `TIPREDEFFECT` is `B`, not that discrete increment. `A^{-1}[e^{A Δt} − I] B z` is not `CINT`, not `M x`, and not Voelkle et al. (2012, Eq. 14). The observed mean of that increment is `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` (Driver et al., 2017, Eq. 5 of the Eq. 3 printed addend after the `T0MEANS` carry and the `CINT` increment). The evolved map `τ + λ μ_t` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`. The evolved-plus-increment latent mean is not `E(y_t)`. The within-interval time-dependent impulse carry is `e^{A(t−u)} M x` for `t0 < u < t` (Driver et al., 2017, Eq. 1–2 Green-function integral of Eq. 2; §7.2 dissipation). Form `m x` first, then `e^{a(t−u)} m x`. A zero drift is `m x` with no dissipation. Underflow of `e^{a(t−u)}` to `+0` is vanishing dissipation and is kept. `e^{A(t−u)} M x` is not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle et al. (2012, Eq. 14). An impulse at `u = t` is the contemporaneous map. An impulse at `u ≤ t0` is already in `η(t0)`. The observed mean of that carry is `τ + λ(μ_t + e^{a(t−u)} m x)` (Driver et al., 2017, Eq. 5 of the Eq. 1–2 carried latent mean). The evolved map `τ + λ μ_t` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean when `u ≠ t`. `MANIFESTMEANS` is not `E(y_t)`. The carried latent mean is not `E(y_t)`. The first-occasion time-independent predictor shift is `t0_b z` (Driver et al., 2017, Table 3 `T0TIPREDEFFECT`; Eq. 3 first summand). Form `t0_b z` first, then `e^{a Δt} t0_b z`. Form `μ_t` first, then add that carry. A zero drift is `t0_b z`. Underflow of `e^{a Δt}` to `+0` is a vanishing carry of the first-occasion shift and is kept. `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`. `e^{A Δt} t0_b z` is not `t0_b z`. `T0TIPREDEFFECT` is the coefficient, not the shift. The observed mean of that first-occasion carry is `τ + λ(μ_t + e^{a Δt} t0_b z)` (Driver et al., 2017, Eq. 5 of the Table 3 / Eq. 3 first-summand composition). The evolved map `τ + λ μ_t` is not that observed mean. The process-increment map `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The impulse-carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t0`. The evolved-plus-carry latent mean is not `E(y_t)`. The first-occasion time-dependent predictor shift is `t0_m x0` (Driver et al., 2017, Table 3 `T0TDPREDEFFECT`; Eq. 3 first summand; JSS PDF re-opened 2026-08-20T19:10Z). Form `t0_m x0` first, then `e^{a Δt} t0_m x0`. Form `μ_t` first, then add that carry. A zero drift is `t0_m x0`. Underflow of `e^{a Δt}` to `+0` is a vanishing carry of the first-occasion shift and is kept. `t0_m x0` is not `M x`, not `e^{A(t−u)} M x` for `t0 < u < t`, not `t0_b z`, not `A^{-1}[e^{A Δt} − I] B z`, and not `CINT`. `e^{A Δt} t0_m x0` is not `t0_m x0`. `T0TDPREDEFFECT` is the coefficient, not the shift. An impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`. The observed mean of that first-occasion TD carry is `τ + λ(μ_t + e^{a Δt} t0_m x0)` (Driver et al., 2017, Eq. 5 of the Table 3 / Eq. 3 first-summand TD composition; JSS PDF re-opened 2026-08-20T19:07Z). The evolved map `τ + λ μ_t` is not that observed mean. The process-increment map `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The impulse-carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t0`. The first-occasion TI map `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean. The evolved-plus-carry latent mean is not `E(y_t)`. The lasting level-change `CINT` is `κ = −a m x` (Driver et al., 2017, §7.2, pp. 20–21; JSS PDF re-opened 2026-08-20T19:45Z). Form `m x` first, then multiply by `−a`. Stable `a < 0` is required so `−κ / a = m x` is an equilibrium offset. `a ≥ 0` cannot hold a new process mean. `−a m x` is not the dissipating Dirac `m x`, not a free `CINT`, and not `A^{-1}[e^{A Δt} − I] B z`. The extra near-zero-drift latent process also named in §7.2 is a different specification and is not this `CINT` setting. Equation 3 maps that intercept as `(1 − e^{a Δt}) m x` (JSS PDF re-opened 2026-08-20T19:50Z). Form the level-change `CINT` first, then the discrete intercept map. Underflow of `e^{a Δt}` to `+0` keeps `m x`. `(1 − e^{a Δt}) m x` is not `m x`, not `κ`, and not `A^{-1}[e^{A Δt} − I] B z`. The printed §7.2 lasting level change is an extra near-zero-drift latent process (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-20T23:10Z). `T0MEANS`, `CINT`, `T0VAR`, `DIFFUSION`, and `TRAITVAR` of that process are fixed to 0; `TDPREDEFFECT` on it is fixed to 1; its `DRIFT` diagonal is very close to 0 (printed example `−0.000001`; precisely 0 causes computational problems); the original process is driven by the `DRIFT` coupling `a_{ηξ}`. After a unit identification impulse the scalar contribution is `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (`ε = a` is `a_{ηξ} x Δt e^{a Δt}`). Form `a_{ηξ} x` first. A zero coupling or zero predictor is exactly zero. `ε ≥ 0` fails closed. That contribution is not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`. The observed mean of that extra-process contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a))` (Driver et al., 2017, Eq. 5 of that §7.2 contribution; JSS PDF re-opened 2026-08-21T06:12Z). The extra process has `LAMBDA` 0 and is not an observed indicator. Original indicators load on the original process after the `DRIFT` coupling. The evolved map `τ + λ μ_t` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The contribution is not `E(y_t)`. The evolved-plus-contribution latent mean is not `E(y_t)`. `T0TDPREDEFFECT` on the extra process begins at `t = 0` and uses `Δt = t − t0` for both the original-process evolution and the extra drive. `TDPREDEFFECT` after `t0` uses `t − u` with `t0 < u < t` for the extra drive while `μ_t` still uses `Δt`. The observed mean of that after-t0 extra-process contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a))` (Driver et al., 2017, Eq. 5 of that §7.2 after-t0 contribution; JSS PDF re-opened 2026-08-21T06:32Z). The first-occasion extra-process observed mean is not that observed mean when `u ≠ t0`. The impulse-carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is a Dirac on the original process and is not that `DRIFT` drive. An impulse at `u = t0` or `u = t` is not interior. The asymptotic time-independent predictor effect is `-B z / a` (Driver et al., 2017, §7.2, pp. 20–21; JSS PDF opened 2026-08-21T13:08Z). Form `B z` first, then divide by `-a`. Stable `a < 0` is required. `a ≥ 0` cannot hold a finite process-mean change. `-B z / a` is not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`. The asymptotic time-independent predictor variance is `(B / a)² v` (Driver et al., 2017, §7.2, pp. 20–21 `addedTIPREDVAR`). Form the unit asymptotic effect first, then square, then multiply by `v`. `(B / a)² v` is not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`. The asymptotic continuous intercept is `-κ / a` (Driver et al., 2017, Table 2, p. 12 `asymCINT`; Eq. 3 as `Δt → ∞`; JSS PDF opened 2026-08-21T16:13Z). Form `κ` first, then divide by `-a`. Stable `a < 0` is required. `-κ / a` is not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`. The p. 16 stationary `T0MEANS` constraint is `-κ / a + −B z / a`. Form the intercept contribution first, then include the TI extra effect, then add. That constrained first-occasion mean is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean. Equation 5 of that constrained mean is `τ + λ(−κ / a + −B z / a)` (Driver et al., 2017, §4.3, pp. 9–10; Eq. 5, p. 5; JSS PDF re-opened 2026-08-21T20:07Z). Form the stationary latent mean first, then `τ + λ` of that mean. `τ + λ μ_0` for free `T0MEANS` is not that composition. `τ + λ(−κ / a)` is not that composition when `B z ≠ 0`. `τ + λ μ_t` is not that composition. `MANIFESTMEANS` is not `E(y_0)`. The constrained latent mean is not `E(y_0)`. The p. 16 constrained first-occasion variance `trait + −q / (2 a) + (B / a)² v` is not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`). The lagged covariance of that constrained process is `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T19:13Z). Trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`. Contemporaneous `T0VAR` is not that lagged map. Decaying the constrained total as if it were all state is not that lagged map. Equation 5 of that lagged covariance is `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ`. `Θ` does not enter. Contemporaneous `Var(y_0)` is not that lagged observed covariance. The lagged latent covariance is not that observed covariance. The later-occasion variance of that constrained process is `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T23:12Z). Trait and `addedTIPREDVAR` do not enter `Q_Δt`. Under stationarity that composition equals contemporaneous `T0VAR`. Evolving the constrained total as if it were all state is not that later map. The lagged covariance omits `Q_Δt` and is not that later map. `Q_Δt` is not that later map. Equation 5 of that later-occasion variance is `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ`. The lagged observed covariance omits `Q_Δt` and `θ`. `MANIFESTVAR` is not `Var(y_t)`. The later-occasion latent variance is not `Var(y_t)`. The later-occasion variance of §4.3 predetermined `T0VAR` is `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T05:12Z). Trait and `addedTIPREDVAR` do not enter `Q_Δt`. Free `T0VAR` `p_0` is not that later map. Setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map. Stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free. Evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map. As `Δt → ∞` with stable `a < 0` the composition approaches contemporaneous stationary `T0VAR`. As `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`. Nonzero diffusion with `a ≥ 0` is a growing process and is kept. Equation 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ`. `MANIFESTVAR` is not `Var(y_t)`. The predetermined later-occasion latent variance is not `Var(y_t)`. Stationary later observed variance is not that observed variance when `p_0` is free. The lagged covariance of §4.3 predetermined `T0VAR` is `trait + e^{a Δt} p_0 + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T09:04Z). Trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`. Free `T0VAR` `p_0` is not that lagged map. Setting `p_0 = −q / (2 a)` recovers the stationary lagged map. Stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free. Evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map. Later-occasion variance includes `Q_Δt` and is not that lagged map. As `Δt → ∞` with stable `a < 0` the state term vanishes. As `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`. Equation 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ`. `MANIFESTVAR` does not enter. The predetermined lagged latent covariance is not that observed covariance. Predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance. Stationary lagged observed covariance is not that observed covariance when `p_0` is free. The predetermined first-occasion variance of §4.3 predetermined `T0VAR` is `trait + p_0 + (B / a)² v`. Free `p_0` is not that map. Stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free. Lagged covariance decays the state and is not that map. Later-occasion variance includes `Q_Δt` and is not that map. Equation 5 of that predetermined first-occasion variance is `λ²(trait + p_0 + (B / a)² v) + θ + ψ`. `MANIFESTVAR` is not that first-occasion observed variance. The predetermined first-occasion latent variance is not that observed variance. Stationary first-occasion observed variance is not that observed variance when `p_0` is free. Predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance. Later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z). First-occasion lagged omits `e^{a s} Q_u`. Later-occasion variance does not lag. Stationary lagged uses `−q / (2 a)`. Decaying the later total is not that map. Equation 5 of that later-start lagged covariance is `λ²` of it plus `ψ`. Independent `ε_t` does not enter. First-occasion lagged observed omits `e^{a s} Q_u`. Predetermined later observed variance includes `Q_u` and `θ` and is not that later-start lagged observed covariance. Later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z). Later-occasion variance at `u` omits `Q_s`. Later-start lagged covariance omits `Q_s`. Stationary later uses `−q / (2 a)`. Evolving the later total as if it were all state is not that map. Ignoring `startoffset` omits `e^{2 a s} Q_u`. Equation 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`. `MANIFESTVAR` is not that observed variance. Page 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; footnote 4; §7.1; JSS PDF re-opened 2026-08-23T11:40Z). Unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`. The §7.1 trait-plus-state autocorrelation `(trait + e^{a Δt} p + added) / (trait + p + added)` uses `TRAITVAR` and is not `discreteDRIFTstd`. The named scalar map is `recover_trait_plus_state_expected_autocorrelation`; it is not unstandardised `discreteDRIFT` `e^{a Δt}` even when trait and added vanish and the numbers coincide. `TRAITVAR` is not the standardisation variance. Page 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:06Z). Unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`. The continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`. `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`. `TRAITVAR` is not the standardisation variance. Page 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z). Unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`. The discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`. `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`. `TRAITVAR` is not the standardisation variance. Page 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z). Unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`. The discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`. `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z). Unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`. The finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`. `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z). Unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`. The asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`. The finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`. `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Page 16 / Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z). The affected variance is free first-occasion `T0VAR`, not `asymDIFFUSION`. Unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`. `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`. `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`. `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. 2017-era `addedT0TIPREDVAR` is `t0_b² v` after a first-occasion time-independent predictor (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z). Form `t0_b` first, then square, then multiply by `v`. A zero coefficient or zero predictor variance is exactly zero. Free `T0TIPREDEFFECT` does not require `a < 0`. `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map. `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance. Free `T0VAR` is not this extra TI variance. `TRAITVAR` is not this extra TI variance. Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z). Form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`. A zero loading or zero extra is exactly zero. `t0_b² v` is the latent extra, not the observed extra. `λ² p_0 + θ` is first-occasion observed variance, not this extra. `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra. `MANIFESTVAR` `θ` is not this extra. Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v` (Driver et al., 2017, Eq. 5, p. 5; Table 2, p. 12; §7.2, pp. 20–21; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:23Z). Form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`. A zero loading or zero extra is exactly zero. Lasting asymptotic extra requires `a < 0`. `(B / a)² v` is the latent extra, not the observed extra. `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra. `λ² p + θ` is stationary observed variance, not this extra. `MANIFESTVAR` `θ` is not this extra. Page 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance. Unstandardised `M` is not `TDPREDEFFECTstd`. `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`. intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`. `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`. Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance. Unstandardised `t0_m` is not `T0TDPREDEFFECTstd`. `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`. `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`. `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`. Free `T0VAR` does not require `a < 0`. Page 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; the default ridge is 0). Unstandardised `T0VAR` is not `T0VARstd`. `T0TDPREDEFFECTstd` is not `T0VARstd`. `addedT0TIPREDVAR` is not `T0VARstd`. Page 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend). Unstandardised `TRAITVAR` is not `TRAITVARstd`. `T0VARstd` is not `TRAITVARstd` even when both equal 1. `addedT0TIPREDVAR` is not `TRAITVARstd`. Page 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0). Unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`. `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1. `MANIFESTVAR` is not `MANIFESTTRAITVARstd`. Page 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug). Unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`. `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1. Equation 5 `Var(y)` is not `MANIFESTVARstd`. Page 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`). Unstandardised `TIPREDVAR` is not `TIPREDVARstd`. `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1. Section 7.2 `addedTIPREDVAR` is not `TIPREDVARstd`. Page 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`). Unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`. `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1. `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`. Page 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`. Unstandardised `discreteCINT` is not `discreteCINTstd`. `κ / √p` is not `discreteCINTstd`. `(-κ / a) / √p` is not `discreteCINTstd`. `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`. Unstandardised `asymCINT` is not `asymCINTstd`. `κ / √p` is not `asymCINTstd`. `discreteCINTstd` is not `asymCINTstd`. `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`. Unstandardised `T0MEANS` is not `T0MEANSstd`. `T0VARstd` is not `T0MEANSstd`. `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`. Page 16 `MANIFESTMEANSstd` is `τ / √θ` after strictly positive `MANIFESTVAR`. Unstandardised `MANIFESTMEANS` is not `MANIFESTMEANSstd`. `MANIFESTVARstd` is not `MANIFESTMEANSstd`. `τ / √(λ² Var(η) + θ)` is not `MANIFESTMEANSstd`. Page 16 `CINTstd` is `κ / √p` after strictly positive `asymDIFFUSION`. Unstandardised `CINT` is not `CINTstd`. `asymCINTstd` is not `CINTstd`. `discreteCINTstd` is not `CINTstd`. `κ / √(trait + p + added)` is not `CINTstd`. Evolving from that stationary start with `CINT` and `TIPREDEFFECT` stays at the stationary mean. Equation 1 is the latent SDE, not the measurement model. Form `(λ p) λ` then add `θ`, then add `ψ`. `MANIFESTVAR` is `Θ`, not `Var(y)`. `MANIFESTTRAITVAR` is `Ψ_τ`, not `Θ`. `TRAITVAR` is latent and scaled by `λ²`. `Var(η)` is not `Var(y)`. +- Do not use the difference quotient as a continuous-time rate. The scalar map is `a = ln(φ) / Δt` on event time. Discrete lags from unequal event intervals are not one coefficient; remap them through that log-rate. Binary64 `exp(a Δt) = 0` is not a discrete lag. A constant predictor's discrete effect is Voelkle et al. (2012, Eq. 12), evaluated as `a_yx (expm1(z) / a_xx)` with `z = a_xx Δt` so a finite result is not lost when `z` overflows to `-∞` or when `a_yx Δt` overflows. When `expm1(z)` overflows at a finite `z`, rewrite in log space; a zero continuous effect is exactly zero; an overflowing `a_yx/a_xx` rewrite term fails closed. The first-order product is the underflow limit of that equation, not the general constant-predictor discrete effect. A time-varying predictor whose sampling interval equals its constancy interval uses Voelkle et al. (2012, Eq. 14): `b* = a_yx Δt`. Unmatched intervals fail closed (Oud & Jansen, 2000, unread). Discrete process noise is Driver et al. (2017, Eq. 3): `Q_Δt = 0.5 q (expm1(z) / a)` with `z = 2 (a Δt)` and `q = G G⊤ ≥ 0`; do not form `2 a` first; `a = 0` and `z → 0` recover `q Δt`; a zero diffusion is exactly zero; an overflowing rewrite scale `0.5 q / a` fails closed; this is not a Kalman filter. `Q_Δt` is `cov(η_t | η_{t-1})`, not `Var(η_t)`. The lagged covariance is `exp(a Δt) p` and the unconditional variance is `exp(2 a Δt) p + Q_Δt` (Driver et al., 2017, Eq. 3–4, pp. 4–5; JSS has no numbered §2.2). A zero diffusion whose `2 (a Δt)` overflows to `+∞` is not a finite `Var(η_t)`. The stationary within-subject variance is the `Δt → ∞` limit of Eq. 4: `-q / (2 a)` for stable `a < 0` (JSS p. 16 `asymDIFFUSION`; §4.3). When `2 a` is finite, form `q / -(2 a)` so `q / a` overflow does not lose a finite result (`q = MAX`, `a = -0.75` → `MAX / 1.5`). When `2 a` overflows, form `(q / a) * -0.5`. Do not form `0.5 q` first (`q = from_bits(1)` underflows). `a ≥ 0` has no finite stationary variance. Finite-interval `Q_Δt` is not that limit. Trait-plus-state variance is `trait + state` and lagged covariance is `trait + exp(a Δt) p` (Driver et al., 2017, §4.3, p. 9). Trait variance is not process noise and not `asymDIFFUSION`. Evolving the summed variance as if it were all state is not that map. This is not RI-CLPM. Observed-indicator variance is `λ² Var(η) + θ` when `MANIFESTTRAITVAR` is zero and `λ² Var(η) + θ + ψ` otherwise (Driver et al., 2017, Eq. 5, p. 5; Table 2, p. 12). Lagged observed covariance is `λ² cov(η_t, η_{t-1}) + ψ`; `MANIFESTVAR` does not enter. Observed-indicator mean is `τ + λ μ` (Driver et al., 2017, Eq. 5; Table 2, p. 12). `MANIFESTMEANS` is `τ`, not `E(y)`. `E(η)` is not `E(y)`. `CINT` is not `MANIFESTMEANS`. `T0MEANS` is not `E(y)`. The discrete latent mean is `μ_t = exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ` (Driver et al., 2017, Eq. 3, p. 4; Table 2, p. 12). `T0MEANS` is not `μ_t`. `CINT` is not that discrete increment. A zero drift is `κ Δt`. Underflow of `exp(a Δt)` to `+0` drops the carried `T0MEANS` and keeps `−κ / a`. The evolved observed mean is `τ + λ μ_t` (Driver et al., 2017, Eq. 5 of that Eq. 3 map). The first-occasion map `τ + λ μ_0` is not `E(y_t)`. `μ_t` is not `E(y_t)`. The contemporaneous time-dependent predictor impulse is `m x` (Driver et al., 2017, Eq. 3 fourth summand; Table 2 `TDPREDEFFECT` is `M`). Form `μ_t` first, then add `m x`. `TDPREDEFFECT` is not `CINT`. `M x` is not `A^{-1}[e^{A Δt} − I] B z` and is not Voelkle et al. (2012, Eq. 14). The §7.2 level-change form is not that impulse. The observed mean of that contemporaneous impulse is `τ + λ(μ_t + m x)` (Driver et al., 2017, Eq. 5 of the Eq. 3 fourth-summand composition). The evolved map `τ + λ μ_t` is not that observed mean. The carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`. The evolved-plus-impulse latent mean is not `E(y_t)`. The time-independent predictor increment is `A^{-1}[e^{A Δt} − I] B z` (Driver et al., 2017, Eq. 3 second summand; Table 2 `TIPREDEFFECT` is `B`). Form `B z` first, then the discrete intercept map. A zero drift is `B z Δt`. `TIPREDEFFECT` is `B`, not that discrete increment. `A^{-1}[e^{A Δt} − I] B z` is not `CINT`, not `M x`, and not Voelkle et al. (2012, Eq. 14). The observed mean of that increment is `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` (Driver et al., 2017, Eq. 5 of the Eq. 3 printed addend after the `T0MEANS` carry and the `CINT` increment). The evolved map `τ + λ μ_t` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`. The evolved-plus-increment latent mean is not `E(y_t)`. The within-interval time-dependent impulse carry is `e^{A(t−u)} M x` for `t0 < u < t` (Driver et al., 2017, Eq. 1–2 Green-function integral of Eq. 2; §7.2 dissipation). Form `m x` first, then `e^{a(t−u)} m x`. A zero drift is `m x` with no dissipation. Underflow of `e^{a(t−u)}` to `+0` is vanishing dissipation and is kept. `e^{A(t−u)} M x` is not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle et al. (2012, Eq. 14). An impulse at `u = t` is the contemporaneous map. An impulse at `u ≤ t0` is already in `η(t0)`. The observed mean of that carry is `τ + λ(μ_t + e^{a(t−u)} m x)` (Driver et al., 2017, Eq. 5 of the Eq. 1–2 carried latent mean). The evolved map `τ + λ μ_t` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean when `u ≠ t`. `MANIFESTMEANS` is not `E(y_t)`. The carried latent mean is not `E(y_t)`. The first-occasion time-independent predictor shift is `t0_b z` (Driver et al., 2017, Table 3 `T0TIPREDEFFECT`; Eq. 3 first summand). Form `t0_b z` first, then `e^{a Δt} t0_b z`. Form `μ_t` first, then add that carry. A zero drift is `t0_b z`. Underflow of `e^{a Δt}` to `+0` is a vanishing carry of the first-occasion shift and is kept. `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`. `e^{A Δt} t0_b z` is not `t0_b z`. `T0TIPREDEFFECT` is the coefficient, not the shift. The observed mean of that first-occasion carry is `τ + λ(μ_t + e^{a Δt} t0_b z)` (Driver et al., 2017, Eq. 5 of the Table 3 / Eq. 3 first-summand composition). The evolved map `τ + λ μ_t` is not that observed mean. The process-increment map `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The impulse-carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t0`. The evolved-plus-carry latent mean is not `E(y_t)`. The first-occasion time-dependent predictor shift is `t0_m x0` (Driver et al., 2017, Table 3 `T0TDPREDEFFECT`; Eq. 3 first summand; JSS PDF re-opened 2026-08-20T19:10Z). Form `t0_m x0` first, then `e^{a Δt} t0_m x0`. Form `μ_t` first, then add that carry. A zero drift is `t0_m x0`. Underflow of `e^{a Δt}` to `+0` is a vanishing carry of the first-occasion shift and is kept. `t0_m x0` is not `M x`, not `e^{A(t−u)} M x` for `t0 < u < t`, not `t0_b z`, not `A^{-1}[e^{A Δt} − I] B z`, and not `CINT`. `e^{A Δt} t0_m x0` is not `t0_m x0`. `T0TDPREDEFFECT` is the coefficient, not the shift. An impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`. The observed mean of that first-occasion TD carry is `τ + λ(μ_t + e^{a Δt} t0_m x0)` (Driver et al., 2017, Eq. 5 of the Table 3 / Eq. 3 first-summand TD composition; JSS PDF re-opened 2026-08-20T19:07Z). The evolved map `τ + λ μ_t` is not that observed mean. The process-increment map `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The impulse-carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t0`. The first-occasion TI map `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean. The evolved-plus-carry latent mean is not `E(y_t)`. The lasting level-change `CINT` is `κ = −a m x` (Driver et al., 2017, §7.2, pp. 20–21; JSS PDF re-opened 2026-08-20T19:45Z). Form `m x` first, then multiply by `−a`. Stable `a < 0` is required so `−κ / a = m x` is an equilibrium offset. `a ≥ 0` cannot hold a new process mean. `−a m x` is not the dissipating Dirac `m x`, not a free `CINT`, and not `A^{-1}[e^{A Δt} − I] B z`. The extra near-zero-drift latent process also named in §7.2 is a different specification and is not this `CINT` setting. Equation 3 maps that intercept as `(1 − e^{a Δt}) m x` (JSS PDF re-opened 2026-08-20T19:50Z). Form the level-change `CINT` first, then the discrete intercept map. Underflow of `e^{a Δt}` to `+0` keeps `m x`. `(1 − e^{a Δt}) m x` is not `m x`, not `κ`, and not `A^{-1}[e^{A Δt} − I] B z`. The printed §7.2 lasting level change is an extra near-zero-drift latent process (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-20T23:10Z). `T0MEANS`, `CINT`, `T0VAR`, `DIFFUSION`, and `TRAITVAR` of that process are fixed to 0; `TDPREDEFFECT` on it is fixed to 1; its `DRIFT` diagonal is very close to 0 (printed example `−0.000001`; precisely 0 causes computational problems); the original process is driven by the `DRIFT` coupling `a_{ηξ}`. After a unit identification impulse the scalar contribution is `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (`ε = a` is `a_{ηξ} x Δt e^{a Δt}`). Form `a_{ηξ} x` first. A zero coupling or zero predictor is exactly zero. `ε ≥ 0` fails closed. That contribution is not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`. The observed mean of that extra-process contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a))` (Driver et al., 2017, Eq. 5 of that §7.2 contribution; JSS PDF re-opened 2026-08-21T06:12Z). The extra process has `LAMBDA` 0 and is not an observed indicator. Original indicators load on the original process after the `DRIFT` coupling. The evolved map `τ + λ μ_t` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The contribution is not `E(y_t)`. The evolved-plus-contribution latent mean is not `E(y_t)`. `T0TDPREDEFFECT` on the extra process begins at `t = 0` and uses `Δt = t − t0` for both the original-process evolution and the extra drive. `TDPREDEFFECT` after `t0` uses `t − u` with `t0 < u < t` for the extra drive while `μ_t` still uses `Δt`. The observed mean of that after-t0 extra-process contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a))` (Driver et al., 2017, Eq. 5 of that §7.2 after-t0 contribution; JSS PDF re-opened 2026-08-21T06:32Z). The first-occasion extra-process observed mean is not that observed mean when `u ≠ t0`. The impulse-carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is a Dirac on the original process and is not that `DRIFT` drive. An impulse at `u = t0` or `u = t` is not interior. The asymptotic time-independent predictor effect is `-B z / a` (Driver et al., 2017, §7.2, pp. 20–21; JSS PDF opened 2026-08-21T13:08Z). Form `B z` first, then divide by `-a`. Stable `a < 0` is required. `a ≥ 0` cannot hold a finite process-mean change. `-B z / a` is not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`. The asymptotic time-independent predictor variance is `(B / a)² v` (Driver et al., 2017, §7.2, pp. 20–21 `addedTIPREDVAR`). Form the unit asymptotic effect first, then square, then multiply by `v`. `(B / a)² v` is not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`. The asymptotic continuous intercept is `-κ / a` (Driver et al., 2017, Table 2, p. 12 `asymCINT`; Eq. 3 as `Δt → ∞`; JSS PDF opened 2026-08-21T16:13Z). Form `κ` first, then divide by `-a`. Stable `a < 0` is required. `-κ / a` is not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`. The p. 16 stationary `T0MEANS` constraint is `-κ / a + −B z / a`. Form the intercept contribution first, then include the TI extra effect, then add. That constrained first-occasion mean is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean. Equation 5 of that constrained mean is `τ + λ(−κ / a + −B z / a)` (Driver et al., 2017, §4.3, pp. 9–10; Eq. 5, p. 5; JSS PDF re-opened 2026-08-21T20:07Z). Form the stationary latent mean first, then `τ + λ` of that mean. `τ + λ μ_0` for free `T0MEANS` is not that composition. `τ + λ(−κ / a)` is not that composition when `B z ≠ 0`. `τ + λ μ_t` is not that composition. `MANIFESTMEANS` is not `E(y_0)`. The constrained latent mean is not `E(y_0)`. The p. 16 constrained first-occasion variance `trait + −q / (2 a) + (B / a)² v` is not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`). The lagged covariance of that constrained process is `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T19:13Z). Trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`. Contemporaneous `T0VAR` is not that lagged map. Decaying the constrained total as if it were all state is not that lagged map. Equation 5 of that lagged covariance is `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ`. `Θ` does not enter. Contemporaneous `Var(y_0)` is not that lagged observed covariance. The lagged latent covariance is not that observed covariance. The later-occasion variance of that constrained process is `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T23:12Z). Trait and `addedTIPREDVAR` do not enter `Q_Δt`. Under stationarity that composition equals contemporaneous `T0VAR`. Evolving the constrained total as if it were all state is not that later map. The lagged covariance omits `Q_Δt` and is not that later map. `Q_Δt` is not that later map. Equation 5 of that later-occasion variance is `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ`. The lagged observed covariance omits `Q_Δt` and `θ`. `MANIFESTVAR` is not `Var(y_t)`. The later-occasion latent variance is not `Var(y_t)`. The later-occasion variance of §4.3 predetermined `T0VAR` is `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T05:12Z). Trait and `addedTIPREDVAR` do not enter `Q_Δt`. Free `T0VAR` `p_0` is not that later map. Setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map. Stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free. Evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map. As `Δt → ∞` with stable `a < 0` the composition approaches contemporaneous stationary `T0VAR`. As `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`. Nonzero diffusion with `a ≥ 0` is a growing process and is kept. Equation 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ`. `MANIFESTVAR` is not `Var(y_t)`. The predetermined later-occasion latent variance is not `Var(y_t)`. Stationary later observed variance is not that observed variance when `p_0` is free. The lagged covariance of §4.3 predetermined `T0VAR` is `trait + e^{a Δt} p_0 + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T09:04Z). Trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`. Free `T0VAR` `p_0` is not that lagged map. Setting `p_0 = −q / (2 a)` recovers the stationary lagged map. Stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free. Evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map. Later-occasion variance includes `Q_Δt` and is not that lagged map. As `Δt → ∞` with stable `a < 0` the state term vanishes. As `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`. Equation 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ`. `MANIFESTVAR` does not enter. The predetermined lagged latent covariance is not that observed covariance. Predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance. Stationary lagged observed covariance is not that observed covariance when `p_0` is free. The predetermined first-occasion variance of §4.3 predetermined `T0VAR` is `trait + p_0 + (B / a)² v`. Free `p_0` is not that map. Stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free. Lagged covariance decays the state and is not that map. Later-occasion variance includes `Q_Δt` and is not that map. Equation 5 of that predetermined first-occasion variance is `λ²(trait + p_0 + (B / a)² v) + θ + ψ`. `MANIFESTVAR` is not that first-occasion observed variance. The predetermined first-occasion latent variance is not that observed variance. Stationary first-occasion observed variance is not that observed variance when `p_0` is free. Predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance. Later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z). First-occasion lagged omits `e^{a s} Q_u`. Later-occasion variance does not lag. Stationary lagged uses `−q / (2 a)`. Decaying the later total is not that map. Equation 5 of that later-start lagged covariance is `λ²` of it plus `ψ`. Independent `ε_t` does not enter. First-occasion lagged observed omits `e^{a s} Q_u`. Predetermined later observed variance includes `Q_u` and `θ` and is not that later-start lagged observed covariance. Later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z). Later-occasion variance at `u` omits `Q_s`. Later-start lagged covariance omits `Q_s`. Stationary later uses `−q / (2 a)`. Evolving the later total as if it were all state is not that map. Ignoring `startoffset` omits `e^{2 a s} Q_u`. Equation 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`. `MANIFESTVAR` is not that observed variance. Page 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; footnote 4; §7.1; JSS PDF re-opened 2026-08-23T11:40Z). Unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`. The §7.1 trait-plus-state autocorrelation `(trait + e^{a Δt} p + added) / (trait + p + added)` uses `TRAITVAR` and is not `discreteDRIFTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:06Z). Unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`. The continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`. `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`. `TRAITVAR` is not the standardisation variance. Page 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z). Unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`. The discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`. `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`. `TRAITVAR` is not the standardisation variance. Page 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z). Unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`. The discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`. `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z). Unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`. The finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`. `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z). Unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`. The asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`. The finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`. `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Page 16 / Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z). The affected variance is free first-occasion `T0VAR`, not `asymDIFFUSION`. Unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`. `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`. `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`. `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. 2017-era `addedT0TIPREDVAR` is `t0_b² v` after a first-occasion time-independent predictor (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z). Form `t0_b` first, then square, then multiply by `v`. A zero coefficient or zero predictor variance is exactly zero. Free `T0TIPREDEFFECT` does not require `a < 0`. `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map. `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance. Free `T0VAR` is not this extra TI variance. `TRAITVAR` is not this extra TI variance. Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z). Form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`. A zero loading or zero extra is exactly zero. `t0_b² v` is the latent extra, not the observed extra. `λ² p_0 + θ` is first-occasion observed variance, not this extra. `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra. `MANIFESTVAR` `θ` is not this extra. Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v` (Driver et al., 2017, Eq. 5, p. 5; Table 2, p. 12; §7.2, pp. 20–21; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:23Z). Form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`. A zero loading or zero extra is exactly zero. Lasting asymptotic extra requires `a < 0`. `(B / a)² v` is the latent extra, not the observed extra. `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra. `λ² p + θ` is stationary observed variance, not this extra. `MANIFESTVAR` `θ` is not this extra. Page 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance. Unstandardised `M` is not `TDPREDEFFECTstd`. `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`. intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`. `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`. Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance. Unstandardised `t0_m` is not `T0TDPREDEFFECTstd`. `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`. `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`. `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`. Free `T0VAR` does not require `a < 0`. Page 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; the default ridge is 0). Unstandardised `T0VAR` is not `T0VARstd`. `T0TDPREDEFFECTstd` is not `T0VARstd`. `addedT0TIPREDVAR` is not `T0VARstd`. Page 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend). Unstandardised `TRAITVAR` is not `TRAITVARstd`. `T0VARstd` is not `TRAITVARstd` even when both equal 1. `addedT0TIPREDVAR` is not `TRAITVARstd`. Page 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0). Unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`. `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1. `MANIFESTVAR` is not `MANIFESTTRAITVARstd`. Page 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug). Unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`. `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1. Equation 5 `Var(y)` is not `MANIFESTVARstd`. Page 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`). Unstandardised `TIPREDVAR` is not `TIPREDVARstd`. `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1. Section 7.2 `addedTIPREDVAR` is not `TIPREDVARstd`. Page 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`). Unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`. `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1. `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`. Page 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`. Unstandardised `discreteCINT` is not `discreteCINTstd`. `κ / √p` is not `discreteCINTstd`. `(-κ / a) / √p` is not `discreteCINTstd`. `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`. Unstandardised `asymCINT` is not `asymCINTstd`. `κ / √p` is not `asymCINTstd`. `discreteCINTstd` is not `asymCINTstd`. `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`. Unstandardised `T0MEANS` is not `T0MEANSstd`. `T0VARstd` is not `T0MEANSstd`. `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`. Page 16 `MANIFESTMEANSstd` is `τ / √θ` after strictly positive `MANIFESTVAR`. Unstandardised `MANIFESTMEANS` is not `MANIFESTMEANSstd`. `MANIFESTVARstd` is not `MANIFESTMEANSstd`. `τ / √(λ² Var(η) + θ)` is not `MANIFESTMEANSstd`. Page 16 `CINTstd` is `κ / √p` after strictly positive `asymDIFFUSION`. Unstandardised `CINT` is not `CINTstd`. `asymCINTstd` is not `CINTstd`. `discreteCINTstd` is not `CINTstd`. `κ / √(trait + p + added)` is not `CINTstd`. Evolving from that stationary start with `CINT` and `TIPREDEFFECT` stays at the stationary mean. Equation 1 is the latent SDE, not the measurement model. Form `(λ p) λ` then add `θ`, then add `ψ`. `MANIFESTVAR` is `Θ`, not `Var(y)`. `MANIFESTTRAITVAR` is `Ψ_τ`, not `Θ`. `TRAITVAR` is latent and scaled by `λ²`. `Var(η)` is not `Var(y)`. - Separate cluster means before within-unit lag. CWC plus an event-time lag is not DSEM. Subtracting the person-specific mean from a raw autoregressive series does not isolate the lagged within-person effect (Curran & Bauer, 2011, pp. 607–608); already-centered residuals with irregular event intervals use the exact scalar map. - Do not treat the CWC cluster-mean coefficient as the between-cluster effect. It is the contextual effect `between − within` (Enders & Tofighi, 2007, Table 2, pp. 124–127). - Never use future-available evidence in historical model fits. diff --git a/crates/longitudinal_core/Cargo.toml b/crates/longitudinal_core/Cargo.toml index 72828a1af..19b9635bc 100644 --- a/crates/longitudinal_core/Cargo.toml +++ b/crates/longitudinal_core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longitudinal_core" -description = "Within/between decomposition gates and component RMSE." +description = "Longitudinal modeling gates, within/between decomposition, temporal association standardization, and recovery metrics." version.workspace = true edition.workspace = true rust-version.workspace = true diff --git a/crates/longitudinal_core/src/association.rs b/crates/longitudinal_core/src/association.rs new file mode 100644 index 000000000..a50bdb99c --- /dev/null +++ b/crates/longitudinal_core/src/association.rs @@ -0,0 +1,137 @@ +//! Event-time lagged association standardization. +//! +//! This module belongs to the Longitudinal Modeling bounded context. It +//! standardizes a lagged covariance only when both marginal variances are +//! available. A one-sided covariance/initial-variance ratio is deliberately +//! not exposed as an autocorrelation. + +use crate::LongitudinalError; + +/// Recover a Pearson correlation for an event-time lag from its covariance and +/// both marginal variances. +/// +/// For observations at event times `t` and `t + Δ`, the correlation is +/// +/// `Cov(Y_t, Y_{t+Δ}) / sqrt(Var(Y_t) * Var(Y_{t+Δ}))`. +/// +/// Requiring both marginals is essential for nonstationary processes. Driver, +/// Oud, and Voelkle (2017) provide the continuous-time state-transition and +/// covariance components from which occasion-specific marginals can be built; +/// they do not justify replacing the second marginal variance with the first +/// when the process is nonstationary. +/// +/// `event_interval` is semantically required to be strictly positive so a +/// measurement-occasion or method facet cannot be passed as an untyped lag. +/// This function does not infer either marginal variance and does not estimate +/// a state process. +/// +/// # Errors +/// +/// Returns [`LongitudinalError::InvalidTemporalAssociationInput`] for +/// non-finite inputs, [`LongitudinalError::NonPositiveMarginalVariance`] when +/// either marginal variance is not strictly positive, +/// [`LongitudinalError::NonPositiveEventInterval`] for a non-positive event +/// interval, and [`LongitudinalError::CovarianceBoundViolation`] when the +/// supplied covariance is incompatible with the two marginal variances. +pub fn recover_event_time_lagged_correlation( + lagged_covariance: f64, + earlier_total_variance: f64, + later_total_variance: f64, + event_interval: f64, +) -> Result { + if !lagged_covariance.is_finite() + || !earlier_total_variance.is_finite() + || !later_total_variance.is_finite() + || !event_interval.is_finite() + { + return Err(LongitudinalError::InvalidTemporalAssociationInput); + } + if earlier_total_variance <= 0.0 || later_total_variance <= 0.0 { + return Err(LongitudinalError::NonPositiveMarginalVariance); + } + if event_interval <= 0.0 { + return Err(LongitudinalError::NonPositiveEventInterval); + } + + // sqrt(v1) * sqrt(v2) avoids the avoidable overflow of sqrt(v1 * v2). + let denominator = earlier_total_variance.sqrt() * later_total_variance.sqrt(); + if !denominator.is_finite() || denominator <= 0.0 { + return Err(LongitudinalError::InvalidTemporalAssociationInput); + } + let correlation = lagged_covariance / denominator; + if !correlation.is_finite() { + return Err(LongitudinalError::InvalidTemporalAssociationInput); + } + if correlation.abs() > 1.0 { + return Err(LongitudinalError::CovarianceBoundViolation); + } + Ok(correlation) +} + +#[cfg(test)] +mod tests { + use super::recover_event_time_lagged_correlation; + use crate::LongitudinalError; + + #[test] + fn nonstationary_marginals_do_not_use_the_earlier_variance_twice() { + // The retired one-sided ratio would be 1.5 and therefore impossible as + // an autocorrelation. Supplying the later marginal gives 0.75. + let recovered = recover_event_time_lagged_correlation(1.5, 1.0, 4.0, 1.0) + .expect("valid nonstationary covariance should standardize"); + assert!((recovered - 0.75).abs() < f64::EPSILON * 4.0); + } + + #[test] + fn covariance_bound_is_fail_closed() { + assert_eq!( + recover_event_time_lagged_correlation(2.000_000_000_1, 1.0, 4.0, 1.0), + Err(LongitudinalError::CovarianceBoundViolation) + ); + assert_eq!( + recover_event_time_lagged_correlation(-2.000_000_000_1, 1.0, 4.0, 1.0), + Err(LongitudinalError::CovarianceBoundViolation) + ); + } + + #[test] + fn exact_boundary_correlations_are_allowed() { + assert_eq!( + recover_event_time_lagged_correlation(2.0, 1.0, 4.0, 1.0), + Ok(1.0) + ); + assert_eq!( + recover_event_time_lagged_correlation(-2.0, 1.0, 4.0, 1.0), + Ok(-1.0) + ); + } + + #[test] + fn square_root_scaling_avoids_avoidable_variance_product_overflow() { + let variance = f64::MAX / 4.0; + let recovered = recover_event_time_lagged_correlation( + variance / 2.0, + variance, + variance, + 1.0, + ) + .expect("representable standardized covariance should remain representable"); + assert!((recovered - 0.5).abs() < 1.0e-15); + } + + #[test] + fn invalid_inputs_fail_closed() { + assert_eq!( + recover_event_time_lagged_correlation(f64::NAN, 1.0, 1.0, 1.0), + Err(LongitudinalError::InvalidTemporalAssociationInput) + ); + assert_eq!( + recover_event_time_lagged_correlation(0.0, 0.0, 1.0, 1.0), + Err(LongitudinalError::NonPositiveMarginalVariance) + ); + assert_eq!( + recover_event_time_lagged_correlation(0.0, 1.0, 1.0, 0.0), + Err(LongitudinalError::NonPositiveEventInterval) + ); + } +} diff --git a/crates/longitudinal_core/src/error.rs b/crates/longitudinal_core/src/error.rs index eb0dba3ab..cea0aa33b 100644 --- a/crates/longitudinal_core/src/error.rs +++ b/crates/longitudinal_core/src/error.rs @@ -1,8 +1,8 @@ -//! Fail-closed longitudinal within/between errors. +//! Fail-closed longitudinal modeling errors. use std::fmt; -/// A fail-closed longitudinal-decomposition error. +/// A fail-closed longitudinal-modeling error. #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[non_exhaustive] pub enum LongitudinalError { @@ -14,6 +14,14 @@ pub enum LongitudinalError { InvalidComponentPayload, /// Observations were empty, sparse, duplicated, or non-finite. InvalidObservationPayload, + /// A lagged covariance, marginal variance, or event interval was non-finite. + InvalidTemporalAssociationInput, + /// At least one marginal variance was not strictly positive. + NonPositiveMarginalVariance, + /// The event-time interval was not strictly positive. + NonPositiveEventInterval, + /// The covariance violates the Cauchy-Schwarz bound implied by the two marginals. + CovarianceBoundViolation, } impl fmt::Display for LongitudinalError { @@ -23,6 +31,16 @@ impl fmt::Display for LongitudinalError { Self::UnknownComponentLevel => "unknown component level", Self::InvalidComponentPayload => "invalid longitudinal component payload", Self::InvalidObservationPayload => "invalid longitudinal observation payload", + Self::InvalidTemporalAssociationInput => "invalid temporal association input", + Self::NonPositiveMarginalVariance => { + "temporal correlation requires strictly positive marginal variances" + } + Self::NonPositiveEventInterval => { + "temporal correlation requires a strictly positive event-time interval" + } + Self::CovarianceBoundViolation => { + "lagged covariance is incompatible with the supplied marginal variances" + } }; formatter.write_str(message) } @@ -53,6 +71,22 @@ mod tests { LongitudinalError::InvalidObservationPayload, "invalid longitudinal observation payload", ), + ( + LongitudinalError::InvalidTemporalAssociationInput, + "invalid temporal association input", + ), + ( + LongitudinalError::NonPositiveMarginalVariance, + "temporal correlation requires strictly positive marginal variances", + ), + ( + LongitudinalError::NonPositiveEventInterval, + "temporal correlation requires a strictly positive event-time interval", + ), + ( + LongitudinalError::CovarianceBoundViolation, + "lagged covariance is incompatible with the supplied marginal variances", + ), ] { assert_eq!(error.to_string(), message); } diff --git a/crates/longitudinal_core/src/lib.rs b/crates/longitudinal_core/src/lib.rs index 31996b824..a6d748046 100644 --- a/crates/longitudinal_core/src/lib.rs +++ b/crates/longitudinal_core/src/lib.rs @@ -1,16 +1,22 @@ #![forbid(unsafe_code)] #![deny(missing_docs)] #![allow(clippy::cast_precision_loss)] -//! Within/between decomposition for longitudinal scores. +//! Longitudinal modeling primitives for temporal psychometrics. //! //! Stable between-unit components cannot be scored as within-unit change. -//! Recovery reports computed component RMSE against known truth (ADR 0005). +//! Event-time lagged associations require both marginal variances before a +//! covariance can be standardized as a correlation. Recovery reports computed +//! component RMSE against known truth (ADR 0005). +mod association; mod component; mod decompose; mod error; mod level; +/// Recover a valid event-time lagged correlation from covariance and both +/// marginal variances. +pub use association::recover_event_time_lagged_correlation; /// One unit-specific within or between component. pub use component::ComponentValue; /// RMSE of recovered components against known truth. @@ -19,7 +25,7 @@ pub use component::component_root_mean_square_error; pub use decompose::OccasionObservation; /// Decompose occasion scores into unit means and within residuals. pub use decompose::decompose_within_between; -/// Fail-closed longitudinal-decomposition errors. +/// Fail-closed longitudinal-modeling errors. pub use error::LongitudinalError; /// Established longitudinal component level. pub use level::ComponentLevel; diff --git a/crates/psychometric_core/src/error.rs b/crates/psychometric_core/src/error.rs index ada153a86..4ab2695e0 100644 --- a/crates/psychometric_core/src/error.rs +++ b/crates/psychometric_core/src/error.rs @@ -710,23 +710,6 @@ pub enum PsychometricError { /// `MANIFESTVARstd`. `λ² Var(η) + θ` is `Var(y)`, not the /// correlation form of `Θ`. ObservedVarianceIsNotStandardisedManifestVariance, - /// Driver §7.1 trait-plus-state expected autocorrelation was - /// requested with a zero total variance. The covariance ratio - /// `(trait + e^{a Δt} p + added) / (trait + p + added)` is - /// undefined at a zero denominator. - TraitPlusStateExpectedAutocorrelationRequiresPositiveTotalVariance, - /// Driver §7.1 trait-plus-state expected autocorrelation was - /// treated as unstandardised `discreteDRIFT`. The total-variance - /// ratio is not `e^{a Δt}` when `TRAITVAR` or `addedTIPREDVAR` - /// is nonzero. Equal numbers when both vanish remain distinct - /// named quantities. - TraitPlusStateExpectedAutocorrelationIsNotDiscreteDrift, - /// Driver §7.1 trait-plus-state expected autocorrelation was - /// treated as p. 16 `discreteDRIFTstd`. Footnote 4 standardises - /// `DRIFT` using only within-subject `asymDIFFUSION`, not the - /// total. The autocorrelation uses `TRAITVAR` and is not that - /// map. - TraitPlusStateExpectedAutocorrelationIsNotStandardisedDiscreteDrift, } impl fmt::Display for PsychometricError { @@ -1252,15 +1235,6 @@ impl fmt::Display for PsychometricError { Self::ObservedVarianceIsNotStandardisedManifestVariance => { "observed-indicator variance is not standardised measurement-error variance" } - Self::TraitPlusStateExpectedAutocorrelationRequiresPositiveTotalVariance => { - "trait-plus-state expected autocorrelation requires a strictly positive total variance" - } - Self::TraitPlusStateExpectedAutocorrelationIsNotDiscreteDrift => { - "trait-plus-state expected autocorrelation is not discrete drift" - } - Self::TraitPlusStateExpectedAutocorrelationIsNotStandardisedDiscreteDrift => { - "trait-plus-state expected autocorrelation is not standardised discrete drift" - } }; formatter.write_str(message) } @@ -2099,22 +2073,4 @@ mod tests { "measurement error is not standardised manifest-trait variance" ); } - - #[test] - fn trait_plus_state_expected_autocorrelation_boundary_messages_are_stable() { - assert_eq!( - PsychometricError::TraitPlusStateExpectedAutocorrelationRequiresPositiveTotalVariance - .to_string(), - "trait-plus-state expected autocorrelation requires a strictly positive total variance" - ); - assert_eq!( - PsychometricError::TraitPlusStateExpectedAutocorrelationIsNotDiscreteDrift.to_string(), - "trait-plus-state expected autocorrelation is not discrete drift" - ); - assert_eq!( - PsychometricError::TraitPlusStateExpectedAutocorrelationIsNotStandardisedDiscreteDrift - .to_string(), - "trait-plus-state expected autocorrelation is not standardised discrete drift" - ); - } } diff --git a/crates/psychometric_core/src/event_time.rs b/crates/psychometric_core/src/event_time.rs index ca2ca18f4..a29bc5c18 100644 --- a/crates/psychometric_core/src/event_time.rs +++ b/crates/psychometric_core/src/event_time.rs @@ -917,122 +917,6 @@ pub fn recover_trait_plus_state_lagged_covariance( require_finite(trait_variance + state_lagged) } -/// Exact scalar §7.1 trait-plus-state expected autocorrelation. -/// -/// Driver, Oud, and Voelkle (2017, §7.1, pp. 18–19; Eq. 3–4, pp. 4–5; -/// §4.3, p. 9; §7.2 `addedTIPREDVAR`; JSS PDF re-opened -/// 2026-09-01T06:30Z from -/// ) -/// name traits the stable between-subject differences (unit-level -/// unobserved heterogeneity). Omitting `TRAITVAR` mixes between- and -/// within-person information (Balestra & Nerlove, 1966; Oud & Jansen, -/// 2000, unread; Halaby, 2004). Figure 4 shows auto-effects -/// (persistence) reduced after traits are modelled. The paper does -/// not print this ratio; it is the covariance ratio of the already -/// recovered §4.3 maps. Form the lagged covariance -/// `trait + e^{a Δt} p` first, then the contemporaneous total -/// `trait + p`, then include `addedTIPREDVAR`, then the ratio -/// `(trait + e^{a Δt} p + added) / (trait + p + added)`. A zero -/// trait and a zero TI extra recovers `e^{a Δt}` numerically and -/// remains a distinct named quantity. A zero state with a positive -/// trait or extra is exactly 1. A zero total variance has no -/// autocorrelation and fails closed. Growing `a > 0` is a covariance -/// ratio of those named maps and is kept; it is not a stationary -/// correlation and is not `discreteDRIFTstd` (which requires stable -/// `a < 0` and strictly positive `asymDIFFUSION`). Unstandardised -/// `discreteDRIFT` `e^{a Δt}` is the state auto-effect, not this -/// total-variance ratio. Page 16 `discreteDRIFTstd` standardises -/// `DRIFT` using only within-subject `asymDIFFUSION` (footnote 4), -/// not the total. `TRAITVAR` is not the standardisation variance. -/// The interval must be event time and strictly positive. This is -/// not a Kalman filter, not a matrix `expm`, not RI-CLPM, not DSEM, -/// and not ctsem estimation. -/// -/// # Errors -/// -/// Propagates [`recover_trait_plus_state_lagged_covariance`] and -/// [`recover_trait_plus_state_latent_variance`]. Returns -/// [`PsychometricError::InvalidNumericInput`] when the TI extra -/// variance is negative or non-finite or a sum or ratio overflows, -/// and -/// [`PsychometricError::TraitPlusStateExpectedAutocorrelationRequiresPositiveTotalVariance`] -/// when `trait + p + added` is zero. -pub fn recover_trait_plus_state_expected_autocorrelation( - trait_variance: f64, - state_variance: f64, - added_time_independent_variance: f64, - log_rate: f64, - event_delta: f64, - clock: LagClock, -) -> Result { - if !added_time_independent_variance.is_finite() || added_time_independent_variance < 0.0 { - return Err(PsychometricError::InvalidNumericInput); - } - let lagged = recover_trait_plus_state_lagged_covariance( - trait_variance, - state_variance, - log_rate, - event_delta, - clock, - )?; - let contemporaneous = recover_trait_plus_state_latent_variance(trait_variance, state_variance)?; - let numerator = if added_time_independent_variance == 0.0 { - lagged - } else { - require_finite(lagged + added_time_independent_variance)? - }; - let denominator = if added_time_independent_variance == 0.0 { - contemporaneous - } else { - require_finite(contemporaneous + added_time_independent_variance)? - }; - if denominator == 0.0 { - return Err( - PsychometricError::TraitPlusStateExpectedAutocorrelationRequiresPositiveTotalVariance, - ); - } - require_finite(numerator / denominator) -} - -/// Refuse treating §7.1 trait-plus-state expected autocorrelation as -/// unstandardised `discreteDRIFT`. -/// -/// `(trait + e^{a Δt} p + added) / (trait + p + added)` is the -/// total-variance ratio. `e^{a Δt}` is the state auto-effect. -/// Equal numbers when trait and added vanish remain distinct named -/// quantities. -/// -/// # Errors -/// -/// Always returns -/// [`PsychometricError::TraitPlusStateExpectedAutocorrelationIsNotDiscreteDrift`]. -pub fn refuse_trait_plus_state_expected_autocorrelation_as_discrete_drift( - expected_autocorrelation: f64, - discrete_drift: f64, -) -> Result { - let _ = (expected_autocorrelation, discrete_drift); - Err(PsychometricError::TraitPlusStateExpectedAutocorrelationIsNotDiscreteDrift) -} - -/// Refuse treating §7.1 trait-plus-state expected autocorrelation as -/// p. 16 `discreteDRIFTstd`. -/// -/// Footnote 4 standardises `DRIFT` using only within-subject -/// `asymDIFFUSION`, not the total. The autocorrelation uses -/// `TRAITVAR` and is not that map. -/// -/// # Errors -/// -/// Always returns -/// [`PsychometricError::TraitPlusStateExpectedAutocorrelationIsNotStandardisedDiscreteDrift`]. -pub fn refuse_trait_plus_state_expected_autocorrelation_as_standardised_discrete_drift( - expected_autocorrelation: f64, - standardised_discrete_drift: f64, -) -> Result { - let _ = (expected_autocorrelation, standardised_discrete_drift); - Err(PsychometricError::TraitPlusStateExpectedAutocorrelationIsNotStandardisedDiscreteDrift) -} - /// Refuse treating Driver §4.3 trait variance as process noise. /// /// A stable trait has `DIFFUSION` fixed to zero. The ctsem @@ -6969,8 +6853,8 @@ pub(crate) fn fit_scalar_log_rate(pairs: &[(f64, f64, f64)]) -> Result 0.28); - assert_eq!( - recover_trait_plus_state_expected_autocorrelation( - 0.0, - state_variance, - 0.0, - drift, - delta, - LagClock::EventTime, - ), - Ok(discrete_drift) - ); - assert_eq!( - recover_trait_plus_state_expected_autocorrelation( - trait_variance, - 0.0, - added, - drift, - delta, - LagClock::EventTime, - ), - Ok(1.0) - ); - let far = recover_trait_plus_state_expected_autocorrelation( - trait_variance, - state_variance, - added, - drift, - 700.0, - LagClock::EventTime, - ) - .expect("Δt→∞"); - let asymptotic = (trait_variance + added) / (trait_variance + state_variance + added); - assert!((far - asymptotic).abs() < 1e-15); - let growing = recover_trait_plus_state_expected_autocorrelation( - trait_variance, - state_variance, - added, - 0.5, - delta, - LagClock::EventTime, - ) - .expect("a>0"); - assert!(growing > 1.0); - assert_eq!( - refuse_trait_plus_state_expected_autocorrelation_as_discrete_drift( - recovered, - discrete_drift - ), - Err(PsychometricError::TraitPlusStateExpectedAutocorrelationIsNotDiscreteDrift) - ); - assert_eq!( - refuse_trait_plus_state_expected_autocorrelation_as_standardised_discrete_drift( - recovered, - discrete_drift - ), - Err( - PsychometricError::TraitPlusStateExpectedAutocorrelationIsNotStandardisedDiscreteDrift - ) - ); - } - - #[test] - fn trait_plus_state_expected_autocorrelation_invalid_inputs_fail_closed() { - assert_eq!( - recover_trait_plus_state_expected_autocorrelation( - 0.0, - 0.0, - 0.0, - -0.5, - 1.0, - LagClock::EventTime, - ), - Err( - PsychometricError::TraitPlusStateExpectedAutocorrelationRequiresPositiveTotalVariance - ) - ); - assert_eq!( - recover_trait_plus_state_expected_autocorrelation( - 0.8, - 0.4, - -0.1, - -0.5, - 1.0, - LagClock::EventTime, - ), - Err(PsychometricError::InvalidNumericInput) - ); - assert_eq!( - recover_trait_plus_state_expected_autocorrelation( - 0.8, - 0.4, - f64::NAN, - -0.5, - 1.0, - LagClock::EventTime, - ), - Err(PsychometricError::InvalidNumericInput) - ); - assert_eq!( - recover_trait_plus_state_expected_autocorrelation( - 0.8, - 0.4, - f64::INFINITY, - -0.5, - 1.0, - LagClock::EventTime, - ), - Err(PsychometricError::InvalidNumericInput) - ); - assert_eq!( - recover_trait_plus_state_expected_autocorrelation( - 0.8, - 0.4, - 0.2, - -0.5, - 1.0, - LagClock::SystemTime, - ), - Err(PsychometricError::EventTimeRequired) - ); - assert_eq!( - recover_trait_plus_state_expected_autocorrelation( - 0.8, - 0.4, - 0.2, - -0.5, - 0.0, - LagClock::EventTime, - ), - Err(PsychometricError::NonPositiveInterval) - ); - assert_eq!( - recover_trait_plus_state_expected_autocorrelation( - -0.1, - 0.4, - 0.2, - -0.5, - 1.0, - LagClock::EventTime, - ), - Err(PsychometricError::InvalidNumericInput) - ); - assert_eq!( - recover_trait_plus_state_expected_autocorrelation( - 1e308, - 0.0, - 1e308, - 0.0, - 1.0, - LagClock::EventTime, - ), - Err(PsychometricError::InvalidNumericInput) - ); - assert_eq!( - recover_trait_plus_state_expected_autocorrelation( - 0.0, - 1e308, - 1e308, - -800.0, - 1.0, - LagClock::EventTime, - ), - Err(PsychometricError::InvalidNumericInput) - ); - } - #[test] fn non_event_clocks_and_difference_quotient_fail_closed() { for clock in [ @@ -11466,8 +11147,8 @@ mod tests { } #[test] - fn stationary_initial_observed_variance_recovers_driver_equation_five_of_section_four_point_three( - ) { + fn stationary_initial_observed_variance_recovers_driver_equation_five_of_section_four_point_three() + { // Driver et al. (2017, §4.3, pp. 9–10; Eq. 5, p. 5) // constrain first-occasion variances to the model-predicted // variance. Equation 5 maps Var(y_0) = λ² of that variance @@ -12042,8 +11723,8 @@ mod tests { } #[test] - fn stationary_lagged_observed_covariance_recovers_driver_equation_five_of_section_four_point_three( - ) { + fn stationary_lagged_observed_covariance_recovers_driver_equation_five_of_section_four_point_three() + { // Driver et al. (2017, §4.3, pp. 9–10; Eq. 5, p. 5) // lagged observed covariance of stationary T0VAR is // λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ. @@ -12619,8 +12300,8 @@ mod tests { #[test] #[allow(clippy::too_many_lines)] - fn stationary_later_observed_variance_recovers_driver_equation_five_of_section_four_point_three( - ) { + fn stationary_later_observed_variance_recovers_driver_equation_five_of_section_four_point_three() + { // Driver et al. (2017, §4.3, pp. 9–10; Eq. 5, p. 5) // later-occasion observed variance of stationary T0VAR is // λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ. @@ -14774,8 +14455,8 @@ mod tests { } #[test] - fn discrete_observed_mean_with_initial_time_independent_predictor_recovers_driver_equation_five( - ) { + fn discrete_observed_mean_with_initial_time_independent_predictor_recovers_driver_equation_five() + { let loading = 2.0_f64; let drift = -0.5_f64; let delta = 2.0_f64; @@ -14925,8 +14606,8 @@ mod tests { } #[test] - fn discrete_observed_mean_with_initial_time_independent_predictor_refuses_evolved_mean_and_overflow( - ) { + fn discrete_observed_mean_with_initial_time_independent_predictor_refuses_evolved_mean_and_overflow() + { let loading = 2.0_f64; let recovered = recover_discrete_observed_mean_with_initial_time_independent_predictor( loading, @@ -15544,8 +15225,8 @@ mod tests { } #[test] - fn discrete_observed_mean_with_initial_time_dependent_predictor_refuses_evolved_mean_and_overflow( - ) { + fn discrete_observed_mean_with_initial_time_dependent_predictor_refuses_evolved_mean_and_overflow() + { let recovered = recover_discrete_observed_mean_with_initial_time_dependent_predictor( 2.0, 1.0, diff --git a/crates/psychometric_core/src/lib.rs b/crates/psychometric_core/src/lib.rs index d41b37093..c081a63f6 100644 --- a/crates/psychometric_core/src/lib.rs +++ b/crates/psychometric_core/src/lib.rs @@ -261,16 +261,6 @@ //! correlation; zero `MANIFESTTRAITVAR` fails closed; a non-event //! clock fails closed; `MANIFESTTRAITVAR` does not require `a < 0`; //! JSS PDF re-opened 2026-08-27T14:20Z), -//! recovers the Driver §7.1 trait-plus-state expected autocorrelation -//! `(trait + e^{a Δt} p + added) / (trait + p + added)` (form the -//! lagged covariance first, then the contemporaneous total, then -//! add `addedTIPREDVAR`, then the ratio; zero trait and zero added -//! recovers `e^{a Δt}` numerically and remains a distinct named -//! quantity; zero state with positive trait or extra is exactly 1; -//! zero total variance fails closed; `a > 0` is a growing covariance -//! ratio and is kept; unstandardised `e^{a Δt}` is not this map; -//! `discreteDRIFTstd` is not this map; JSS PDF re-opened -//! 2026-09-01T06:30Z), //! and refuses //! latent-mean comparison below strong invariance. @@ -452,8 +442,6 @@ pub use event_time::recover_stationary_later_observed_variance; pub use event_time::recover_time_dependent_predictor_impulse; /// Exact scalar within-interval `TDPREDEFFECT` carry `e^{A(t−u)} M x`. pub use event_time::recover_time_dependent_predictor_impulse_carry; -/// Exact scalar §7.1 trait-plus-state expected autocorrelation `(trait + e^{a Δt} p + added) / (trait + p + added)`. -pub use event_time::recover_trait_plus_state_expected_autocorrelation; /// Exact scalar trait-plus-state lagged covariance. pub use event_time::recover_trait_plus_state_lagged_covariance; /// Exact scalar trait-plus-state latent variance. @@ -727,10 +715,6 @@ pub use event_time::refuse_time_independent_effect_as_time_varying_discrete_effe pub use event_time::refuse_time_independent_observed_mean_as_initial_time_dependent_observed_mean; /// Refuse treating process-increment `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` as the first-occasion TI-predictor observed mean. pub use event_time::refuse_time_independent_observed_mean_as_initial_time_independent_observed_mean; -/// Refuse treating §7.1 trait-plus-state expected autocorrelation as unstandardised `discreteDRIFT`. -pub use event_time::refuse_trait_plus_state_expected_autocorrelation_as_discrete_drift; -/// Refuse treating §7.1 trait-plus-state expected autocorrelation as p. 16 `discreteDRIFTstd`. -pub use event_time::refuse_trait_plus_state_expected_autocorrelation_as_standardised_discrete_drift; /// Refuse treating §4.3 trait-plus-state lagged covariance as lagged stationary `T0VAR`. pub use event_time::refuse_trait_plus_state_lagged_covariance_as_stationary_lagged_latent_covariance; /// Refuse treating `κ / √(trait + p + added)` as `CINTstd`. 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 6bcf43faf..1c0027f44 100644 --- a/crates/psychometric_core/tests/multilevel_event_time_recovery_contract.rs +++ b/crates/psychometric_core/tests/multilevel_event_time_recovery_contract.rs @@ -2,8 +2,10 @@ #![allow(clippy::cast_precision_loss)] use psychometric_core::{ - map_discrete_lag_across_event_intervals, ordinary_least_squares_slope, - recover_asymptotic_continuous_intercept, recover_asymptotic_time_independent_predictor_effect, + ClusteredEventScore, ClusteredScore, EventOccasion, IndicatorKind, LagClock, + LaggedWithinResidual, PsychometricError, map_discrete_lag_across_event_intervals, + ordinary_least_squares_slope, recover_asymptotic_continuous_intercept, + recover_asymptotic_time_independent_predictor_effect, recover_asymptotic_time_independent_predictor_variance, recover_cluster_mean_within_between_slopes, recover_discrete_constant_predictor_effect, recover_discrete_continuous_intercept_effect, recover_discrete_lag_from_log_rate, @@ -43,8 +45,8 @@ use psychometric_core::{ recover_stationary_lagged_observed_covariance, recover_stationary_latent_variance, recover_stationary_later_latent_variance, recover_stationary_later_observed_variance, recover_time_dependent_predictor_impulse, recover_time_dependent_predictor_impulse_carry, - recover_trait_plus_state_expected_autocorrelation, recover_trait_plus_state_lagged_covariance, - recover_trait_plus_state_latent_variance, recover_within_residual_event_time_log_rate, + recover_trait_plus_state_lagged_covariance, recover_trait_plus_state_latent_variance, + recover_within_residual_event_time_log_rate, refuse_after_extra_process_contribution_as_observed_mean, refuse_after_extra_process_latent_mean_as_observed_mean, refuse_asymptotic_continuous_intercept_as_asymptotic_time_independent_effect, @@ -157,16 +159,12 @@ use psychometric_core::{ refuse_time_independent_effect_as_time_varying_discrete_effect, refuse_time_independent_observed_mean_as_initial_time_dependent_observed_mean, refuse_time_independent_observed_mean_as_initial_time_independent_observed_mean, - refuse_trait_plus_state_expected_autocorrelation_as_discrete_drift, - refuse_trait_plus_state_expected_autocorrelation_as_standardised_discrete_drift, 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_manifest_trait_variance_as_standardised_manifest_trait_variance, refuse_unstandardised_manifest_variance_as_standardised_manifest_variance, - refuse_unstandardised_trait_variance_as_standardised_trait_variance, ClusteredEventScore, - ClusteredScore, EventOccasion, IndicatorKind, LagClock, LaggedWithinResidual, - PsychometricError, + refuse_unstandardised_trait_variance_as_standardised_trait_variance, }; fn rmse(truth: &[f64], recovered: &[f64]) -> f64 { @@ -934,121 +932,6 @@ fn trait_plus_state_recovers_driver_section_four_point_three() { ); } -#[test] -fn trait_plus_state_expected_autocorrelation_recovers_driver_section_seven_point_one() { - let trait_variance = 0.8_f64; - let state_variance = 0.4_f64; - let added = 0.2_f64; - let drift = -0.5_f64; - let delta = 1.0_f64; - let recovered = recover_trait_plus_state_expected_autocorrelation( - trait_variance, - state_variance, - added, - drift, - delta, - LagClock::EventTime, - ) - .expect("ρ"); - let lagged = recover_trait_plus_state_lagged_covariance( - trait_variance, - state_variance, - drift, - delta, - LagClock::EventTime, - ) - .expect("lagged"); - let contemporaneous = - recover_trait_plus_state_latent_variance(trait_variance, state_variance).expect("total"); - let expected = (lagged + added) / (contemporaneous + added); - let error = rmse(&[expected], &[recovered]); - assert!( - error < 1e-15, - "Driver §7.1 trait-plus-state expected autocorrelation RMSE {error}" - ); - let discrete_drift = - recover_discrete_lag_from_log_rate(drift, delta, LagClock::EventTime).expect("φ"); - let collapsed = rmse(&[expected], &[discrete_drift]); - assert!( - collapsed > error, - "treating e^{{a Δt}} as the §7.1 autocorrelation is not that map: collapsed RMSE {collapsed} must exceed {error}" - ); - assert!((discrete_drift - recovered).abs() > 0.28); - assert_eq!( - recover_trait_plus_state_expected_autocorrelation( - 0.0, - state_variance, - 0.0, - drift, - delta, - LagClock::EventTime, - ), - Ok(discrete_drift) - ); - assert_eq!( - recover_trait_plus_state_expected_autocorrelation( - trait_variance, - 0.0, - added, - drift, - delta, - LagClock::EventTime, - ), - Ok(1.0) - ); - let far = recover_trait_plus_state_expected_autocorrelation( - trait_variance, - state_variance, - added, - drift, - 700.0, - LagClock::EventTime, - ) - .expect("Δt→∞"); - let asymptotic = (trait_variance + added) / (trait_variance + state_variance + added); - let far_error = rmse(&[asymptotic], &[far]); - assert!( - far_error < 1e-15, - "as Δt → ∞ the state term vanishes: RMSE {far_error}" - ); - assert_eq!( - refuse_trait_plus_state_expected_autocorrelation_as_discrete_drift( - recovered, - discrete_drift - ), - Err(PsychometricError::TraitPlusStateExpectedAutocorrelationIsNotDiscreteDrift) - ); - assert_eq!( - refuse_trait_plus_state_expected_autocorrelation_as_standardised_discrete_drift( - recovered, - discrete_drift - ), - Err(PsychometricError::TraitPlusStateExpectedAutocorrelationIsNotStandardisedDiscreteDrift) - ); - assert_eq!( - recover_trait_plus_state_expected_autocorrelation( - 0.0, - 0.0, - 0.0, - drift, - delta, - LagClock::EventTime, - ), - Err(PsychometricError::TraitPlusStateExpectedAutocorrelationRequiresPositiveTotalVariance) - ); - assert_eq!( - recover_trait_plus_state_expected_autocorrelation( - trait_variance, - state_variance, - added, - drift, - delta, - LagClock::SystemTime, - ), - Err(PsychometricError::EventTimeRequired) - ); -} - #[test] fn manifest_observed_variance_recovers_driver_equation_five() { let loading = 2.0_f64; @@ -2356,8 +2239,8 @@ fn discrete_observed_mean_with_initial_time_independent_predictor_is_not_impulse } #[test] -fn discrete_observed_mean_with_initial_time_independent_predictor_refuses_evolved_process_impulse_and_carry( -) { +fn discrete_observed_mean_with_initial_time_independent_predictor_refuses_evolved_process_impulse_and_carry() + { let loading = 2.0_f64; let drift = -0.5_f64; let delta = 2.0_f64; @@ -2511,8 +2394,8 @@ fn discrete_observed_mean_with_initial_time_independent_predictor_zero_loading_i } #[test] -fn discrete_observed_mean_with_initial_time_independent_predictor_refuses_overflow_and_non_event_clocks( -) { +fn discrete_observed_mean_with_initial_time_independent_predictor_refuses_overflow_and_non_event_clocks() + { assert_eq!( recover_discrete_observed_mean_with_initial_time_independent_predictor( 1e308, @@ -3443,8 +3326,8 @@ fn discrete_observed_mean_with_initial_time_dependent_predictor_is_not_impulse_o #[test] #[allow(clippy::too_many_lines)] -fn discrete_observed_mean_with_initial_time_dependent_predictor_refuses_evolved_process_impulse_and_carry( -) { +fn discrete_observed_mean_with_initial_time_dependent_predictor_refuses_evolved_process_impulse_and_carry() + { let loading = 2.0_f64; let drift = -0.5_f64; let delta = 2.0_f64; @@ -3617,8 +3500,8 @@ fn discrete_observed_mean_with_initial_time_dependent_predictor_zero_loading_is_ } #[test] -fn discrete_observed_mean_with_initial_time_dependent_predictor_refuses_overflow_and_non_event_clocks( -) { +fn discrete_observed_mean_with_initial_time_dependent_predictor_refuses_overflow_and_non_event_clocks() + { assert_eq!( recover_discrete_observed_mean_with_initial_time_dependent_predictor( 1e308, diff --git a/crates/psychometric_core/tests/scientific_claim_boundary_contract.rs b/crates/psychometric_core/tests/scientific_claim_boundary_contract.rs index 34df26bc4..6ccf7f38b 100644 --- a/crates/psychometric_core/tests/scientific_claim_boundary_contract.rs +++ b/crates/psychometric_core/tests/scientific_claim_boundary_contract.rs @@ -1,6 +1,7 @@ //! Scientific claim boundaries for compositional coordinates and posterior draws. use psychometric_core::{ + ClusteredEventScore, ClusteredScore, IndicatorKind, LagClock, LaggedWithinResidual, ordinary_least_squares_slope, posterior_draw_point_estimate_mean, recover_asymptotic_continuous_intercept, recover_asymptotic_time_independent_predictor_effect, recover_asymptotic_time_independent_predictor_variance, @@ -39,8 +40,8 @@ use psychometric_core::{ recover_stationary_lagged_observed_covariance, recover_stationary_latent_variance, recover_stationary_later_latent_variance, recover_stationary_later_observed_variance, recover_time_dependent_predictor_impulse, recover_time_dependent_predictor_impulse_carry, - recover_trait_plus_state_expected_autocorrelation, recover_trait_plus_state_lagged_covariance, - recover_trait_plus_state_latent_variance, recover_within_residual_event_time_log_rate, + recover_trait_plus_state_lagged_covariance, recover_trait_plus_state_latent_variance, + recover_within_residual_event_time_log_rate, refuse_after_extra_process_contribution_as_observed_mean, refuse_after_extra_process_latent_mean_as_observed_mean, refuse_asymptotic_continuous_intercept_as_asymptotic_time_independent_effect, @@ -166,8 +167,6 @@ use psychometric_core::{ refuse_time_independent_effect_as_time_varying_discrete_effect, refuse_time_independent_observed_mean_as_initial_time_dependent_observed_mean, refuse_time_independent_observed_mean_as_initial_time_independent_observed_mean, - refuse_trait_plus_state_expected_autocorrelation_as_discrete_drift, - refuse_trait_plus_state_expected_autocorrelation_as_standardised_discrete_drift, refuse_trait_plus_state_lagged_covariance_as_stationary_lagged_latent_covariance, refuse_trait_scaled_continuous_intercept_as_standardised_continuous_intercept, refuse_trait_variance_as_process_noise, refuse_trait_variance_as_stationary_within_subject, @@ -182,7 +181,6 @@ use psychometric_core::{ refuse_unstandardised_manifest_variance_as_standardised_manifest_variance, refuse_unstandardised_trait_variance_as_standardised_trait_variance, refuse_within_subject_scaled_initial_latent_mean_as_standardised_initial_latent_mean, - ClusteredEventScore, ClusteredScore, IndicatorKind, LagClock, LaggedWithinResidual, }; #[test] @@ -3784,95 +3782,3 @@ fn standardised_manifest_variance_is_not_unstandardised_traitstd_or_observed_var ) ); } - -#[test] -fn trait_plus_state_expected_autocorrelation_is_not_discrete_drift_or_discrete_drift_std() { - let trait_variance = 0.8_f64; - let state_variance = 0.4_f64; - let added = 0.2_f64; - let drift = -0.5_f64; - let delta = 1.0_f64; - let recovered = recover_trait_plus_state_expected_autocorrelation( - trait_variance, - state_variance, - added, - drift, - delta, - LagClock::EventTime, - ) - .expect("ρ"); - assert!( - (recovered - 0.887_580_188_5).abs() < 1e-9, - "Driver et al. (2017, §7.1): (trait + e^{{a Δt}} p + added) / (trait + p + added)" - ); - let discrete_drift = drift.exp(); - assert!( - (discrete_drift - recovered).abs() > 0.28, - "Driver et al. (2017, Eq. 3 / §7.1): e^{{a Δt}} is not the trait-plus-state autocorrelation" - ); - assert_eq!( - recover_trait_plus_state_expected_autocorrelation( - 0.0, - state_variance, - 0.0, - drift, - delta, - LagClock::EventTime, - ) - .expect("zero trait+added"), - discrete_drift - ); - assert_eq!( - refuse_trait_plus_state_expected_autocorrelation_as_discrete_drift( - recovered, - discrete_drift - ), - Err( - psychometric_core::PsychometricError::TraitPlusStateExpectedAutocorrelationIsNotDiscreteDrift - ) - ); - assert_eq!( - refuse_trait_plus_state_expected_autocorrelation_as_standardised_discrete_drift( - recovered, - discrete_drift - ), - Err( - psychometric_core::PsychometricError::TraitPlusStateExpectedAutocorrelationIsNotStandardisedDiscreteDrift - ) - ); - assert_eq!( - recover_trait_plus_state_expected_autocorrelation( - 0.0, - 0.0, - 0.0, - drift, - delta, - LagClock::EventTime, - ), - Err( - psychometric_core::PsychometricError::TraitPlusStateExpectedAutocorrelationRequiresPositiveTotalVariance - ) - ); - assert_eq!( - recover_trait_plus_state_expected_autocorrelation( - trait_variance, - 0.0, - added, - drift, - delta, - LagClock::EventTime, - ), - Ok(1.0) - ); - assert_eq!( - recover_trait_plus_state_expected_autocorrelation( - trait_variance, - state_variance, - added, - drift, - delta, - LagClock::DocumentTime, - ), - Err(psychometric_core::PsychometricError::EventTimeRequired) - ); -} diff --git a/docs/TRACEABILITY.md b/docs/TRACEABILITY.md index b159c431f..2b783c2ab 100644 --- a/docs/TRACEABILITY.md +++ b/docs/TRACEABILITY.md @@ -76,7 +76,7 @@ The full APA 7th standards/literature register remains `docs/research/standards- | report template/section/copied/style/modality method effects | ADR 0004/0012; PRD/TRD | simulation truth factors implemented; `prompt_source` prompt-versus-unique-content identity on the active PR; estimator-side method model remains future | partial | | candidate K statistical/Pareto gates | ADR 0012; research | `model_selection` fits each candidate `K` with the CPU `f64` reference and scores the actual mixture likelihood plus Schwarz's (1978) `ℓ − (p ln N)/2` penalty before the Pareto gate; candidate blinding, blinded LLM review, GPU, and backend comparison remain accepted-target | active-PR | | compositional topic correlation / stable clustering | ADR 0005/0012; research | future `network_analysis` | accepted-target | -| posterior ESEM / longitudinal invariance / DSEM | ADR 0005 | `psychometric_core` construct/input gates, true-loading OLS recovery, posterior-draw point-estimate averaging, Rubin `T` on draw-level OLS loadings, CWC within/between OLS plus the contextual effect, event-time log-rate, constant- and time-varying-predictor discrete effects (Voelkle Eqs. 12 and 14), exact scalar discrete process noise (Driver et al., 2017, Eq. 3), lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; `asymDIFFUSION`), trait-plus-state variance and expected autocorrelation (Driver et al., 2017, §4.3 `TRAITVAR`; §7.1; not process noise, not discreteDRIFT, not discreteDRIFTstd), observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5; Table 2 `MANIFESTVAR` is `Θ`, not `Var(y)`; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; `Θ` does not enter lagged observed covariance; observed-indicator mean is `τ + λ μ`; `MANIFESTMEANS` is not `E(y)`; `CINT` is not `MANIFESTMEANS`; discrete latent mean is `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ`; `T0MEANS` is not `μ_t`; evolved observed mean is `τ + λ μ_t`; `τ + λ μ_0` is not `E(y_t)`; contemporaneous `TDPREDEFFECT` impulse is `m x`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that contemporaneous impulse is `τ + λ(μ_t + m x)`, and `τ + λ μ_t` is not that observed mean; time-independent `TIPREDEFFECT` increment is `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, not `M x`, not Voelkle Eq. 14, and not the coefficient `B`; Eq. 5 of that increment is `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`; within-interval `TDPREDEFFECT` carry is `e^{A(t−u)} M x` for `t0 < u < t`, not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that carry is `τ + λ(μ_t + e^{a(t−u)} m x)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that carried observed mean when `u ≠ t`; §7.2 level-change `CINT` is `κ = −a m x` (`a < 0`; not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`; Eq. 3 of that setting is `(1 − e^{a Δt}) m x`); §7.2 extra-process contribution is `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, not the dissipating Dirac; `ε ≥ 0` fails closed; Eq. 5 of that contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)`; extra `LAMBDA` is 0; `τ + λ μ_t` is not that observed mean; after-t0 extra-process `TDPREDEFFECT` uses `t − u` with `t0 < u < t` while `μ_t` uses `Δt`; that after-t0 observed mean is not the first-occasion extra-process observed mean; §7.2 `asymTIPREDEFFECT` is `-B z / a` for `a < 0` and is not `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; §7.2 `addedTIPREDVAR` is `(B / a)² v` and is not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`; Table 2 `asymCINT` is `-κ / a` for `a < 0` and is not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; p. 16 stationary `T0MEANS` is `-κ / a + −B z / a` and is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean; Eq. 5 of that constrained mean is `τ + λ(−κ / a + −B z / a)`; `τ + λ μ_0` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`; stationary `T0VAR` is `trait + −q / (2 a) + (B / a)² v` (not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`)); lagged stationary `T0VAR` is `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (trait and `addedTIPREDVAR` do not decay; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map; Eq. 5 of that lagged covariance is `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ`; `Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance); later-occasion stationary `T0VAR` is `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt`; `Q_Δt` is not that later map; Eq. 5 of that later-occasion variance is `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ`; lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not `Var(y_t)`; the later-occasion latent variance is not `Var(y_t)`); predetermined later-occasion `T0VAR` is `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (free `T0VAR` `p_0` is not that later map; setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map; stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map; Eq. 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ`; `MANIFESTVAR` is not `Var(y_t)`; the predetermined later-occasion latent variance is not `Var(y_t)`; stationary later observed variance is not that observed variance when `p_0` is free); predetermined lagged `T0VAR` is `trait + e^{a Δt} p_0 + (B / a)² v` (free `T0VAR` `p_0` is not that lagged map; setting `p_0 = −q / (2 a)` recovers the stationary lagged map; stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map; later-occasion variance includes `Q_Δt` and is not that lagged map; Eq. 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ`; `MANIFESTVAR` does not enter; the predetermined lagged latent covariance is not that observed covariance; predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance; stationary lagged observed covariance is not that observed covariance when `p_0` is free; the predetermined first-occasion variance of §4.3 predetermined `T0VAR` is `trait + p_0 + (B / a)² v`; free `p_0` is not that map; stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free; lagged covariance decays the state and is not that map; later-occasion variance includes `Q_Δt` and is not that map; Eq. 5 of that predetermined first-occasion variance is `λ²(trait + p_0 + (B / a)² v) + θ + ψ`; `MANIFESTVAR` is not that first-occasion observed variance; the predetermined first-occasion latent variance is not that observed variance; stationary first-occasion observed variance is not that observed variance when `p_0` is free; predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance; later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z; first-occasion lagged omits `e^{a s} Q_u`; later-occasion variance does not lag; stationary lagged uses `−q / (2 a)`; decaying the later total is not that map; Eq. 5 of that later-start lagged covariance is `λ²` of it plus `ψ`; `Θ` does not enter; first-occasion lagged observed omits `e^{a s} Q_u`; later observed variance includes `Q_u` and `θ`; later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z; later-occasion variance at `u` omits `Q_s`; later-start lagged covariance omits `Q_s`; stationary later uses `−q / (2 a)`; evolving the later total as if it were all state is not that map; ignoring `startoffset` omits `e^{2 a s} Q_u`; Eq. 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`; `MANIFESTVAR` is not that observed variance; p. 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4; unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`; the §7.1 trait-plus-state autocorrelation uses `TRAITVAR` and is not `discreteDRIFTstd`; p. 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4; unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`; the continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`; `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z; unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`; the discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`; `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z); unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`; the discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`; `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z; unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`; `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z; unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`; the asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`; `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z; the affected variance is free `T0VAR`, not `asymDIFFUSION`; unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`; `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`; `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`; `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); 2017-era `addedT0TIPREDVAR` is `t0_b² v` (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z; `T0TIPREDEFFECT %*% TIPREDVAR %*% t(T0TIPREDEFFECT)` immediately after `T0TIPREDEFFECTstd`; form `t0_b` first, then square, then multiply by `v`; a zero coefficient or zero predictor variance is exactly zero; free `T0TIPREDEFFECT` does not require `a < 0`; `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map; `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance; free `T0VAR` is not this extra TI variance; `TRAITVAR` is not this extra TI variance; Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z; form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; `t0_b² v` is the latent extra, not the observed extra; `λ² p_0 + θ` is first-occasion observed variance, not this extra; `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra; `MANIFESTVAR` `θ` is not this extra; Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v`; form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; lasting asymptotic extra requires `a < 0`; `(B / a)² v` is the latent extra, not the observed extra; `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra; `λ² p + θ` is stationary observed variance, not this extra; `MANIFESTVAR` `θ` is not this extra; p. 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance; unstandardised `M` is not `TDPREDEFFECTstd`; `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`; intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`; `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`; Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance; unstandardised `t0_m` is not `T0TDPREDEFFECTstd`; `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`; `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`; `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`; free `T0VAR` does not require `a < 0`; p. 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; default ridge is 0); unstandardised `T0VAR` is not `T0VARstd`; `T0TDPREDEFFECTstd` is not `T0VARstd`; `addedT0TIPREDVAR` is not `T0VARstd`; p. 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend); unstandardised `TRAITVAR` is not `TRAITVARstd`; `T0VARstd` is not `TRAITVARstd` even when both equal 1; `addedT0TIPREDVAR` is not `TRAITVARstd`; p. 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0); unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`; `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1; `MANIFESTVAR` is not `MANIFESTTRAITVARstd`; p. 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug); unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`; `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1; Equation 5 `Var(y)` is not `MANIFESTVARstd`; p. 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`); unstandardised `TIPREDVAR` is not `TIPREDVARstd`; `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1; §7.2 `addedTIPREDVAR` is not `TIPREDVARstd`; p. 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`); unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`; `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1; `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`; p. 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `discreteCINT` is not `discreteCINTstd`; `κ / √p` is not `discreteCINTstd`; `(-κ / a) / √p` is not `discreteCINTstd`; `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`; unstandardised `asymCINT` is not `asymCINTstd`; `κ / √p` is not `asymCINTstd`; `discreteCINTstd` is not `asymCINTstd`; `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`; unstandardised `T0MEANS` is not `T0MEANSstd`; `T0VARstd` is not `T0MEANSstd`; `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`; `MANIFESTMEANSstd` is `τ / √θ` after strictly positive `MANIFESTVAR`; unstandardised `MANIFESTMEANS` is not `MANIFESTMEANSstd`; `MANIFESTVARstd` is not `MANIFESTMEANSstd`; `τ / √(λ² Var(η) + θ)` is not `MANIFESTMEANSstd`; p. 16 `CINTstd` is `κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `CINT` is not `CINTstd`; `asymCINTstd` is not `CINTstd`; `discreteCINTstd` is not `CINTstd`; `κ / √(trait + p + added)` is not `CINTstd`;))))), irregular already-centered residual lag, and strong/strict-gated latent means on the stacked psychometric PR (two-observation residual variance is identically `0` and caps at strong/scalar; Putnick & Bornstein, 2016, PMC5145197 opened 2026-08-19T22:15Z); full ESEM/DSEM remaining | partial | +| posterior ESEM / longitudinal invariance / DSEM | ADR 0005 | `psychometric_core` construct/input gates, true-loading OLS recovery, posterior-draw point-estimate averaging, Rubin `T` on draw-level OLS loadings, CWC within/between OLS plus the contextual effect, event-time log-rate, constant- and time-varying-predictor discrete effects (Voelkle Eqs. 12 and 14), exact scalar discrete process noise (Driver et al., 2017, Eq. 3), lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; `asymDIFFUSION`), trait-plus-state variance (Driver et al., 2017, §4.3 `TRAITVAR`; not process noise), observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5; Table 2 `MANIFESTVAR` is `Θ`, not `Var(y)`; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; `Θ` does not enter lagged observed covariance; observed-indicator mean is `τ + λ μ`; `MANIFESTMEANS` is not `E(y)`; `CINT` is not `MANIFESTMEANS`; discrete latent mean is `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ`; `T0MEANS` is not `μ_t`; evolved observed mean is `τ + λ μ_t`; `τ + λ μ_0` is not `E(y_t)`; contemporaneous `TDPREDEFFECT` impulse is `m x`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that contemporaneous impulse is `τ + λ(μ_t + m x)`, and `τ + λ μ_t` is not that observed mean; time-independent `TIPREDEFFECT` increment is `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, not `M x`, not Voelkle Eq. 14, and not the coefficient `B`; Eq. 5 of that increment is `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`; within-interval `TDPREDEFFECT` carry is `e^{A(t−u)} M x` for `t0 < u < t`, not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that carry is `τ + λ(μ_t + e^{a(t−u)} m x)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that carried observed mean when `u ≠ t`; §7.2 level-change `CINT` is `κ = −a m x` (`a < 0`; not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`; Eq. 3 of that setting is `(1 − e^{a Δt}) m x`); §7.2 extra-process contribution is `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, not the dissipating Dirac; `ε ≥ 0` fails closed; Eq. 5 of that contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)`; extra `LAMBDA` is 0; `τ + λ μ_t` is not that observed mean; after-t0 extra-process `TDPREDEFFECT` uses `t − u` with `t0 < u < t` while `μ_t` uses `Δt`; that after-t0 observed mean is not the first-occasion extra-process observed mean; §7.2 `asymTIPREDEFFECT` is `-B z / a` for `a < 0` and is not `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; §7.2 `addedTIPREDVAR` is `(B / a)² v` and is not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`; Table 2 `asymCINT` is `-κ / a` for `a < 0` and is not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; p. 16 stationary `T0MEANS` is `-κ / a + −B z / a` and is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean; Eq. 5 of that constrained mean is `τ + λ(−κ / a + −B z / a)`; `τ + λ μ_0` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`; stationary `T0VAR` is `trait + −q / (2 a) + (B / a)² v` (not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`)); lagged stationary `T0VAR` is `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (trait and `addedTIPREDVAR` do not decay; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map; Eq. 5 of that lagged covariance is `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ`; `Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance); later-occasion stationary `T0VAR` is `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt`; `Q_Δt` is not that later map; Eq. 5 of that later-occasion variance is `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ`; lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not `Var(y_t)`; the later-occasion latent variance is not `Var(y_t)`); predetermined later-occasion `T0VAR` is `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (free `T0VAR` `p_0` is not that later map; setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map; stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map; Eq. 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ`; `MANIFESTVAR` is not `Var(y_t)`; the predetermined later-occasion latent variance is not `Var(y_t)`; stationary later observed variance is not that observed variance when `p_0` is free); predetermined lagged `T0VAR` is `trait + e^{a Δt} p_0 + (B / a)² v` (free `T0VAR` `p_0` is not that lagged map; setting `p_0 = −q / (2 a)` recovers the stationary lagged map; stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map; later-occasion variance includes `Q_Δt` and is not that lagged map; Eq. 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ`; `MANIFESTVAR` does not enter; the predetermined lagged latent covariance is not that observed covariance; predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance; stationary lagged observed covariance is not that observed covariance when `p_0` is free; the predetermined first-occasion variance of §4.3 predetermined `T0VAR` is `trait + p_0 + (B / a)² v`; free `p_0` is not that map; stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free; lagged covariance decays the state and is not that map; later-occasion variance includes `Q_Δt` and is not that map; Eq. 5 of that predetermined first-occasion variance is `λ²(trait + p_0 + (B / a)² v) + θ + ψ`; `MANIFESTVAR` is not that first-occasion observed variance; the predetermined first-occasion latent variance is not that observed variance; stationary first-occasion observed variance is not that observed variance when `p_0` is free; predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance; later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z; first-occasion lagged omits `e^{a s} Q_u`; later-occasion variance does not lag; stationary lagged uses `−q / (2 a)`; decaying the later total is not that map; Eq. 5 of that later-start lagged covariance is `λ²` of it plus `ψ`; `Θ` does not enter; first-occasion lagged observed omits `e^{a s} Q_u`; later observed variance includes `Q_u` and `θ`; later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z; later-occasion variance at `u` omits `Q_s`; later-start lagged covariance omits `Q_s`; stationary later uses `−q / (2 a)`; evolving the later total as if it were all state is not that map; ignoring `startoffset` omits `e^{2 a s} Q_u`; Eq. 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`; `MANIFESTVAR` is not that observed variance; p. 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4; unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`; the §7.1 trait-plus-state autocorrelation uses `TRAITVAR` and is not `discreteDRIFTstd`; p. 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4; unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`; the continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`; `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z; unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`; the discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`; `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z); unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`; the discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`; `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z; unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`; `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z; unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`; the asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`; `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z; the affected variance is free `T0VAR`, not `asymDIFFUSION`; unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`; `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`; `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`; `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); 2017-era `addedT0TIPREDVAR` is `t0_b² v` (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z; `T0TIPREDEFFECT %*% TIPREDVAR %*% t(T0TIPREDEFFECT)` immediately after `T0TIPREDEFFECTstd`; form `t0_b` first, then square, then multiply by `v`; a zero coefficient or zero predictor variance is exactly zero; free `T0TIPREDEFFECT` does not require `a < 0`; `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map; `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance; free `T0VAR` is not this extra TI variance; `TRAITVAR` is not this extra TI variance; Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z; form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; `t0_b² v` is the latent extra, not the observed extra; `λ² p_0 + θ` is first-occasion observed variance, not this extra; `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra; `MANIFESTVAR` `θ` is not this extra; Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v`; form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; lasting asymptotic extra requires `a < 0`; `(B / a)² v` is the latent extra, not the observed extra; `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra; `λ² p + θ` is stationary observed variance, not this extra; `MANIFESTVAR` `θ` is not this extra; p. 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance; unstandardised `M` is not `TDPREDEFFECTstd`; `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`; intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`; `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`; Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance; unstandardised `t0_m` is not `T0TDPREDEFFECTstd`; `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`; `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`; `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`; free `T0VAR` does not require `a < 0`; p. 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; default ridge is 0); unstandardised `T0VAR` is not `T0VARstd`; `T0TDPREDEFFECTstd` is not `T0VARstd`; `addedT0TIPREDVAR` is not `T0VARstd`; p. 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend); unstandardised `TRAITVAR` is not `TRAITVARstd`; `T0VARstd` is not `TRAITVARstd` even when both equal 1; `addedT0TIPREDVAR` is not `TRAITVARstd`; p. 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0); unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`; `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1; `MANIFESTVAR` is not `MANIFESTTRAITVARstd`; p. 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug); unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`; `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1; Equation 5 `Var(y)` is not `MANIFESTVARstd`; p. 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`); unstandardised `TIPREDVAR` is not `TIPREDVARstd`; `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1; §7.2 `addedTIPREDVAR` is not `TIPREDVARstd`; p. 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`); unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`; `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1; `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`; p. 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `discreteCINT` is not `discreteCINTstd`; `κ / √p` is not `discreteCINTstd`; `(-κ / a) / √p` is not `discreteCINTstd`; `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`; unstandardised `asymCINT` is not `asymCINTstd`; `κ / √p` is not `asymCINTstd`; `discreteCINTstd` is not `asymCINTstd`; `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`; unstandardised `T0MEANS` is not `T0MEANSstd`; `T0VARstd` is not `T0MEANSstd`; `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`; `MANIFESTMEANSstd` is `τ / √θ` after strictly positive `MANIFESTVAR`; unstandardised `MANIFESTMEANS` is not `MANIFESTMEANSstd`; `MANIFESTVARstd` is not `MANIFESTMEANSstd`; `τ / √(λ² Var(η) + θ)` is not `MANIFESTMEANSstd`; p. 16 `CINTstd` is `κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `CINT` is not `CINTstd`; `asymCINTstd` is not `CINTstd`; `discreteCINTstd` is not `CINTstd`; `κ / √(trait + p + added)` is not `CINTstd`;))))), irregular already-centered residual lag, and strong/strict-gated latent means on the stacked psychometric PR (two-observation residual variance is identically `0` and caps at strong/scalar; Putnick & Bornstein, 2016, PMC5145197 opened 2026-08-19T22:15Z); full ESEM/DSEM remaining | partial | | CPU bounded multithreading + GPU/VRAM streaming/parity | ADR 0001/0006 | future `compute_backend` | accepted-target | | CPU bounded multithreading + GPU/VRAM streaming/parity | ADR 0001/0006 | future `compute_backend` | accepted-target | | TDT detection/tracking vs CHRONOS schema/prediction/temporal consistency | ADR 0016; PRD/research | `event_core` bounded reasoner plus canonical digest-bound JSON and provenance-bearing GraphML successor artifact; ADR 0013 `model_artifact` persists the immutable object reference/type/digest; JSON-LD remains accepted-target | active-PR | diff --git a/docs/adr/0005-posterior-esem-dsem.md b/docs/adr/0005-posterior-esem-dsem.md index 166aaaca6..ee1e6cf0d 100644 --- a/docs/adr/0005-posterior-esem-dsem.md +++ b/docs/adr/0005-posterior-esem-dsem.md @@ -1,8 +1,8 @@ # ADR 0005 — Posterior-aware ESEM/DSEM and structural interpretation **Decision status:** Accepted -**Implementation maturity:** partial — construct classification, valid log-ratio/logistic-normal indicator gates, CPU `f64` OLS and posterior-draw loading point-estimate averaging, Rubin `T_m = Ū_m + (1+1/m) B_m` on draw-level OLS loadings, cluster-mean within/between OLS with the CWC contextual effect and Kish ESS WLS, event-time discrete lag-1 and exact scalar local log-rate, exact scalar forward map and unequal-interval remapping, exact scalar discrete effect of a constant predictor, first-order discrete effect of a time-varying predictor with matched sampling and constancy intervals (Voelkle et al., 2012, Eq. 14), exact scalar discrete process noise (Driver, Oud, & Voelkle, 2017, Eq. 3), exact scalar lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), exact scalar stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; §4.3; p. 16 `asymDIFFUSION`), exact scalar trait-plus-state variance, lagged covariance, and expected autocorrelation (Driver et al., 2017, §4.3 `TRAITVAR`; §7.1; not process noise, not `asymDIFFUSION`, not `discreteDRIFT`, and not `discreteDRIFTstd`), exact scalar observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5, p. 5; Table 2, p. 12; `λ² Var(η) + θ` when `MANIFESTTRAITVAR` is zero, else `λ² Var(η) + θ + ψ`; lagged `λ² cov(η_t, η_{t-1}) + ψ`; `MANIFESTVAR` is `Θ`, not `Var(y)`; `Θ` does not enter lagged observed covariance; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; observed-indicator mean is `τ + λ μ` (`MANIFESTMEANS` is `τ`, not `E(y)`; `CINT` is not `MANIFESTMEANS`; `T0MEANS` is not `E(y)`; Equation 1 is the SDE; not a Kalman filter), exact scalar discrete latent mean `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ` (Driver et al., 2017, Eq. 3; `T0MEANS` is not `μ_t`; `CINT` is not the discrete increment), exact scalar evolved observed-indicator mean `τ + λ μ_t` (Driver et al., 2017, Eq. 5 of that Eq. 3 map; the first-occasion map `τ + λ μ_0` is not `E(y_t)`), exact scalar contemporaneous `TDPREDEFFECT` impulse `m x` (Driver et al., 2017, Eq. 3 fourth summand; Table 2 `TDPREDEFFECT` is `M`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; the §7.2 level-change form is not that impulse), exact scalar observed mean of that contemporaneous impulse `τ + λ(μ_t + m x)` (Driver et al., 2017, Eq. 5 of that Eq. 3 composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`), exact scalar time-independent `TIPREDEFFECT` increment `A^{-1}[e^{A Δt} − I] B z` (Driver et al., 2017, Eq. 3 second summand; Table 2 `TIPREDEFFECT` is `B`, not `κ`, not `M`, and not Voelkle Eq. 14; `B` is not that discrete increment), exact scalar observed mean of that increment `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` (Driver et al., 2017, Eq. 5 of that Eq. 3 printed addend after the `T0MEANS` carry and the `CINT` increment; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`), exact scalar within-interval `TDPREDEFFECT` carry `e^{A(t−u)} M x` for `t0 < u < t` (Driver et al., 2017, Eq. 1–2 Green-function integral of Eq. 2; §7.2 dissipation; not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14), exact scalar observed mean of that carry `τ + λ(μ_t + e^{a(t−u)} m x)` (Driver et al., 2017, Eq. 5 of that carried latent mean; `τ + λ μ_t` is not that observed mean), exact scalar first-occasion `T0TIPREDEFFECT` shift `t0_b z` and Eq. 3 first-summand carry `e^{A Δt} t0_b z` (Driver et al., 2017, Table 3, p. 13; Eq. 3, p. 5; `T0TIPREDEFFECT` is not `TIPREDEFFECT` `B`; `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`; `e^{A Δt} t0_b z` is not `t0_b z`), exact scalar observed mean of that first-occasion carry `τ + λ(μ_t + e^{a Δt} t0_b z)` (Driver et al., 2017, Eq. 5 of that Table 3 / Eq. 3 first-summand composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean), exact scalar first-occasion `T0TDPREDEFFECT` shift `t0_m x0` and Eq. 3 first-summand carry `e^{A Δt} t0_m x0` (Driver et al., 2017, Table 3, p. 13; Eq. 3, p. 5; JSS PDF re-opened 2026-08-20T19:10Z; `T0TDPREDEFFECT` is not `TDPREDEFFECT` `M`; `t0_m x0` is not `M x`; `e^{A Δt} t0_m x0` is not `t0_m x0`; `e^{A Δt} t0_m x0` is not `e^{A(t−u)} M x` for `t0 < u < t`; `t0_m x0` is not `t0_b z`; an impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`), exact scalar observed mean of that first-occasion TD carry `τ + λ(μ_t + e^{a Δt} t0_m x0)` (Driver et al., 2017, Eq. 5 of that Table 3 / Eq. 3 first-summand TD composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean; `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean), exact scalar §7.2 level-change `CINT` `κ = −a m x` (Driver et al., 2017, §7.2, pp. 20–21; `a < 0` so `−κ / a = m x`; not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`, and not the extra near-zero-drift latent process also named in §7.2), exact scalar Eq. 3 increment of that setting `(1 − e^{a Δt}) m x` (not `m x`, not `κ`, and not `TIPREDEFFECT`), exact scalar §7.2 extra near-zero-drift latent process contribution `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-20T23:10Z; identification `TDPREDEFFECT` on the extra process is 1; extra `DRIFT` printed as `−0.000001`; precisely 0 causes computational problems; `ε = a` is `a_{ηξ} x Δt e^{a Δt}`; not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`; `ε ≥ 0` fails closed), exact scalar observed mean of that extra-process contribution `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a))` (Driver et al., 2017, Eq. 5, p. 5; §7.2, pp. 22–23; JSS PDF re-opened 2026-08-21T06:12Z; the extra process has `LAMBDA` 0 and is not an observed indicator; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; the contribution is not `E(y_t)`; the evolved-plus-contribution latent mean is not `E(y_t)`), exact scalar after-t0 extra-process contribution `a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)` for `t0 < u < t` and Eq. 5 observed mean `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a))` (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-21T06:32Z; `T0TDPREDEFFECT` uses `Δt` for both the evolution and the extra drive; `TDPREDEFFECT` after `t0` uses `t − u`; `e^{a(t−u)} m x` is a Dirac on the original process, not this `DRIFT` drive), exact scalar §7.2 `asymTIPREDEFFECT` `-B z / a` (Driver et al., 2017, §7.2, pp. 20–21; JSS PDF opened 2026-08-21T13:08Z; expected total change in process means given a time-independent predictor; `a < 0`; not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; `a ≥ 0` fails closed), exact scalar §7.2 `addedTIPREDVAR` `(B / a)² v` (Driver et al., 2017, §7.2, pp. 20–21; stable between-subject variance accounted for by a time-independent predictor; not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`), exact scalar Table 2 `asymCINT` `-κ / a` (Driver et al., 2017, Table 2, p. 12; Eq. 3 as `Δt → ∞`; `a < 0`; not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; `a ≥ 0` fails closed), exact scalar p. 16 stationary `T0MEANS` `-κ / a + −B z / a` (Driver et al., 2017, p. 16; constrained first-occasion mean using `T0MEANSbase` / `T0MEANSfree`; not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean), exact scalar Eq. 5 of that constrained mean `τ + λ(−κ / a + −B z / a)` (Driver et al., 2017, §4.3, pp. 9–10; Eq. 5, p. 5; JSS PDF re-opened 2026-08-21T20:07Z; form the stationary latent mean first, then `τ + λ` of that mean; `τ + λ μ_0` is not that observed mean; `τ + λ(−κ / a)` is not that observed mean when `B z ≠ 0`; `τ + λ μ_t` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`), exact scalar §4.3 / p. 16 stationary `T0VAR` `trait + −q / (2 a) + (B / a)² v` (Driver et al., 2017, §4.3, pp. 9–10; p. 16; JSS PDF re-opened 2026-08-22T03:07Z; not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`), exact scalar lagged covariance of that constrained process `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T19:13Z; trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map), exact scalar Eq. 5 of that lagged covariance `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ` (`Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance), exact scalar later-occasion variance of that constrained process `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T23:12Z; trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt` and is not that later map; `Q_Δt` is not that later map), exact scalar Eq. 5 of that later-occasion variance `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ` (the lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not that later observed variance; the later-occasion latent variance is not that observed variance)), exact scalar later-occasion variance of §4.3 predetermined `T0VAR` `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T05:12Z; trait and `addedTIPREDVAR` do not enter `Q_Δt`; free `T0VAR` `p_0` is not that later map; setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map; stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map; as `Δt → ∞` with stable `a < 0` the composition approaches contemporaneous stationary `T0VAR`; as `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`; nonzero diffusion with `a ≥ 0` is a growing process and is kept. Eq. 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ` (`MANIFESTVAR` is not that later observed variance; the predetermined later-occasion latent variance is not that observed variance; stationary later observed variance is not that observed variance when `p_0` is free)), exact scalar lagged covariance of §4.3 predetermined `T0VAR` `trait + e^{a Δt} p_0 + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T09:04Z; trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`; free `T0VAR` `p_0` is not that lagged map; setting `p_0 = −q / (2 a)` recovers the stationary lagged map; stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map; later-occasion variance includes `Q_Δt` and is not that lagged map. Eq. 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ` (`MANIFESTVAR` does not enter; the predetermined lagged latent covariance is not that observed covariance; predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance; stationary lagged observed covariance is not that observed covariance when `p_0` is free)), exact scalar first-occasion variance of §4.3 predetermined `T0VAR` `trait + p_0 + (B / a)² v` (free `p_0` is not that map; stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free; lagged covariance decays the state and is not that map; later-occasion variance includes `Q_Δt` and is not that map), exact scalar Eq. 5 of that predetermined first-occasion variance `λ²(trait + p_0 + (B / a)² v) + θ + ψ` (`MANIFESTVAR` is not that first-occasion observed variance; the predetermined first-occasion latent variance is not that observed variance; stationary first-occasion observed variance is not that observed variance when `p_0` is free; predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance; later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z; first-occasion lagged omits `e^{a s} Q_u`; later-occasion variance does not lag; stationary lagged uses `−q / (2 a)`; decaying the later total is not that map; Eq. 5 of that later-start lagged covariance is `λ²` of it plus `ψ`; `Θ` does not enter; first-occasion lagged observed omits `e^{a s} Q_u`; later observed variance includes `Q_u` and `θ`; later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z; later-occasion variance at `u` omits `Q_s`; later-start lagged covariance omits `Q_s`; stationary later uses `−q / (2 a)`; evolving the later total as if it were all state is not that map; ignoring `startoffset` omits `e^{2 a s} Q_u`; Eq. 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`; `MANIFESTVAR` is not that observed variance; p. 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; footnote 4; §7.1; JSS PDF re-opened 2026-08-23T11:40Z; unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`; the trait-plus-state autocorrelation uses `TRAITVAR` and is not `discreteDRIFTstd`; p. 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:06Z; unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`; the continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`; `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z; unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`; the discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`; `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z); unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`; the discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`; `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z; unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`; `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z; unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`; the asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`; `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z; the affected variance is free `T0VAR`, not `asymDIFFUSION`; unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`; `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`; `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`; `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); 2017-era `addedT0TIPREDVAR` is `t0_b² v` (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z; `T0TIPREDEFFECT %*% TIPREDVAR %*% t(T0TIPREDEFFECT)` immediately after `T0TIPREDEFFECTstd`; form `t0_b` first, then square, then multiply by `v`; a zero coefficient or zero predictor variance is exactly zero; free `T0TIPREDEFFECT` does not require `a < 0`; `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map; `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance; free `T0VAR` is not this extra TI variance; `TRAITVAR` is not this extra TI variance; Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z; form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; `t0_b² v` is the latent extra, not the observed extra; `λ² p_0 + θ` is first-occasion observed variance, not this extra; `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra; `MANIFESTVAR` `θ` is not this extra; Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v`; form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; lasting asymptotic extra requires `a < 0`; `(B / a)² v` is the latent extra, not the observed extra; `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra; `λ² p + θ` is stationary observed variance, not this extra; `MANIFESTVAR` `θ` is not this extra; p. 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance; unstandardised `M` is not `TDPREDEFFECTstd`; `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`; intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`; `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`; Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance; unstandardised `t0_m` is not `T0TDPREDEFFECTstd`; `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`; `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`; `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`; free `T0VAR` does not require `a < 0`; p. 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; default ridge is 0); unstandardised `T0VAR` is not `T0VARstd`; `T0TDPREDEFFECTstd` is not `T0VARstd`; `addedT0TIPREDVAR` is not `T0VARstd`; p. 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend); unstandardised `TRAITVAR` is not `TRAITVARstd`; `T0VARstd` is not `TRAITVARstd` even when both equal 1; `addedT0TIPREDVAR` is not `TRAITVARstd`; p. 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0); unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`; `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1; `MANIFESTVAR` is not `MANIFESTTRAITVARstd`; p. 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug); unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`; `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1; Equation 5 `Var(y)` is not `MANIFESTVARstd`; p. 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`); unstandardised `TIPREDVAR` is not `TIPREDVARstd`; `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1; §7.2 `addedTIPREDVAR` is not `TIPREDVARstd`; p. 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`); unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`; `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1; `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`; p. 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `discreteCINT` is not `discreteCINTstd`; `κ / √p` is not `discreteCINTstd`; `(-κ / a) / √p` is not `discreteCINTstd`; `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`; unstandardised `asymCINT` is not `asymCINTstd`; `κ / √p` is not `asymCINTstd`; `discreteCINTstd` is not `asymCINTstd`; `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`; unstandardised `T0MEANS` is not `T0MEANSstd`; `T0VARstd` is not `T0MEANSstd`; `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`; `MANIFESTMEANSstd` is `τ / √θ` after strictly positive `MANIFESTVAR`; unstandardised `MANIFESTMEANS` is not `MANIFESTMEANSstd`; `MANIFESTVARstd` is not `MANIFESTMEANSstd`; `τ / √(λ² Var(η) + θ)` is not `MANIFESTMEANSstd`;))))), CWC-then-event-time residual lag, irregular already-centered residual log-rate, and strong/strict-gated two-group OLS latent-mean difference are implemented on the stacked psychometric PR and are not implemented-main until exact-head checks, review, and protected-main integration complete; full ESEM/set-ESEM, formative composites, DSEM, and matrix continuous-time dynamics remain accepted-target -**Implementation maturity:** partial — construct classification, valid log-ratio/logistic-normal indicator gates, CPU `f64` OLS and posterior-draw loading point-estimate averaging, Rubin `T_m = Ū_m + (1+1/m) B_m` on draw-level OLS loadings, cluster-mean within/between OLS with the CWC contextual effect and Kish ESS WLS, event-time discrete lag-1 and exact scalar local log-rate, exact scalar forward map and unequal-interval remapping, exact scalar discrete effect of a constant predictor, first-order discrete effect of a time-varying predictor with matched sampling and constancy intervals (Voelkle et al., 2012, Eq. 14), exact scalar discrete process noise (Driver, Oud, & Voelkle, 2017, Eq. 3), exact scalar lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), exact scalar stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; §4.3; p. 16 `asymDIFFUSION`), exact scalar trait-plus-state variance, lagged covariance, and expected autocorrelation (Driver et al., 2017, §4.3 `TRAITVAR`; §7.1; not process noise, not `asymDIFFUSION`, not `discreteDRIFT`, and not `discreteDRIFTstd`), exact scalar observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5, p. 5; Table 2, p. 12; `λ² Var(η) + θ` when `MANIFESTTRAITVAR` is zero, else `λ² Var(η) + θ + ψ`; lagged `λ² cov(η_t, η_{t-1}) + ψ`; `MANIFESTVAR` is `Θ`, not `Var(y)`; `Θ` does not enter lagged observed covariance; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; observed-indicator mean is `τ + λ μ` (`MANIFESTMEANS` is `τ`, not `E(y)`; `CINT` is not `MANIFESTMEANS`; `T0MEANS` is not `E(y)`; Equation 1 is the SDE; not a Kalman filter), exact scalar discrete latent mean `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ` (Driver et al., 2017, Eq. 3; `T0MEANS` is not `μ_t`; `CINT` is not the discrete increment), exact scalar evolved observed-indicator mean `τ + λ μ_t` (Driver et al., 2017, Eq. 5 of that Eq. 3 map; the first-occasion map `τ + λ μ_0` is not `E(y_t)`), exact scalar contemporaneous `TDPREDEFFECT` impulse `m x` (Driver et al., 2017, Eq. 3 fourth summand; Table 2 `TDPREDEFFECT` is `M`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; the §7.2 level-change form is not that impulse), exact scalar observed mean of that contemporaneous impulse `τ + λ(μ_t + m x)` (Driver et al., 2017, Eq. 5 of that Eq. 3 composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`), exact scalar time-independent `TIPREDEFFECT` increment `A^{-1}[e^{A Δt} − I] B z` (Driver et al., 2017, Eq. 3 second summand; Table 2 `TIPREDEFFECT` is `B`, not `κ`, not `M`, and not Voelkle Eq. 14; `B` is not that discrete increment), exact scalar observed mean of that increment `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` (Driver et al., 2017, Eq. 5 of that Eq. 3 printed addend after the `T0MEANS` carry and the `CINT` increment; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`), exact scalar within-interval `TDPREDEFFECT` carry `e^{A(t−u)} M x` for `t0 < u < t` (Driver et al., 2017, Eq. 1–2 Green-function integral of Eq. 2; §7.2 dissipation; not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14), exact scalar observed mean of that carry `τ + λ(μ_t + e^{a(t−u)} m x)` (Driver et al., 2017, Eq. 5 of that carried latent mean; `τ + λ μ_t` is not that observed mean), exact scalar first-occasion `T0TIPREDEFFECT` shift `t0_b z` and Eq. 3 first-summand carry `e^{A Δt} t0_b z` (Driver et al., 2017, Table 3, p. 13; Eq. 3, p. 5; `T0TIPREDEFFECT` is not `TIPREDEFFECT` `B`; `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`; `e^{A Δt} t0_b z` is not `t0_b z`), exact scalar observed mean of that first-occasion carry `τ + λ(μ_t + e^{a Δt} t0_b z)` (Driver et al., 2017, Eq. 5 of that Table 3 / Eq. 3 first-summand composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean), exact scalar first-occasion `T0TDPREDEFFECT` shift `t0_m x0` and Eq. 3 first-summand carry `e^{A Δt} t0_m x0` (Driver et al., 2017, Table 3, p. 13; Eq. 3, p. 5; JSS PDF re-opened 2026-08-20T19:10Z; `T0TDPREDEFFECT` is not `TDPREDEFFECT` `M`; `t0_m x0` is not `M x`; `e^{A Δt} t0_m x0` is not `t0_m x0`; `e^{A Δt} t0_m x0` is not `e^{A(t−u)} M x` for `t0 < u < t`; `t0_m x0` is not `t0_b z`; an impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`), exact scalar observed mean of that first-occasion TD carry `τ + λ(μ_t + e^{a Δt} t0_m x0)` (Driver et al., 2017, Eq. 5 of that Table 3 / Eq. 3 first-summand TD composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean; `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean), exact scalar §7.2 level-change `CINT` `κ = −a m x` (Driver et al., 2017, §7.2, pp. 20–21; `a < 0` so `−κ / a = m x`; not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`, and not the extra near-zero-drift latent process also named in §7.2), exact scalar Eq. 3 increment of that setting `(1 − e^{a Δt}) m x` (not `m x`, not `κ`, and not `TIPREDEFFECT`), exact scalar §7.2 extra near-zero-drift latent process contribution `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-20T23:10Z; identification `TDPREDEFFECT` on the extra process is 1; extra `DRIFT` printed as `−0.000001`; precisely 0 causes computational problems; `ε = a` is `a_{ηξ} x Δt e^{a Δt}`; not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`; `ε ≥ 0` fails closed), exact scalar observed mean of that extra-process contribution `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a))` (Driver et al., 2017, Eq. 5, p. 5; §7.2, pp. 22–23; JSS PDF re-opened 2026-08-21T06:12Z; the extra process has `LAMBDA` 0 and is not an observed indicator; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; the contribution is not `E(y_t)`; the evolved-plus-contribution latent mean is not `E(y_t)`), exact scalar after-t0 extra-process contribution `a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)` for `t0 < u < t` and Eq. 5 observed mean `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a))` (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-21T06:32Z; `T0TDPREDEFFECT` uses `Δt` for both the evolution and the extra drive; `TDPREDEFFECT` after `t0` uses `t − u`; `e^{a(t−u)} m x` is a Dirac on the original process, not this `DRIFT` drive), exact scalar §7.2 `asymTIPREDEFFECT` `-B z / a` (Driver et al., 2017, §7.2, pp. 20–21; JSS PDF opened 2026-08-21T13:08Z; expected total change in process means given a time-independent predictor; `a < 0`; not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; `a ≥ 0` fails closed), exact scalar §7.2 `addedTIPREDVAR` `(B / a)² v` (Driver et al., 2017, §7.2, pp. 20–21; stable between-subject variance accounted for by a time-independent predictor; not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`), exact scalar Table 2 `asymCINT` `-κ / a` (Driver et al., 2017, Table 2, p. 12; Eq. 3 as `Δt → ∞`; `a < 0`; not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; `a ≥ 0` fails closed), exact scalar p. 16 stationary `T0MEANS` `-κ / a + −B z / a` (Driver et al., 2017, p. 16; constrained first-occasion mean using `T0MEANSbase` / `T0MEANSfree`; not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean), exact scalar Eq. 5 of that constrained mean `τ + λ(−κ / a + −B z / a)` (Driver et al., 2017, §4.3, pp. 9–10; Eq. 5, p. 5; JSS PDF re-opened 2026-08-21T20:07Z; form the stationary latent mean first, then `τ + λ` of that mean; `τ + λ μ_0` is not that observed mean; `τ + λ(−κ / a)` is not that observed mean when `B z ≠ 0`; `τ + λ μ_t` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`), exact scalar §4.3 / p. 16 stationary `T0VAR` `trait + −q / (2 a) + (B / a)² v` (Driver et al., 2017, §4.3, pp. 9–10; p. 16; JSS PDF re-opened 2026-08-22T03:07Z; not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`), exact scalar lagged covariance of that constrained process `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T19:13Z; trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map), exact scalar Eq. 5 of that lagged covariance `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ` (`Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance), exact scalar later-occasion variance of that constrained process `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T23:12Z; trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt` and is not that later map; `Q_Δt` is not that later map), exact scalar Eq. 5 of that later-occasion variance `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ` (the lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not that later observed variance; the later-occasion latent variance is not that observed variance)), exact scalar later-occasion variance of §4.3 predetermined `T0VAR` `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T05:12Z; trait and `addedTIPREDVAR` do not enter `Q_Δt`; free `T0VAR` `p_0` is not that later map; setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map; stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map; as `Δt → ∞` with stable `a < 0` the composition approaches contemporaneous stationary `T0VAR`; as `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`; nonzero diffusion with `a ≥ 0` is a growing process and is kept. Eq. 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ` (`MANIFESTVAR` is not that later observed variance; the predetermined later-occasion latent variance is not that observed variance; stationary later observed variance is not that observed variance when `p_0` is free)), exact scalar lagged covariance of §4.3 predetermined `T0VAR` `trait + e^{a Δt} p_0 + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T09:04Z; trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`; free `T0VAR` `p_0` is not that lagged map; setting `p_0 = −q / (2 a)` recovers the stationary lagged map; stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map; later-occasion variance includes `Q_Δt` and is not that lagged map. Eq. 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ` (`MANIFESTVAR` does not enter; the predetermined lagged latent covariance is not that observed covariance; predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance; stationary lagged observed covariance is not that observed covariance when `p_0` is free)), exact scalar first-occasion variance of §4.3 predetermined `T0VAR` `trait + p_0 + (B / a)² v` (free `p_0` is not that map; stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free; lagged covariance decays the state and is not that map; later-occasion variance includes `Q_Δt` and is not that map), exact scalar Eq. 5 of that predetermined first-occasion variance `λ²(trait + p_0 + (B / a)² v) + θ + ψ` (`MANIFESTVAR` is not that first-occasion observed variance; the predetermined first-occasion latent variance is not that observed variance; stationary first-occasion observed variance is not that observed variance when `p_0` is free; predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance; later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z; first-occasion lagged omits `e^{a s} Q_u`; later-occasion variance does not lag; stationary lagged uses `−q / (2 a)`; decaying the later total is not that map; Eq. 5 of that later-start lagged covariance is `λ²` of it plus `ψ`; `Θ` does not enter; first-occasion lagged observed omits `e^{a s} Q_u`; later observed variance includes `Q_u` and `θ`; later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z; later-occasion variance at `u` omits `Q_s`; later-start lagged covariance omits `Q_s`; stationary later uses `−q / (2 a)`; evolving the later total as if it were all state is not that map; ignoring `startoffset` omits `e^{2 a s} Q_u`; Eq. 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`; `MANIFESTVAR` is not that observed variance; p. 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; footnote 4; §7.1; JSS PDF re-opened 2026-08-23T11:40Z; unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`; the trait-plus-state autocorrelation uses `TRAITVAR` and is not `discreteDRIFTstd`; p. 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:06Z; unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`; the continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`; `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z; unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`; the discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`; `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z); unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`; the discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`; `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z; unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`; `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z; unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`; the asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`; `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z; the affected variance is free `T0VAR`, not `asymDIFFUSION`; unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`; `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`; `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`; `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); 2017-era `addedT0TIPREDVAR` is `t0_b² v` (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z; `T0TIPREDEFFECT %*% TIPREDVAR %*% t(T0TIPREDEFFECT)` immediately after `T0TIPREDEFFECTstd`; form `t0_b` first, then square, then multiply by `v`; a zero coefficient or zero predictor variance is exactly zero; free `T0TIPREDEFFECT` does not require `a < 0`; `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map; `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance; free `T0VAR` is not this extra TI variance; `TRAITVAR` is not this extra TI variance; Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z; form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; `t0_b² v` is the latent extra, not the observed extra; `λ² p_0 + θ` is first-occasion observed variance, not this extra; `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra; `MANIFESTVAR` `θ` is not this extra; Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v`; form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; lasting asymptotic extra requires `a < 0`; `(B / a)² v` is the latent extra, not the observed extra; `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra; `λ² p + θ` is stationary observed variance, not this extra; `MANIFESTVAR` `θ` is not this extra; p. 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance; unstandardised `M` is not `TDPREDEFFECTstd`; `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`; intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`; `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`; Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance; unstandardised `t0_m` is not `T0TDPREDEFFECTstd`; `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`; `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`; `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`; free `T0VAR` does not require `a < 0`; p. 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; default ridge is 0); unstandardised `T0VAR` is not `T0VARstd`; `T0TDPREDEFFECTstd` is not `T0VARstd`; `addedT0TIPREDVAR` is not `T0VARstd`; p. 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend); unstandardised `TRAITVAR` is not `TRAITVARstd`; `T0VARstd` is not `TRAITVARstd` even when both equal 1; `addedT0TIPREDVAR` is not `TRAITVARstd`; p. 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0); unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`; `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1; `MANIFESTVAR` is not `MANIFESTTRAITVARstd`; p. 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug); unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`; `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1; Equation 5 `Var(y)` is not `MANIFESTVARstd`; p. 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`); unstandardised `TIPREDVAR` is not `TIPREDVARstd`; `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1; §7.2 `addedTIPREDVAR` is not `TIPREDVARstd`; p. 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`); unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`; `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1; `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`; p. 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `discreteCINT` is not `discreteCINTstd`; `κ / √p` is not `discreteCINTstd`; `(-κ / a) / √p` is not `discreteCINTstd`; `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`; unstandardised `asymCINT` is not `asymCINTstd`; `κ / √p` is not `asymCINTstd`; `discreteCINTstd` is not `asymCINTstd`; `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`; unstandardised `T0MEANS` is not `T0MEANSstd`; `T0VARstd` is not `T0MEANSstd`; `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`;))))), CWC-then-event-time residual lag, irregular already-centered residual log-rate, and strong/strict-gated two-group OLS latent-mean difference are implemented on the consolidation vehicle PR `integration/psychometric-standardisation` (folding draft stack #181–#218) and are not implemented-main until exact-head checks, review, and protected-main integration complete; full ESEM/set-ESEM, formative composites, DSEM, and matrix continuous-time dynamics remain accepted-target +**Implementation maturity:** partial — construct classification, valid log-ratio/logistic-normal indicator gates, CPU `f64` OLS and posterior-draw loading point-estimate averaging, Rubin `T_m = Ū_m + (1+1/m) B_m` on draw-level OLS loadings, cluster-mean within/between OLS with the CWC contextual effect and Kish ESS WLS, event-time discrete lag-1 and exact scalar local log-rate, exact scalar forward map and unequal-interval remapping, exact scalar discrete effect of a constant predictor, first-order discrete effect of a time-varying predictor with matched sampling and constancy intervals (Voelkle et al., 2012, Eq. 14), exact scalar discrete process noise (Driver, Oud, & Voelkle, 2017, Eq. 3), exact scalar lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), exact scalar stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; §4.3; p. 16 `asymDIFFUSION`), exact scalar trait-plus-state variance and lagged covariance (Driver et al., 2017, §4.3 `TRAITVAR`; not process noise and not `asymDIFFUSION`), exact scalar observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5, p. 5; Table 2, p. 12; `λ² Var(η) + θ` when `MANIFESTTRAITVAR` is zero, else `λ² Var(η) + θ + ψ`; lagged `λ² cov(η_t, η_{t-1}) + ψ`; `MANIFESTVAR` is `Θ`, not `Var(y)`; `Θ` does not enter lagged observed covariance; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; observed-indicator mean is `τ + λ μ` (`MANIFESTMEANS` is `τ`, not `E(y)`; `CINT` is not `MANIFESTMEANS`; `T0MEANS` is not `E(y)`; Equation 1 is the SDE; not a Kalman filter), exact scalar discrete latent mean `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ` (Driver et al., 2017, Eq. 3; `T0MEANS` is not `μ_t`; `CINT` is not the discrete increment), exact scalar evolved observed-indicator mean `τ + λ μ_t` (Driver et al., 2017, Eq. 5 of that Eq. 3 map; the first-occasion map `τ + λ μ_0` is not `E(y_t)`), exact scalar contemporaneous `TDPREDEFFECT` impulse `m x` (Driver et al., 2017, Eq. 3 fourth summand; Table 2 `TDPREDEFFECT` is `M`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; the §7.2 level-change form is not that impulse), exact scalar observed mean of that contemporaneous impulse `τ + λ(μ_t + m x)` (Driver et al., 2017, Eq. 5 of that Eq. 3 composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`), exact scalar time-independent `TIPREDEFFECT` increment `A^{-1}[e^{A Δt} − I] B z` (Driver et al., 2017, Eq. 3 second summand; Table 2 `TIPREDEFFECT` is `B`, not `κ`, not `M`, and not Voelkle Eq. 14; `B` is not that discrete increment), exact scalar observed mean of that increment `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` (Driver et al., 2017, Eq. 5 of that Eq. 3 printed addend after the `T0MEANS` carry and the `CINT` increment; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`), exact scalar within-interval `TDPREDEFFECT` carry `e^{A(t−u)} M x` for `t0 < u < t` (Driver et al., 2017, Eq. 1–2 Green-function integral of Eq. 2; §7.2 dissipation; not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14), exact scalar observed mean of that carry `τ + λ(μ_t + e^{a(t−u)} m x)` (Driver et al., 2017, Eq. 5 of that carried latent mean; `τ + λ μ_t` is not that observed mean), exact scalar first-occasion `T0TIPREDEFFECT` shift `t0_b z` and Eq. 3 first-summand carry `e^{A Δt} t0_b z` (Driver et al., 2017, Table 3, p. 13; Eq. 3, p. 5; `T0TIPREDEFFECT` is not `TIPREDEFFECT` `B`; `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`; `e^{A Δt} t0_b z` is not `t0_b z`), exact scalar observed mean of that first-occasion carry `τ + λ(μ_t + e^{a Δt} t0_b z)` (Driver et al., 2017, Eq. 5 of that Table 3 / Eq. 3 first-summand composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean), exact scalar first-occasion `T0TDPREDEFFECT` shift `t0_m x0` and Eq. 3 first-summand carry `e^{A Δt} t0_m x0` (Driver et al., 2017, Table 3, p. 13; Eq. 3, p. 5; JSS PDF re-opened 2026-08-20T19:10Z; `T0TDPREDEFFECT` is not `TDPREDEFFECT` `M`; `t0_m x0` is not `M x`; `e^{A Δt} t0_m x0` is not `t0_m x0`; `e^{A Δt} t0_m x0` is not `e^{A(t−u)} M x` for `t0 < u < t`; `t0_m x0` is not `t0_b z`; an impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`), exact scalar observed mean of that first-occasion TD carry `τ + λ(μ_t + e^{a Δt} t0_m x0)` (Driver et al., 2017, Eq. 5 of that Table 3 / Eq. 3 first-summand TD composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean; `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean), exact scalar §7.2 level-change `CINT` `κ = −a m x` (Driver et al., 2017, §7.2, pp. 20–21; `a < 0` so `−κ / a = m x`; not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`, and not the extra near-zero-drift latent process also named in §7.2), exact scalar Eq. 3 increment of that setting `(1 − e^{a Δt}) m x` (not `m x`, not `κ`, and not `TIPREDEFFECT`), exact scalar §7.2 extra near-zero-drift latent process contribution `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-20T23:10Z; identification `TDPREDEFFECT` on the extra process is 1; extra `DRIFT` printed as `−0.000001`; precisely 0 causes computational problems; `ε = a` is `a_{ηξ} x Δt e^{a Δt}`; not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`; `ε ≥ 0` fails closed), exact scalar observed mean of that extra-process contribution `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a))` (Driver et al., 2017, Eq. 5, p. 5; §7.2, pp. 22–23; JSS PDF re-opened 2026-08-21T06:12Z; the extra process has `LAMBDA` 0 and is not an observed indicator; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; the contribution is not `E(y_t)`; the evolved-plus-contribution latent mean is not `E(y_t)`), exact scalar after-t0 extra-process contribution `a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)` for `t0 < u < t` and Eq. 5 observed mean `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a))` (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-21T06:32Z; `T0TDPREDEFFECT` uses `Δt` for both the evolution and the extra drive; `TDPREDEFFECT` after `t0` uses `t − u`; `e^{a(t−u)} m x` is a Dirac on the original process, not this `DRIFT` drive), exact scalar §7.2 `asymTIPREDEFFECT` `-B z / a` (Driver et al., 2017, §7.2, pp. 20–21; JSS PDF opened 2026-08-21T13:08Z; expected total change in process means given a time-independent predictor; `a < 0`; not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; `a ≥ 0` fails closed), exact scalar §7.2 `addedTIPREDVAR` `(B / a)² v` (Driver et al., 2017, §7.2, pp. 20–21; stable between-subject variance accounted for by a time-independent predictor; not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`), exact scalar Table 2 `asymCINT` `-κ / a` (Driver et al., 2017, Table 2, p. 12; Eq. 3 as `Δt → ∞`; `a < 0`; not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; `a ≥ 0` fails closed), exact scalar p. 16 stationary `T0MEANS` `-κ / a + −B z / a` (Driver et al., 2017, p. 16; constrained first-occasion mean using `T0MEANSbase` / `T0MEANSfree`; not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean), exact scalar Eq. 5 of that constrained mean `τ + λ(−κ / a + −B z / a)` (Driver et al., 2017, §4.3, pp. 9–10; Eq. 5, p. 5; JSS PDF re-opened 2026-08-21T20:07Z; form the stationary latent mean first, then `τ + λ` of that mean; `τ + λ μ_0` is not that observed mean; `τ + λ(−κ / a)` is not that observed mean when `B z ≠ 0`; `τ + λ μ_t` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`), exact scalar §4.3 / p. 16 stationary `T0VAR` `trait + −q / (2 a) + (B / a)² v` (Driver et al., 2017, §4.3, pp. 9–10; p. 16; JSS PDF re-opened 2026-08-22T03:07Z; not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`), exact scalar lagged covariance of that constrained process `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T19:13Z; trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map), exact scalar Eq. 5 of that lagged covariance `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ` (`Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance), exact scalar later-occasion variance of that constrained process `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T23:12Z; trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt` and is not that later map; `Q_Δt` is not that later map), exact scalar Eq. 5 of that later-occasion variance `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ` (the lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not that later observed variance; the later-occasion latent variance is not that observed variance)), exact scalar later-occasion variance of §4.3 predetermined `T0VAR` `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T05:12Z; trait and `addedTIPREDVAR` do not enter `Q_Δt`; free `T0VAR` `p_0` is not that later map; setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map; stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map; as `Δt → ∞` with stable `a < 0` the composition approaches contemporaneous stationary `T0VAR`; as `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`; nonzero diffusion with `a ≥ 0` is a growing process and is kept. Eq. 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ` (`MANIFESTVAR` is not that later observed variance; the predetermined later-occasion latent variance is not that observed variance; stationary later observed variance is not that observed variance when `p_0` is free)), exact scalar lagged covariance of §4.3 predetermined `T0VAR` `trait + e^{a Δt} p_0 + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T09:04Z; trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`; free `T0VAR` `p_0` is not that lagged map; setting `p_0 = −q / (2 a)` recovers the stationary lagged map; stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map; later-occasion variance includes `Q_Δt` and is not that lagged map. Eq. 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ` (`MANIFESTVAR` does not enter; the predetermined lagged latent covariance is not that observed covariance; predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance; stationary lagged observed covariance is not that observed covariance when `p_0` is free)), exact scalar first-occasion variance of §4.3 predetermined `T0VAR` `trait + p_0 + (B / a)² v` (free `p_0` is not that map; stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free; lagged covariance decays the state and is not that map; later-occasion variance includes `Q_Δt` and is not that map), exact scalar Eq. 5 of that predetermined first-occasion variance `λ²(trait + p_0 + (B / a)² v) + θ + ψ` (`MANIFESTVAR` is not that first-occasion observed variance; the predetermined first-occasion latent variance is not that observed variance; stationary first-occasion observed variance is not that observed variance when `p_0` is free; predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance; later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z; first-occasion lagged omits `e^{a s} Q_u`; later-occasion variance does not lag; stationary lagged uses `−q / (2 a)`; decaying the later total is not that map; Eq. 5 of that later-start lagged covariance is `λ²` of it plus `ψ`; `Θ` does not enter; first-occasion lagged observed omits `e^{a s} Q_u`; later observed variance includes `Q_u` and `θ`; later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z; later-occasion variance at `u` omits `Q_s`; later-start lagged covariance omits `Q_s`; stationary later uses `−q / (2 a)`; evolving the later total as if it were all state is not that map; ignoring `startoffset` omits `e^{2 a s} Q_u`; Eq. 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`; `MANIFESTVAR` is not that observed variance; p. 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; footnote 4; §7.1; JSS PDF re-opened 2026-08-23T11:40Z; unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`; the trait-plus-state autocorrelation uses `TRAITVAR` and is not `discreteDRIFTstd`; p. 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:06Z; unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`; the continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`; `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z; unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`; the discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`; `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z); unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`; the discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`; `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z; unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`; `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z; unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`; the asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`; `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z; the affected variance is free `T0VAR`, not `asymDIFFUSION`; unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`; `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`; `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`; `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); 2017-era `addedT0TIPREDVAR` is `t0_b² v` (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z; `T0TIPREDEFFECT %*% TIPREDVAR %*% t(T0TIPREDEFFECT)` immediately after `T0TIPREDEFFECTstd`; form `t0_b` first, then square, then multiply by `v`; a zero coefficient or zero predictor variance is exactly zero; free `T0TIPREDEFFECT` does not require `a < 0`; `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map; `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance; free `T0VAR` is not this extra TI variance; `TRAITVAR` is not this extra TI variance; Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z; form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; `t0_b² v` is the latent extra, not the observed extra; `λ² p_0 + θ` is first-occasion observed variance, not this extra; `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra; `MANIFESTVAR` `θ` is not this extra; Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v`; form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; lasting asymptotic extra requires `a < 0`; `(B / a)² v` is the latent extra, not the observed extra; `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra; `λ² p + θ` is stationary observed variance, not this extra; `MANIFESTVAR` `θ` is not this extra; p. 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance; unstandardised `M` is not `TDPREDEFFECTstd`; `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`; intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`; `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`; Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance; unstandardised `t0_m` is not `T0TDPREDEFFECTstd`; `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`; `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`; `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`; free `T0VAR` does not require `a < 0`; p. 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; default ridge is 0); unstandardised `T0VAR` is not `T0VARstd`; `T0TDPREDEFFECTstd` is not `T0VARstd`; `addedT0TIPREDVAR` is not `T0VARstd`; p. 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend); unstandardised `TRAITVAR` is not `TRAITVARstd`; `T0VARstd` is not `TRAITVARstd` even when both equal 1; `addedT0TIPREDVAR` is not `TRAITVARstd`; p. 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0); unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`; `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1; `MANIFESTVAR` is not `MANIFESTTRAITVARstd`; p. 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug); unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`; `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1; Equation 5 `Var(y)` is not `MANIFESTVARstd`; p. 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`); unstandardised `TIPREDVAR` is not `TIPREDVARstd`; `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1; §7.2 `addedTIPREDVAR` is not `TIPREDVARstd`; p. 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`); unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`; `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1; `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`; p. 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `discreteCINT` is not `discreteCINTstd`; `κ / √p` is not `discreteCINTstd`; `(-κ / a) / √p` is not `discreteCINTstd`; `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`; unstandardised `asymCINT` is not `asymCINTstd`; `κ / √p` is not `asymCINTstd`; `discreteCINTstd` is not `asymCINTstd`; `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`; unstandardised `T0MEANS` is not `T0MEANSstd`; `T0VARstd` is not `T0MEANSstd`; `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`; `MANIFESTMEANSstd` is `τ / √θ` after strictly positive `MANIFESTVAR`; unstandardised `MANIFESTMEANS` is not `MANIFESTMEANSstd`; `MANIFESTVARstd` is not `MANIFESTMEANSstd`; `τ / √(λ² Var(η) + θ)` is not `MANIFESTMEANSstd`;))))), CWC-then-event-time residual lag, irregular already-centered residual log-rate, and strong/strict-gated two-group OLS latent-mean difference are implemented on the stacked psychometric PR and are not implemented-main until exact-head checks, review, and protected-main integration complete; full ESEM/set-ESEM, formative composites, DSEM, and matrix continuous-time dynamics remain accepted-target +**Implementation maturity:** partial — construct classification, valid log-ratio/logistic-normal indicator gates, CPU `f64` OLS and posterior-draw loading point-estimate averaging, Rubin `T_m = Ū_m + (1+1/m) B_m` on draw-level OLS loadings, cluster-mean within/between OLS with the CWC contextual effect and Kish ESS WLS, event-time discrete lag-1 and exact scalar local log-rate, exact scalar forward map and unequal-interval remapping, exact scalar discrete effect of a constant predictor, first-order discrete effect of a time-varying predictor with matched sampling and constancy intervals (Voelkle et al., 2012, Eq. 14), exact scalar discrete process noise (Driver, Oud, & Voelkle, 2017, Eq. 3), exact scalar lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), exact scalar stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; §4.3; p. 16 `asymDIFFUSION`), exact scalar trait-plus-state variance and lagged covariance (Driver et al., 2017, §4.3 `TRAITVAR`; not process noise and not `asymDIFFUSION`), exact scalar observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5, p. 5; Table 2, p. 12; `λ² Var(η) + θ` when `MANIFESTTRAITVAR` is zero, else `λ² Var(η) + θ + ψ`; lagged `λ² cov(η_t, η_{t-1}) + ψ`; `MANIFESTVAR` is `Θ`, not `Var(y)`; `Θ` does not enter lagged observed covariance; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; observed-indicator mean is `τ + λ μ` (`MANIFESTMEANS` is `τ`, not `E(y)`; `CINT` is not `MANIFESTMEANS`; `T0MEANS` is not `E(y)`; Equation 1 is the SDE; not a Kalman filter), exact scalar discrete latent mean `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ` (Driver et al., 2017, Eq. 3; `T0MEANS` is not `μ_t`; `CINT` is not the discrete increment), exact scalar evolved observed-indicator mean `τ + λ μ_t` (Driver et al., 2017, Eq. 5 of that Eq. 3 map; the first-occasion map `τ + λ μ_0` is not `E(y_t)`), exact scalar contemporaneous `TDPREDEFFECT` impulse `m x` (Driver et al., 2017, Eq. 3 fourth summand; Table 2 `TDPREDEFFECT` is `M`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; the §7.2 level-change form is not that impulse), exact scalar observed mean of that contemporaneous impulse `τ + λ(μ_t + m x)` (Driver et al., 2017, Eq. 5 of that Eq. 3 composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`), exact scalar time-independent `TIPREDEFFECT` increment `A^{-1}[e^{A Δt} − I] B z` (Driver et al., 2017, Eq. 3 second summand; Table 2 `TIPREDEFFECT` is `B`, not `κ`, not `M`, and not Voelkle Eq. 14; `B` is not that discrete increment), exact scalar observed mean of that increment `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` (Driver et al., 2017, Eq. 5 of that Eq. 3 printed addend after the `T0MEANS` carry and the `CINT` increment; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`), exact scalar within-interval `TDPREDEFFECT` carry `e^{A(t−u)} M x` for `t0 < u < t` (Driver et al., 2017, Eq. 1–2 Green-function integral of Eq. 2; §7.2 dissipation; not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14), exact scalar observed mean of that carry `τ + λ(μ_t + e^{a(t−u)} m x)` (Driver et al., 2017, Eq. 5 of that carried latent mean; `τ + λ μ_t` is not that observed mean), exact scalar first-occasion `T0TIPREDEFFECT` shift `t0_b z` and Eq. 3 first-summand carry `e^{A Δt} t0_b z` (Driver et al., 2017, Table 3, p. 13; Eq. 3, p. 5; `T0TIPREDEFFECT` is not `TIPREDEFFECT` `B`; `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`; `e^{A Δt} t0_b z` is not `t0_b z`), exact scalar observed mean of that first-occasion carry `τ + λ(μ_t + e^{a Δt} t0_b z)` (Driver et al., 2017, Eq. 5 of that Table 3 / Eq. 3 first-summand composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean), exact scalar first-occasion `T0TDPREDEFFECT` shift `t0_m x0` and Eq. 3 first-summand carry `e^{A Δt} t0_m x0` (Driver et al., 2017, Table 3, p. 13; Eq. 3, p. 5; JSS PDF re-opened 2026-08-20T19:10Z; `T0TDPREDEFFECT` is not `TDPREDEFFECT` `M`; `t0_m x0` is not `M x`; `e^{A Δt} t0_m x0` is not `t0_m x0`; `e^{A Δt} t0_m x0` is not `e^{A(t−u)} M x` for `t0 < u < t`; `t0_m x0` is not `t0_b z`; an impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`), exact scalar observed mean of that first-occasion TD carry `τ + λ(μ_t + e^{a Δt} t0_m x0)` (Driver et al., 2017, Eq. 5 of that Table 3 / Eq. 3 first-summand TD composition; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean; `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean), exact scalar §7.2 level-change `CINT` `κ = −a m x` (Driver et al., 2017, §7.2, pp. 20–21; `a < 0` so `−κ / a = m x`; not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`, and not the extra near-zero-drift latent process also named in §7.2), exact scalar Eq. 3 increment of that setting `(1 − e^{a Δt}) m x` (not `m x`, not `κ`, and not `TIPREDEFFECT`), exact scalar §7.2 extra near-zero-drift latent process contribution `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-20T23:10Z; identification `TDPREDEFFECT` on the extra process is 1; extra `DRIFT` printed as `−0.000001`; precisely 0 causes computational problems; `ε = a` is `a_{ηξ} x Δt e^{a Δt}`; not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`; `ε ≥ 0` fails closed), exact scalar observed mean of that extra-process contribution `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a))` (Driver et al., 2017, Eq. 5, p. 5; §7.2, pp. 22–23; JSS PDF re-opened 2026-08-21T06:12Z; the extra process has `LAMBDA` 0 and is not an observed indicator; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; the contribution is not `E(y_t)`; the evolved-plus-contribution latent mean is not `E(y_t)`), exact scalar after-t0 extra-process contribution `a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)` for `t0 < u < t` and Eq. 5 observed mean `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a))` (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-21T06:32Z; `T0TDPREDEFFECT` uses `Δt` for both the evolution and the extra drive; `TDPREDEFFECT` after `t0` uses `t − u`; `e^{a(t−u)} m x` is a Dirac on the original process, not this `DRIFT` drive), exact scalar §7.2 `asymTIPREDEFFECT` `-B z / a` (Driver et al., 2017, §7.2, pp. 20–21; JSS PDF opened 2026-08-21T13:08Z; expected total change in process means given a time-independent predictor; `a < 0`; not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; `a ≥ 0` fails closed), exact scalar §7.2 `addedTIPREDVAR` `(B / a)² v` (Driver et al., 2017, §7.2, pp. 20–21; stable between-subject variance accounted for by a time-independent predictor; not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`), exact scalar Table 2 `asymCINT` `-κ / a` (Driver et al., 2017, Table 2, p. 12; Eq. 3 as `Δt → ∞`; `a < 0`; not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; `a ≥ 0` fails closed), exact scalar p. 16 stationary `T0MEANS` `-κ / a + −B z / a` (Driver et al., 2017, p. 16; constrained first-occasion mean using `T0MEANSbase` / `T0MEANSfree`; not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean), exact scalar Eq. 5 of that constrained mean `τ + λ(−κ / a + −B z / a)` (Driver et al., 2017, §4.3, pp. 9–10; Eq. 5, p. 5; JSS PDF re-opened 2026-08-21T20:07Z; form the stationary latent mean first, then `τ + λ` of that mean; `τ + λ μ_0` is not that observed mean; `τ + λ(−κ / a)` is not that observed mean when `B z ≠ 0`; `τ + λ μ_t` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`), exact scalar §4.3 / p. 16 stationary `T0VAR` `trait + −q / (2 a) + (B / a)² v` (Driver et al., 2017, §4.3, pp. 9–10; p. 16; JSS PDF re-opened 2026-08-22T03:07Z; not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`), exact scalar lagged covariance of that constrained process `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T19:13Z; trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map), exact scalar Eq. 5 of that lagged covariance `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ` (`Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance), exact scalar later-occasion variance of that constrained process `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T23:12Z; trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt` and is not that later map; `Q_Δt` is not that later map), exact scalar Eq. 5 of that later-occasion variance `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ` (the lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not that later observed variance; the later-occasion latent variance is not that observed variance)), exact scalar later-occasion variance of §4.3 predetermined `T0VAR` `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T05:12Z; trait and `addedTIPREDVAR` do not enter `Q_Δt`; free `T0VAR` `p_0` is not that later map; setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map; stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map; as `Δt → ∞` with stable `a < 0` the composition approaches contemporaneous stationary `T0VAR`; as `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`; nonzero diffusion with `a ≥ 0` is a growing process and is kept. Eq. 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ` (`MANIFESTVAR` is not that later observed variance; the predetermined later-occasion latent variance is not that observed variance; stationary later observed variance is not that observed variance when `p_0` is free)), exact scalar lagged covariance of §4.3 predetermined `T0VAR` `trait + e^{a Δt} p_0 + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T09:04Z; trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`; free `T0VAR` `p_0` is not that lagged map; setting `p_0 = −q / (2 a)` recovers the stationary lagged map; stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map; later-occasion variance includes `Q_Δt` and is not that lagged map. Eq. 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ` (`MANIFESTVAR` does not enter; the predetermined lagged latent covariance is not that observed covariance; predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance; stationary lagged observed covariance is not that observed covariance when `p_0` is free)), exact scalar first-occasion variance of §4.3 predetermined `T0VAR` `trait + p_0 + (B / a)² v` (free `p_0` is not that map; stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free; lagged covariance decays the state and is not that map; later-occasion variance includes `Q_Δt` and is not that map), exact scalar Eq. 5 of that predetermined first-occasion variance `λ²(trait + p_0 + (B / a)² v) + θ + ψ` (`MANIFESTVAR` is not that first-occasion observed variance; the predetermined first-occasion latent variance is not that observed variance; stationary first-occasion observed variance is not that observed variance when `p_0` is free; predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance; later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z; first-occasion lagged omits `e^{a s} Q_u`; later-occasion variance does not lag; stationary lagged uses `−q / (2 a)`; decaying the later total is not that map; Eq. 5 of that later-start lagged covariance is `λ²` of it plus `ψ`; `Θ` does not enter; first-occasion lagged observed omits `e^{a s} Q_u`; later observed variance includes `Q_u` and `θ`; later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z; later-occasion variance at `u` omits `Q_s`; later-start lagged covariance omits `Q_s`; stationary later uses `−q / (2 a)`; evolving the later total as if it were all state is not that map; ignoring `startoffset` omits `e^{2 a s} Q_u`; Eq. 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`; `MANIFESTVAR` is not that observed variance; p. 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; footnote 4; §7.1; JSS PDF re-opened 2026-08-23T11:40Z; unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`; the trait-plus-state autocorrelation uses `TRAITVAR` and is not `discreteDRIFTstd`; p. 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:06Z; unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`; the continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`; `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z; unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`; the discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`; `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z); unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`; the discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`; `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z; unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`; `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z; unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`; the asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`; `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z; the affected variance is free `T0VAR`, not `asymDIFFUSION`; unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`; `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`; `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`; `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); 2017-era `addedT0TIPREDVAR` is `t0_b² v` (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z; `T0TIPREDEFFECT %*% TIPREDVAR %*% t(T0TIPREDEFFECT)` immediately after `T0TIPREDEFFECTstd`; form `t0_b` first, then square, then multiply by `v`; a zero coefficient or zero predictor variance is exactly zero; free `T0TIPREDEFFECT` does not require `a < 0`; `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map; `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance; free `T0VAR` is not this extra TI variance; `TRAITVAR` is not this extra TI variance; Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z; form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; `t0_b² v` is the latent extra, not the observed extra; `λ² p_0 + θ` is first-occasion observed variance, not this extra; `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra; `MANIFESTVAR` `θ` is not this extra; Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v`; form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; lasting asymptotic extra requires `a < 0`; `(B / a)² v` is the latent extra, not the observed extra; `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra; `λ² p + θ` is stationary observed variance, not this extra; `MANIFESTVAR` `θ` is not this extra; p. 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance; unstandardised `M` is not `TDPREDEFFECTstd`; `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`; intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`; `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`; Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance; unstandardised `t0_m` is not `T0TDPREDEFFECTstd`; `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`; `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`; `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`; free `T0VAR` does not require `a < 0`; p. 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; default ridge is 0); unstandardised `T0VAR` is not `T0VARstd`; `T0TDPREDEFFECTstd` is not `T0VARstd`; `addedT0TIPREDVAR` is not `T0VARstd`; p. 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend); unstandardised `TRAITVAR` is not `TRAITVARstd`; `T0VARstd` is not `TRAITVARstd` even when both equal 1; `addedT0TIPREDVAR` is not `TRAITVARstd`; p. 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0); unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`; `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1; `MANIFESTVAR` is not `MANIFESTTRAITVARstd`; p. 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug); unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`; `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1; Equation 5 `Var(y)` is not `MANIFESTVARstd`; p. 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`); unstandardised `TIPREDVAR` is not `TIPREDVARstd`; `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1; §7.2 `addedTIPREDVAR` is not `TIPREDVARstd`; p. 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`); unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`; `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1; `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`; p. 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `discreteCINT` is not `discreteCINTstd`; `κ / √p` is not `discreteCINTstd`; `(-κ / a) / √p` is not `discreteCINTstd`; `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`; unstandardised `asymCINT` is not `asymCINTstd`; `κ / √p` is not `asymCINTstd`; `discreteCINTstd` is not `asymCINTstd`; `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`; unstandardised `T0MEANS` is not `T0MEANSstd`; `T0VARstd` is not `T0MEANSstd`; `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`;))))), CWC-then-event-time residual lag, irregular already-centered residual log-rate, and strong/strict-gated two-group OLS latent-mean difference are implemented on the consolidation vehicle PR `integration/psychometric-standardisation` (folding draft stack #181–#218) and are not implemented-main until exact-head checks, review, and protected-main integration complete; full ESEM/set-ESEM, formative composites, DSEM, and matrix continuous-time dynamics remain accepted-target **Date:** 2026-08-05 **Supersedes:** None. ADR 0012 governs upstream topic measurement/network coordinates; this ADR governs higher-order psychometric structure and longitudinal interpretation. diff --git a/docs/research/multilevel-event-time-recovery.md b/docs/research/multilevel-event-time-recovery.md index 819828dce..3701dcb4b 100644 --- a/docs/research/multilevel-event-time-recovery.md +++ b/docs/research/multilevel-event-time-recovery.md @@ -108,13 +108,11 @@ This slice stays inside `psychometric_core`. It does not add a second invariance 102. refuse unmatched sampling and constancy intervals for a time-varying predictor (Oud & Jansen, 2000, unread); 103. refuse the difference quotient as a continuous-time rate; 104. apply the same event-time map to CWC residuals (still not DSEM); -105. map already-centered lagged residuals with irregular event intervals without re-centering (Curran & Bauer, 2011, pp. 607–608); -106. recover the exact scalar §7.1 trait-plus-state expected autocorrelation `(trait + e^{a Δt} p + added) / (trait + p + added)` (Driver et al., 2017, §7.1, pp. 18–19; Eq. 3–4; §4.3; §7.2 `addedTIPREDVAR`; JSS PDF re-opened 2026-09-01T06:30Z; form the lagged covariance first, then the contemporaneous total, then add `addedTIPREDVAR`, then the ratio; zero trait and zero added recovers `e^{a Δt}` numerically and remains a distinct named quantity; zero state with positive trait or extra is exactly 1; zero total variance fails closed; `a > 0` is a growing covariance ratio and is kept; a non-event clock fails closed; a non-positive event interval fails closed); -107. refuse treating that expected autocorrelation as unstandardised `discreteDRIFT` `e^{a Δt}` and refuse treating it as p. 16 `discreteDRIFTstd` (footnote 4 standardises `DRIFT` using only within-subject `asymDIFFUSION`, not the total; `TRAITVAR` is not the standardisation variance). +105. map already-centered lagged residuals with irregular event intervals without re-centering (Curran & Bauer, 2011, pp. 607–608). ## Claim boundary -This is two-level OLS and a noiseless scalar continuous-time map. It is not DSEM, not RI-CLPM, not a random-effects sampler, not a Kalman filter, and not a matrix `expm` implementation. The CWC cluster-mean coefficient is the **contextual** effect, not the between-cluster effect. Discrete lags from different event intervals are not one coefficient. Equation 14 is not Equation 12. Discrete process noise \(Q_{\Delta t}\) is not the continuous diffusion \(GG^{\top}\). \(Q_{\Delta t}\) is the conditional residual variance, not \(\operatorname{Var}(\eta_{t})\). Finite-interval \(Q_{\Delta t}\) is not the stationary within-subject variance. Trait variance is not process noise and not the stationary within-subject variance. The §7.1 trait-plus-state expected autocorrelation `(trait + e^{a Δt} p + added) / (trait + p + added)` is not unstandardised `discreteDRIFT` `e^{a Δt}` and is not p. 16 `discreteDRIFTstd`. Measurement-error variance is not the observed-indicator variance. Latent variance is not the observed-indicator variance. Manifest means are not the observed-indicator mean. The latent mean is not the observed-indicator mean. The continuous intercept is not the manifest mean. The first-occasion latent mean is not the evolved latent mean. The continuous intercept is not the discrete mean increment. The first-occasion observed mean is not the evolved observed mean. The contemporaneous `TDPREDEFFECT` impulse is not the continuous intercept, not the time-independent discrete effect, and not Voelkle et al. (2012, Eq. 14). The time-independent `TIPREDEFFECT` increment is not the continuous intercept, not the contemporaneous impulse, not Voelkle et al. (2012, Eq. 14), and not the coefficient `B`. The within-interval `TDPREDEFFECT` carry `e^{A(t−u)} M x` for `t0 < u < t` is not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle et al. (2012, Eq. 14). The evolved observed mean `τ + λ μ_t` is not the contemporaneous-impulse observed mean `τ + λ(μ_t + m x)`. The contemporaneous composition `τ + λ(μ_t + m x)` is not the impulse-carry observed mean `τ + λ(μ_t + e^{a(t−u)} m x)` when `u ≠ t`. The evolved observed mean `τ + λ μ_t` is not the impulse-carry observed mean. The carried latent mean is not `E(y_t)`. The evolved-plus-impulse latent mean is not `E(y_t)`. The evolved observed mean `τ + λ μ_t` is not the time-independent-predictor observed mean `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)`. The contemporaneous composition `τ + λ(μ_t + m x)` is not that time-independent-predictor observed mean. The impulse-carry composition `τ + λ(μ_t + e^{a(t−u)} m x)` is not that time-independent-predictor observed mean when `u ≠ t`. The evolved-plus-increment latent mean is not `E(y_t)`. The Table 3 first-occasion `T0TIPREDEFFECT` shift `t0_b z` is not the Eq. 3 process increment `A^{-1}[e^{A Δt} − I] B z`, not `κ`, and not `M x`. The Eq. 3 first-summand carry `e^{A Δt} t0_b z` is not `t0_b z` and is not that process increment. `T0TIPREDEFFECT` is the coefficient, not the first-occasion shift. The evolved observed mean `τ + λ μ_t` is not the first-occasion TI-predictor observed mean `τ + λ(μ_t + e^{a Δt} t0_b z)`. The process-increment composition `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that first-occasion observed mean. The contemporaneous composition `τ + λ(μ_t + m x)` is not that first-occasion observed mean. The impulse-carry composition `τ + λ(μ_t + e^{a(t−u)} m x)` is not that first-occasion observed mean when `u ≠ t0`. The evolved-plus-T0TIPRED latent mean is not `E(y_t)`. The Table 3 first-occasion `T0TDPREDEFFECT` shift `t0_m x0` is not `M x`, not `e^{A(t−u)} M x` for `t0 < u < t`, not `t0_b z`, not `A^{-1}[e^{A Δt} − I] B z`, and not `κ`. The Eq. 3 first-summand carry `e^{A Δt} t0_m x0` is not `t0_m x0` and is not that within-interval impulse carry. `T0TDPREDEFFECT` is the coefficient, not the first-occasion shift. An impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`. The evolved observed mean `τ + λ μ_t` is not the first-occasion TD-predictor observed mean `τ + λ(μ_t + e^{a Δt} t0_m x0)`. The process-increment composition `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that first-occasion TD observed mean. The contemporaneous composition `τ + λ(μ_t + m x)` is not that first-occasion TD observed mean. The impulse-carry composition `τ + λ(μ_t + e^{a(t−u)} m x)` is not that first-occasion TD observed mean when `u ≠ t0`. The first-occasion TI composition `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that first-occasion TD observed mean. Same numbers as `T0TIPREDEFFECT` yield the same product; Table 3 names a different matrix. The evolved-plus-T0TDPRED latent mean is not `E(y_t)`. The §7.2 level-change `CINT` `κ = −a m x` is not the dissipating Dirac `m x`, not a free `CINT`, and not `A^{-1}[e^{A Δt} − I] B z`. Lasting level change via that `CINT` setting requires `a < 0`. The extra near-zero-drift latent process also named in §7.2 is a different specification and is not that `CINT` setting. The Eq. 3 increment of that setting `(1 − e^{a Δt}) m x` is not the dissipating Dirac `m x`, not `κ`, and not `A^{-1}[e^{A Δt} − I] B z`. Underflow of `e^{a Δt}` to `+0` keeps the equilibrium offset `m x`. The §7.2 extra-process contribution `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` is not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`. Lasting level change via that extra process requires `ε < 0`. Precisely `ε = 0` causes computational problems in the printed specification. The extra-process observed mean `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a))` is not `τ + λ μ_t`, not `τ + λ(μ_t + m x)`, not the contribution, and not the evolved-plus-contribution latent mean. The extra process has `LAMBDA` 0 and is not an observed indicator. `T0TDPREDEFFECT` on the extra process uses `Δt = t − t0` for both the original-process evolution and the extra drive. `TDPREDEFFECT` after `t0` uses `t − u` with `t0 < u < t` for the extra drive while `μ_t` still uses `Δt`. The after-t0 extra-process observed mean is not the first-occasion extra-process observed mean when `u ≠ t0`. The impulse-carry `e^{a(t−u)} m x` is a Dirac on the original process and is not that `DRIFT` drive. The §7.2 `asymTIPREDEFFECT` `-B z / a` is the expected total change in process means given a time-independent predictor. It is not the coefficient `B`, not the finite-interval increment `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`. Lasting asymptotic change via that map requires `a < 0`. The §7.2 `addedTIPREDVAR` `(B / a)² v` is the stable between-subject variance accounted for by that predictor. It is not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`. Table 2 `asymCINT` `-κ / a` is the intercept contribution to the stationary process mean. It is not `κ`, not the finite-interval increment `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`. Lasting asymptotic intercept change via that map requires `a < 0`. Page 16 notes that a `T0MEANS` stationarity constraint includes time-independent predictors; that composition is not this intercept-only map. The p. 16 constrained first-occasion mean `-κ / a + −B z / a` is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean. Equation 5 of that constrained mean `τ + λ(−κ / a + −B z / a)` is not `τ + λ μ_0`, not `τ + λ(−κ / a)` when `B z ≠ 0`, not `τ + λ μ_t`, not `MANIFESTMEANS`, and not the constrained latent mean. The p. 16 constrained first-occasion variance `trait + −q / (2 a) + (B / a)² v` is not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Equation 5 of that constrained variance `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` is not `λ² p_0 + θ`, not `λ²(−q / (2 a)) + θ` when `TRAITVAR` or `addedTIPREDVAR` is nonzero, not `λ² Var(η_t) + θ` when the first occasion is constrained, not `MANIFESTVAR`, and not the constrained latent variance. The lagged covariance of that constrained process `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` is not contemporaneous `T0VAR`, not `e^{a Δt}` of the constrained total, and not `trait + e^{a Δt} p` when `addedTIPREDVAR` is nonzero. Trait variance and `addedTIPREDVAR` do not decay with `e^{a Δt}`. Equation 5 of that lagged covariance `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ` is not `θ`, not contemporaneous `Var(y_0)`, and not the lagged latent covariance. Independent measurement error does not enter lagged observed covariance. The later-occasion variance of that constrained process `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` equals contemporaneous `T0VAR` under stationarity and is not the lagged covariance, not `e^{2 a Δt}` of the constrained total plus `Q_Δt`, and not `Q_Δt` alone. Trait variance and `addedTIPREDVAR` do not enter `Q_Δt`. Equation 5 of that later-occasion variance `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ` is not `θ`, not lagged `cov(y_t, y_{t-1})`, and not the later-occasion latent variance. The later-occasion variance of §4.3 predetermined `T0VAR` `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` is not stationary later-occasion variance when `p_0` is free, not `e^{2 a Δt}` of `trait + p_0 + (B / a)² v` plus `Q_Δt`, and not free `p_0`. Trait variance and `addedTIPREDVAR` do not enter `Q_Δt`. Setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map. As `Δt → ∞` with stable `a < 0` the composition approaches contemporaneous stationary `T0VAR`. Nonzero diffusion with `a ≥ 0` is a growing process and is kept. Equation 5 of that predetermined later-occasion variance `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ` is not `θ`, not the predetermined later-occasion latent variance, and not stationary later-occasion observed variance when `p_0` is free. The lagged covariance of §4.3 predetermined `T0VAR` `trait + e^{a Δt} p_0 + (B / a)² v` is not stationary lagged covariance when `p_0` is free, not later-occasion variance, not `e^{a Δt}` of `trait + p_0 + (B / a)² v`, and not free `p_0`. Trait variance and `addedTIPREDVAR` do not decay with `e^{a Δt}`. Setting `p_0 = −q / (2 a)` recovers the stationary lagged map. As `Δt → ∞` with stable `a < 0` the state term vanishes. A zero-diffusion carry with `a ≥ 0` is `e^{a Δt} p_0` and is kept. Equation 5 of that predetermined lagged covariance `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ` is not `θ`, not the predetermined lagged latent covariance, not predetermined later observed variance, and not stationary lagged observed covariance when `p_0` is free. Independent measurement error does not enter lagged observed covariance. The first-occasion variance of §4.3 predetermined `T0VAR` `trait + p_0 + (B / a)² v` is not stationary first-occasion variance when `p_0` is free, not free `p_0`, not lagged covariance, and not later-occasion variance. Trait variance and `addedTIPREDVAR` do not decay and do not enter `Q_Δt`. Setting `p_0 = −q / (2 a)` recovers the stationary first-occasion map. As `Δt → 0+` the lagged and later maps approach this composition. Trait-only variance does not require a stable drift. Equation 5 of that predetermined first-occasion variance `λ²(trait + p_0 + (B / a)² v) + θ + ψ` is not `θ`, not the predetermined first-occasion latent variance, not stationary first-occasion observed variance, and not predetermined later observed variance when `p_0` is free. The later-start lagged covariance of §4.3 predetermined `T0VAR` `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` is not first-occasion lagged covariance when `u > 0`, not later-occasion variance, not stationary lagged covariance when `p_0` is free, and not `e^{a s}` of the later total. Trait variance and `addedTIPREDVAR` do not decay with `e^{a s}`. Setting `p_0 = −q / (2 a)` recovers the stationary lagged map. As `u → 0+` the composition approaches first-occasion lagged covariance. As `s → 0+` the composition approaches later-occasion variance at `u`. Nonzero diffusion with `a ≥ 0` is a growing process and is kept. Equation 5 of that later-start lagged covariance `λ²(trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v) + ψ` is not `θ`, not the later-start lagged latent covariance, not first-occasion lagged observed covariance, not predetermined later observed variance, and not stationary lagged observed covariance when `p_0` is free. Independent measurement error does not enter lagged observed covariance. The later-start later-occasion variance of §4.3 predetermined `T0VAR` `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` is not later-occasion variance at `u` when `s > 0`, not later-start lagged covariance, not stationary later-occasion variance when `p_0` is free, not `e^{2 a s}` of the later total plus `Q_s`, and not later-occasion variance over the lag interval alone when `u > 0`. Trait variance and `addedTIPREDVAR` do not enter `Q_s`. Chapman–Kolmogorov writes `Q_{u+s} = e^{2 a s} Q_u + Q_s`. Setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map. As `u → 0+` the composition approaches later-occasion variance over `s`. As `s → 0+` the composition approaches later-occasion variance at `u`. Nonzero diffusion with `a ≥ 0` is a growing process and is kept. Equation 5 of that later-start later-occasion variance `λ²(trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v) + θ + ψ` is not `θ`, not the later-start later-occasion latent variance, not predetermined later observed variance, not later-start lagged observed covariance, and not stationary later-occasion observed variance when `p_0` is free. Page 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4: standardise `DRIFT` using only within-subject variance, not the total). Unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`. The §7.1 trait-plus-state autocorrelation `(trait + e^{a Δt} p + added) / (trait + p + added)` uses `TRAITVAR` and is not `discreteDRIFTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4: standardise process noise using only within-subject variance, not the total). Unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`. The continuous standardisation `q / (−q / (2 a)) = −2 a` is not `discreteDIFFUSIONstd`. `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`. `TRAITVAR` is not the standardisation variance. Page 16 `DIFFUSIONstd` is `q / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4: standardise process noise using only within-subject variance, not the total). Unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`. The discrete standardisation `Q_Δt / (−q / (2 a)) = 1 − exp(2 a Δt)` depends on the event interval and is not `DIFFUSIONstd`. `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`. `TRAITVAR` is not the standardisation variance. Page 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4: standardise `DRIFT` using only within-subject variance, not the total). Unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`. The discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`. `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (footnote 4: standardise using only the relevant variance, not the total; the affecting variance is `TIPREDVAR`; the affected variance is `asymDIFFUSION`). Unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`. The finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`. `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (footnote 4: standardise using only the relevant variance, not the total; the affecting variance is `TIPREDVAR`; the affected variance is `asymDIFFUSION`). Unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`. The asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`. The finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`. `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Table 3 / p. 16 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (footnote 4: standardise using only the relevant variance, not the total; the affecting variance is `TIPREDVAR`; the affected variance is free `T0VAR`, not `asymDIFFUSION`). Unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`. `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` uses `asymDIFFUSION` and is not `T0TIPREDEFFECTstd`. `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`. `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. 2017-era `addedT0TIPREDVAR` is `t0_b² v` (`T0TIPREDEFFECT %*% TIPREDVAR %*% t(T0TIPREDEFFECT)` immediately after `T0TIPREDEFFECTstd`). A zero coefficient or zero predictor variance is exactly zero. Free `T0TIPREDEFFECT` does not require `a < 0`. `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map. `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance. Free `T0VAR` is not this extra TI variance. `TRAITVAR` is not this extra TI variance. Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v`. Form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`. A zero loading or zero extra is exactly zero. `t0_b² v` is the latent extra, not the observed extra. `λ² p_0 + θ` is first-occasion observed variance, not this extra. `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra. `MANIFESTVAR` `θ` is not this extra. Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v`. Form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`. A zero loading or zero extra is exactly zero. Lasting asymptotic extra requires `a < 0`. `(B / a)² v` is the latent extra, not the observed extra. `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra. `λ² p + θ` is stationary observed variance, not this extra. `MANIFESTVAR` `θ` is not this extra. Page 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive time-dependent predictor variance `v` (footnote 4: standardise using only the relevant variance, not the total; the affecting variance is the TD predictor; the affected variance is `asymDIFFUSION`). Unstandardised `M` is defined for a zero coefficient and for zero predictor variance and is not `TDPREDEFFECTstd`. `TIPREDEFFECTstd` `B · √v / √p` is a different named matrix even when `M = B`. The finite-interval intercept-style standardisation `A^{-1}[e^{A Δt} − I] M · √v / √p` depends on the event interval and is not `TDPREDEFFECTstd`. `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive time-dependent predictor variance `v` (footnote 4: standardise using only the relevant variance, not the total; the affecting variance is the TD predictor, not `TIPREDVAR`; the affected variance is free `T0VAR`, not `asymDIFFUSION`). Unstandardised `t0_m` is defined for a zero coefficient and for zero predictor variance and is not `T0TDPREDEFFECTstd`. `TDPREDEFFECTstd` `m · √v / √(-q / (2 a))` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`. `T0TIPREDEFFECTstd` `t0_b · √v / √p_0` is a different named matrix even when `t0_m = t0_b`. `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Free `T0VAR` does not require `a < 0`. Page 16 `T0VARstd` is the correlation form `solve(sqrt(diag(T0VAR))) %&% T0VAR` after strictly positive free `T0VAR` `p_0` (2017-era `summary.ctsemFit.R`; OpenMx `%&%` is `t(A) %*% B %*% A`; the default ridge is 0; the scalar map is `p_0 / p_0 = 1`). Unstandardised `T0VAR` is defined for a zero first-occasion variance and is not `T0VARstd`. Zero `p_0` fails closed. Distinct positive `p_0` recover the same 1. `T0TDPREDEFFECTstd` `t0_m · √v / √p_0` depends on `p_0` and is not `T0VARstd`. `addedT0TIPREDVAR` `t0_b² v` is extra TI variance, not this correlation. `TRAITVAR` is not the standardisation variance. Page 16 `TRAITVARstd` is the correlation form `solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR` after strictly positive `TRAITVAR` (2017-era `summary.ctsemFit.R` forms it only when `TRAITVAR != 0`; OpenMx `%&%` is `t(A) %*% B %*% A`; unlike `T0VARstd` there is no ridge addend; the scalar map is `trait / trait = 1`). Unstandardised `TRAITVAR` is defined for a zero trait and is not `TRAITVARstd`. Zero `TRAITVAR` fails closed. Distinct positive `trait` recover the same 1. `T0VARstd` `p_0 / p_0 = 1` recovers the same number and remains a distinct named quantity. `addedT0TIPREDVAR` `t0_b² v` is extra TI variance, not this correlation. `TRAITVAR` does not require `a < 0`. Page 16 `MANIFESTTRAITVARstd` is the correlation form `solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR` after strictly positive `MANIFESTTRAITVAR` (2017-era `summary.ctsemFit.R` forms it only when `MANIFESTTRAITVAR != 0`; OpenMx `%&%` is `t(A) %*% B %*% A`; unlike `TRAITVARstd` the 2017-era source adds ridging; the default ridge is 0; the scalar map is `ψ / ψ = 1`). Unstandardised `MANIFESTTRAITVAR` is defined for a zero trait and is not `MANIFESTTRAITVARstd`. Zero `MANIFESTTRAITVAR` fails closed. Distinct positive `ψ` recover the same 1. `TRAITVARstd` `trait / trait = 1` recovers the same number and remains a distinct named quantity. `MANIFESTVAR` `θ` is measurement error, not this correlation. `MANIFESTTRAITVAR` does not require `a < 0`. Page 16 `MANIFESTVARstd` is the correlation form `solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR` after strictly positive `MANIFESTVAR` (2017-era `summary.ctsemFit.R` forms it whenever `verbose = TRUE`; OpenMx `%&%` is `t(A) %*% B %*% A`; unlike `TRAITVARstd` the 2017-era source adds ridging; the default ridge is 0; the 2017-era `dimnames` assignment to `latentNames` is a source bug; the scalar map is `θ / θ = 1`). Unstandardised `MANIFESTVAR` is defined for a zero residual and is not `MANIFESTVARstd`. Zero `MANIFESTVAR` makes `solve(sqrt(0))` fail and fails closed. Distinct positive `θ` recover the same 1. `MANIFESTTRAITVARstd` `ψ / ψ = 1` recovers the same number and remains a distinct named quantity. Equation 5 `λ² Var(η) + θ` is `Var(y)`, not this correlation. `MANIFESTVAR` does not require `a < 0`. Page 16 `TIPREDVARstd` is the correlation form `solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR` after strictly positive `TIPREDVAR` (2017-era `summary.ctsemFit.R` forms it whenever `verbose = TRUE` and `n.TIpred > 0`; OpenMx `%&%` is `t(A) %*% B %*% A`; unlike `TRAITVARstd` the 2017-era source adds ridging; the default ridge is 0; `dimnames` are `TIpredNames`; the scalar map is `v / v = 1`). Unstandardised `TIPREDVAR` is defined for a zero predictor and is not `TIPREDVARstd`. Zero `TIPREDVAR` makes `solve(sqrt(0))` fail and fails closed. Distinct positive `v` recover the same 1. `MANIFESTVARstd` `θ / θ = 1` recovers the same number and remains a distinct named quantity. Section 7.2 `addedTIPREDVAR` `(B / a)² v` is extra process variance, not this correlation. `TIPREDVAR` does not require `a < 0`. Page 16 `asymDIFFUSIONstd` is the correlation form `solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION` after strictly positive `asymDIFFUSION` `-q / (2 a)` (2017-era `summary.ctsemFit.R` forms it whenever `verbose = TRUE`; OpenMx `%&%` is `t(A) %*% B %*% A`; the 2017-era source adds ridging; the default ridge is 0; `dimnames` are `latentNames`; the scalar map is `p / p = 1`). Unstandardised `asymDIFFUSION` is defined for a zero process and is not `asymDIFFUSIONstd`. Zero `q` makes `solve(sqrt(0))` fail and fails closed. Distinct positive `p` recover the same 1. `TIPREDVARstd` `v / v = 1` recovers the same number and remains a distinct named quantity. `DIFFUSIONstd` `q / p = −2 a` is the continuous-diffusion ratio, not this correlation. Lasting `asymDIFFUSION` requires `a < 0`. Page 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION` `-q / (2 a)` (2017-era `summary.ctsemFit.R` forms unstandardised `discreteCINT` whenever `verbose = TRUE` as `solve(DRIFT) %*% (discreteDRIFT − I) %*% CINT`; that source does not form a `discreteCINTstd` matrix; the scalar map is the footnote 4 standardisation of that named discrete intercept). Unstandardised `discreteCINT` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteCINTstd`. Zero `q` has no positive process SD and fails closed. `κ / √p` does not depend on `Δt` and is not this finite-interval map. `(-κ / a) / √p` is the standardised asymptotic intercept and is not this map. Lasting `asymDIFFUSION` requires `a < 0`. Page 16 `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION` `-q / (2 a)` (2017-era `summary.ctsemFit.R` forms unstandardised `asymCINT` whenever `verbose = TRUE` as `-solve(DRIFT) %*% CINT`; that source does not form an `asymCINTstd` matrix; the scalar map is the footnote 4 standardisation of that named asymptotic intercept). Unstandardised `asymCINT` is defined for a zero process and is not `asymCINTstd`. Zero `q` has no positive process SD and fails closed. `κ / √p` is the continuous intercept standardisation and is not this total-change map. `A^{-1}[e^{A Δt} − I] κ / √p` depends on the event interval and is not this `Δt → ∞` map. Lasting `asymDIFFUSION` requires `a < 0`. Page 16 `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR` `p_0` (2017-era `summary.ctsemFit.R` forms unstandardised `T0MEANS` and does not form a `T0MEANSstd` matrix; the scalar map is the footnote 4 standardisation of that named first-occasion mean; relevant variance is free `T0VAR`, not `asymDIFFUSION`). Unstandardised `T0MEANS` is defined for a zero first-occasion variance and is not `T0MEANSstd`. Zero `p_0` has no positive SD and fails closed. `T0VARstd` `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. Free `T0MEANS` does not require `a < 0`. Page 16 `MANIFESTMEANSstd` is `τ / √θ` after strictly positive `MANIFESTVAR` `θ` (2017-era `summary.ctsemFit.R` forms unstandardised `MANIFESTMEANS` and does not form a `MANIFESTMEANSstd` matrix; the scalar map is the footnote 4 standardisation of that named measurement intercept; relevant variance is residual `MANIFESTVAR`, not total observed `Var(y)`). Unstandardised `MANIFESTMEANS` is defined for a zero residual and is not `MANIFESTMEANSstd`. Zero `θ` has no positive SD and fails closed. `MANIFESTVARstd` `θ / θ = 1` recovers the same number when `τ = √θ` and remains a distinct named quantity. `τ / √(λ² Var(η) + θ)` uses total observed variance and is not this residual map. `MANIFESTMEANS` does not require `a < 0`. +This is two-level OLS and a noiseless scalar continuous-time map. It is not DSEM, not RI-CLPM, not a random-effects sampler, not a Kalman filter, and not a matrix `expm` implementation. The CWC cluster-mean coefficient is the **contextual** effect, not the between-cluster effect. Discrete lags from different event intervals are not one coefficient. Equation 14 is not Equation 12. Discrete process noise \(Q_{\Delta t}\) is not the continuous diffusion \(GG^{\top}\). \(Q_{\Delta t}\) is the conditional residual variance, not \(\operatorname{Var}(\eta_{t})\). Finite-interval \(Q_{\Delta t}\) is not the stationary within-subject variance. Trait variance is not process noise and not the stationary within-subject variance. Measurement-error variance is not the observed-indicator variance. Latent variance is not the observed-indicator variance. Manifest means are not the observed-indicator mean. The latent mean is not the observed-indicator mean. The continuous intercept is not the manifest mean. The first-occasion latent mean is not the evolved latent mean. The continuous intercept is not the discrete mean increment. The first-occasion observed mean is not the evolved observed mean. The contemporaneous `TDPREDEFFECT` impulse is not the continuous intercept, not the time-independent discrete effect, and not Voelkle et al. (2012, Eq. 14). The time-independent `TIPREDEFFECT` increment is not the continuous intercept, not the contemporaneous impulse, not Voelkle et al. (2012, Eq. 14), and not the coefficient `B`. The within-interval `TDPREDEFFECT` carry `e^{A(t−u)} M x` for `t0 < u < t` is not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle et al. (2012, Eq. 14). The evolved observed mean `τ + λ μ_t` is not the contemporaneous-impulse observed mean `τ + λ(μ_t + m x)`. The contemporaneous composition `τ + λ(μ_t + m x)` is not the impulse-carry observed mean `τ + λ(μ_t + e^{a(t−u)} m x)` when `u ≠ t`. The evolved observed mean `τ + λ μ_t` is not the impulse-carry observed mean. The carried latent mean is not `E(y_t)`. The evolved-plus-impulse latent mean is not `E(y_t)`. The evolved observed mean `τ + λ μ_t` is not the time-independent-predictor observed mean `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)`. The contemporaneous composition `τ + λ(μ_t + m x)` is not that time-independent-predictor observed mean. The impulse-carry composition `τ + λ(μ_t + e^{a(t−u)} m x)` is not that time-independent-predictor observed mean when `u ≠ t`. The evolved-plus-increment latent mean is not `E(y_t)`. The Table 3 first-occasion `T0TIPREDEFFECT` shift `t0_b z` is not the Eq. 3 process increment `A^{-1}[e^{A Δt} − I] B z`, not `κ`, and not `M x`. The Eq. 3 first-summand carry `e^{A Δt} t0_b z` is not `t0_b z` and is not that process increment. `T0TIPREDEFFECT` is the coefficient, not the first-occasion shift. The evolved observed mean `τ + λ μ_t` is not the first-occasion TI-predictor observed mean `τ + λ(μ_t + e^{a Δt} t0_b z)`. The process-increment composition `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that first-occasion observed mean. The contemporaneous composition `τ + λ(μ_t + m x)` is not that first-occasion observed mean. The impulse-carry composition `τ + λ(μ_t + e^{a(t−u)} m x)` is not that first-occasion observed mean when `u ≠ t0`. The evolved-plus-T0TIPRED latent mean is not `E(y_t)`. The Table 3 first-occasion `T0TDPREDEFFECT` shift `t0_m x0` is not `M x`, not `e^{A(t−u)} M x` for `t0 < u < t`, not `t0_b z`, not `A^{-1}[e^{A Δt} − I] B z`, and not `κ`. The Eq. 3 first-summand carry `e^{A Δt} t0_m x0` is not `t0_m x0` and is not that within-interval impulse carry. `T0TDPREDEFFECT` is the coefficient, not the first-occasion shift. An impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`. The evolved observed mean `τ + λ μ_t` is not the first-occasion TD-predictor observed mean `τ + λ(μ_t + e^{a Δt} t0_m x0)`. The process-increment composition `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that first-occasion TD observed mean. The contemporaneous composition `τ + λ(μ_t + m x)` is not that first-occasion TD observed mean. The impulse-carry composition `τ + λ(μ_t + e^{a(t−u)} m x)` is not that first-occasion TD observed mean when `u ≠ t0`. The first-occasion TI composition `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that first-occasion TD observed mean. Same numbers as `T0TIPREDEFFECT` yield the same product; Table 3 names a different matrix. The evolved-plus-T0TDPRED latent mean is not `E(y_t)`. The §7.2 level-change `CINT` `κ = −a m x` is not the dissipating Dirac `m x`, not a free `CINT`, and not `A^{-1}[e^{A Δt} − I] B z`. Lasting level change via that `CINT` setting requires `a < 0`. The extra near-zero-drift latent process also named in §7.2 is a different specification and is not that `CINT` setting. The Eq. 3 increment of that setting `(1 − e^{a Δt}) m x` is not the dissipating Dirac `m x`, not `κ`, and not `A^{-1}[e^{A Δt} − I] B z`. Underflow of `e^{a Δt}` to `+0` keeps the equilibrium offset `m x`. The §7.2 extra-process contribution `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` is not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`. Lasting level change via that extra process requires `ε < 0`. Precisely `ε = 0` causes computational problems in the printed specification. The extra-process observed mean `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a))` is not `τ + λ μ_t`, not `τ + λ(μ_t + m x)`, not the contribution, and not the evolved-plus-contribution latent mean. The extra process has `LAMBDA` 0 and is not an observed indicator. `T0TDPREDEFFECT` on the extra process uses `Δt = t − t0` for both the original-process evolution and the extra drive. `TDPREDEFFECT` after `t0` uses `t − u` with `t0 < u < t` for the extra drive while `μ_t` still uses `Δt`. The after-t0 extra-process observed mean is not the first-occasion extra-process observed mean when `u ≠ t0`. The impulse-carry `e^{a(t−u)} m x` is a Dirac on the original process and is not that `DRIFT` drive. The §7.2 `asymTIPREDEFFECT` `-B z / a` is the expected total change in process means given a time-independent predictor. It is not the coefficient `B`, not the finite-interval increment `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`. Lasting asymptotic change via that map requires `a < 0`. The §7.2 `addedTIPREDVAR` `(B / a)² v` is the stable between-subject variance accounted for by that predictor. It is not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`. Table 2 `asymCINT` `-κ / a` is the intercept contribution to the stationary process mean. It is not `κ`, not the finite-interval increment `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`. Lasting asymptotic intercept change via that map requires `a < 0`. Page 16 notes that a `T0MEANS` stationarity constraint includes time-independent predictors; that composition is not this intercept-only map. The p. 16 constrained first-occasion mean `-κ / a + −B z / a` is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean. Equation 5 of that constrained mean `τ + λ(−κ / a + −B z / a)` is not `τ + λ μ_0`, not `τ + λ(−κ / a)` when `B z ≠ 0`, not `τ + λ μ_t`, not `MANIFESTMEANS`, and not the constrained latent mean. The p. 16 constrained first-occasion variance `trait + −q / (2 a) + (B / a)² v` is not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Equation 5 of that constrained variance `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` is not `λ² p_0 + θ`, not `λ²(−q / (2 a)) + θ` when `TRAITVAR` or `addedTIPREDVAR` is nonzero, not `λ² Var(η_t) + θ` when the first occasion is constrained, not `MANIFESTVAR`, and not the constrained latent variance. The lagged covariance of that constrained process `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` is not contemporaneous `T0VAR`, not `e^{a Δt}` of the constrained total, and not `trait + e^{a Δt} p` when `addedTIPREDVAR` is nonzero. Trait variance and `addedTIPREDVAR` do not decay with `e^{a Δt}`. Equation 5 of that lagged covariance `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ` is not `θ`, not contemporaneous `Var(y_0)`, and not the lagged latent covariance. Independent measurement error does not enter lagged observed covariance. The later-occasion variance of that constrained process `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` equals contemporaneous `T0VAR` under stationarity and is not the lagged covariance, not `e^{2 a Δt}` of the constrained total plus `Q_Δt`, and not `Q_Δt` alone. Trait variance and `addedTIPREDVAR` do not enter `Q_Δt`. Equation 5 of that later-occasion variance `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ` is not `θ`, not lagged `cov(y_t, y_{t-1})`, and not the later-occasion latent variance. The later-occasion variance of §4.3 predetermined `T0VAR` `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` is not stationary later-occasion variance when `p_0` is free, not `e^{2 a Δt}` of `trait + p_0 + (B / a)² v` plus `Q_Δt`, and not free `p_0`. Trait variance and `addedTIPREDVAR` do not enter `Q_Δt`. Setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map. As `Δt → ∞` with stable `a < 0` the composition approaches contemporaneous stationary `T0VAR`. Nonzero diffusion with `a ≥ 0` is a growing process and is kept. Equation 5 of that predetermined later-occasion variance `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ` is not `θ`, not the predetermined later-occasion latent variance, and not stationary later-occasion observed variance when `p_0` is free. The lagged covariance of §4.3 predetermined `T0VAR` `trait + e^{a Δt} p_0 + (B / a)² v` is not stationary lagged covariance when `p_0` is free, not later-occasion variance, not `e^{a Δt}` of `trait + p_0 + (B / a)² v`, and not free `p_0`. Trait variance and `addedTIPREDVAR` do not decay with `e^{a Δt}`. Setting `p_0 = −q / (2 a)` recovers the stationary lagged map. As `Δt → ∞` with stable `a < 0` the state term vanishes. A zero-diffusion carry with `a ≥ 0` is `e^{a Δt} p_0` and is kept. Equation 5 of that predetermined lagged covariance `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ` is not `θ`, not the predetermined lagged latent covariance, not predetermined later observed variance, and not stationary lagged observed covariance when `p_0` is free. Independent measurement error does not enter lagged observed covariance. The first-occasion variance of §4.3 predetermined `T0VAR` `trait + p_0 + (B / a)² v` is not stationary first-occasion variance when `p_0` is free, not free `p_0`, not lagged covariance, and not later-occasion variance. Trait variance and `addedTIPREDVAR` do not decay and do not enter `Q_Δt`. Setting `p_0 = −q / (2 a)` recovers the stationary first-occasion map. As `Δt → 0+` the lagged and later maps approach this composition. Trait-only variance does not require a stable drift. Equation 5 of that predetermined first-occasion variance `λ²(trait + p_0 + (B / a)² v) + θ + ψ` is not `θ`, not the predetermined first-occasion latent variance, not stationary first-occasion observed variance, and not predetermined later observed variance when `p_0` is free. The later-start lagged covariance of §4.3 predetermined `T0VAR` `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` is not first-occasion lagged covariance when `u > 0`, not later-occasion variance, not stationary lagged covariance when `p_0` is free, and not `e^{a s}` of the later total. Trait variance and `addedTIPREDVAR` do not decay with `e^{a s}`. Setting `p_0 = −q / (2 a)` recovers the stationary lagged map. As `u → 0+` the composition approaches first-occasion lagged covariance. As `s → 0+` the composition approaches later-occasion variance at `u`. Nonzero diffusion with `a ≥ 0` is a growing process and is kept. Equation 5 of that later-start lagged covariance `λ²(trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v) + ψ` is not `θ`, not the later-start lagged latent covariance, not first-occasion lagged observed covariance, not predetermined later observed variance, and not stationary lagged observed covariance when `p_0` is free. Independent measurement error does not enter lagged observed covariance. The later-start later-occasion variance of §4.3 predetermined `T0VAR` `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` is not later-occasion variance at `u` when `s > 0`, not later-start lagged covariance, not stationary later-occasion variance when `p_0` is free, not `e^{2 a s}` of the later total plus `Q_s`, and not later-occasion variance over the lag interval alone when `u > 0`. Trait variance and `addedTIPREDVAR` do not enter `Q_s`. Chapman–Kolmogorov writes `Q_{u+s} = e^{2 a s} Q_u + Q_s`. Setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map. As `u → 0+` the composition approaches later-occasion variance over `s`. As `s → 0+` the composition approaches later-occasion variance at `u`. Nonzero diffusion with `a ≥ 0` is a growing process and is kept. Equation 5 of that later-start later-occasion variance `λ²(trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v) + θ + ψ` is not `θ`, not the later-start later-occasion latent variance, not predetermined later observed variance, not later-start lagged observed covariance, and not stationary later-occasion observed variance when `p_0` is free. Page 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4: standardise `DRIFT` using only within-subject variance, not the total). Unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`. The §7.1 trait-plus-state autocorrelation `(trait + e^{a Δt} p + added) / (trait + p + added)` uses `TRAITVAR` and is not `discreteDRIFTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4: standardise process noise using only within-subject variance, not the total). Unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`. The continuous standardisation `q / (−q / (2 a)) = −2 a` is not `discreteDIFFUSIONstd`. `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`. `TRAITVAR` is not the standardisation variance. Page 16 `DIFFUSIONstd` is `q / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4: standardise process noise using only within-subject variance, not the total). Unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`. The discrete standardisation `Q_Δt / (−q / (2 a)) = 1 − exp(2 a Δt)` depends on the event interval and is not `DIFFUSIONstd`. `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`. `TRAITVAR` is not the standardisation variance. Page 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4: standardise `DRIFT` using only within-subject variance, not the total). Unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`. The discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`. `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (footnote 4: standardise using only the relevant variance, not the total; the affecting variance is `TIPREDVAR`; the affected variance is `asymDIFFUSION`). Unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`. The finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`. `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (footnote 4: standardise using only the relevant variance, not the total; the affecting variance is `TIPREDVAR`; the affected variance is `asymDIFFUSION`). Unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`. The asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`. The finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`. `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Table 3 / p. 16 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (footnote 4: standardise using only the relevant variance, not the total; the affecting variance is `TIPREDVAR`; the affected variance is free `T0VAR`, not `asymDIFFUSION`). Unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`. `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` uses `asymDIFFUSION` and is not `T0TIPREDEFFECTstd`. `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`. `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. 2017-era `addedT0TIPREDVAR` is `t0_b² v` (`T0TIPREDEFFECT %*% TIPREDVAR %*% t(T0TIPREDEFFECT)` immediately after `T0TIPREDEFFECTstd`). A zero coefficient or zero predictor variance is exactly zero. Free `T0TIPREDEFFECT` does not require `a < 0`. `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map. `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance. Free `T0VAR` is not this extra TI variance. `TRAITVAR` is not this extra TI variance. Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v`. Form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`. A zero loading or zero extra is exactly zero. `t0_b² v` is the latent extra, not the observed extra. `λ² p_0 + θ` is first-occasion observed variance, not this extra. `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra. `MANIFESTVAR` `θ` is not this extra. Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v`. Form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`. A zero loading or zero extra is exactly zero. Lasting asymptotic extra requires `a < 0`. `(B / a)² v` is the latent extra, not the observed extra. `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra. `λ² p + θ` is stationary observed variance, not this extra. `MANIFESTVAR` `θ` is not this extra. Page 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive time-dependent predictor variance `v` (footnote 4: standardise using only the relevant variance, not the total; the affecting variance is the TD predictor; the affected variance is `asymDIFFUSION`). Unstandardised `M` is defined for a zero coefficient and for zero predictor variance and is not `TDPREDEFFECTstd`. `TIPREDEFFECTstd` `B · √v / √p` is a different named matrix even when `M = B`. The finite-interval intercept-style standardisation `A^{-1}[e^{A Δt} − I] M · √v / √p` depends on the event interval and is not `TDPREDEFFECTstd`. `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive time-dependent predictor variance `v` (footnote 4: standardise using only the relevant variance, not the total; the affecting variance is the TD predictor, not `TIPREDVAR`; the affected variance is free `T0VAR`, not `asymDIFFUSION`). Unstandardised `t0_m` is defined for a zero coefficient and for zero predictor variance and is not `T0TDPREDEFFECTstd`. `TDPREDEFFECTstd` `m · √v / √(-q / (2 a))` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`. `T0TIPREDEFFECTstd` `t0_b · √v / √p_0` is a different named matrix even when `t0_m = t0_b`. `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Free `T0VAR` does not require `a < 0`. Page 16 `T0VARstd` is the correlation form `solve(sqrt(diag(T0VAR))) %&% T0VAR` after strictly positive free `T0VAR` `p_0` (2017-era `summary.ctsemFit.R`; OpenMx `%&%` is `t(A) %*% B %*% A`; the default ridge is 0; the scalar map is `p_0 / p_0 = 1`). Unstandardised `T0VAR` is defined for a zero first-occasion variance and is not `T0VARstd`. Zero `p_0` fails closed. Distinct positive `p_0` recover the same 1. `T0TDPREDEFFECTstd` `t0_m · √v / √p_0` depends on `p_0` and is not `T0VARstd`. `addedT0TIPREDVAR` `t0_b² v` is extra TI variance, not this correlation. `TRAITVAR` is not the standardisation variance. Page 16 `TRAITVARstd` is the correlation form `solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR` after strictly positive `TRAITVAR` (2017-era `summary.ctsemFit.R` forms it only when `TRAITVAR != 0`; OpenMx `%&%` is `t(A) %*% B %*% A`; unlike `T0VARstd` there is no ridge addend; the scalar map is `trait / trait = 1`). Unstandardised `TRAITVAR` is defined for a zero trait and is not `TRAITVARstd`. Zero `TRAITVAR` fails closed. Distinct positive `trait` recover the same 1. `T0VARstd` `p_0 / p_0 = 1` recovers the same number and remains a distinct named quantity. `addedT0TIPREDVAR` `t0_b² v` is extra TI variance, not this correlation. `TRAITVAR` does not require `a < 0`. Page 16 `MANIFESTTRAITVARstd` is the correlation form `solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR` after strictly positive `MANIFESTTRAITVAR` (2017-era `summary.ctsemFit.R` forms it only when `MANIFESTTRAITVAR != 0`; OpenMx `%&%` is `t(A) %*% B %*% A`; unlike `TRAITVARstd` the 2017-era source adds ridging; the default ridge is 0; the scalar map is `ψ / ψ = 1`). Unstandardised `MANIFESTTRAITVAR` is defined for a zero trait and is not `MANIFESTTRAITVARstd`. Zero `MANIFESTTRAITVAR` fails closed. Distinct positive `ψ` recover the same 1. `TRAITVARstd` `trait / trait = 1` recovers the same number and remains a distinct named quantity. `MANIFESTVAR` `θ` is measurement error, not this correlation. `MANIFESTTRAITVAR` does not require `a < 0`. Page 16 `MANIFESTVARstd` is the correlation form `solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR` after strictly positive `MANIFESTVAR` (2017-era `summary.ctsemFit.R` forms it whenever `verbose = TRUE`; OpenMx `%&%` is `t(A) %*% B %*% A`; unlike `TRAITVARstd` the 2017-era source adds ridging; the default ridge is 0; the 2017-era `dimnames` assignment to `latentNames` is a source bug; the scalar map is `θ / θ = 1`). Unstandardised `MANIFESTVAR` is defined for a zero residual and is not `MANIFESTVARstd`. Zero `MANIFESTVAR` makes `solve(sqrt(0))` fail and fails closed. Distinct positive `θ` recover the same 1. `MANIFESTTRAITVARstd` `ψ / ψ = 1` recovers the same number and remains a distinct named quantity. Equation 5 `λ² Var(η) + θ` is `Var(y)`, not this correlation. `MANIFESTVAR` does not require `a < 0`. Page 16 `TIPREDVARstd` is the correlation form `solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR` after strictly positive `TIPREDVAR` (2017-era `summary.ctsemFit.R` forms it whenever `verbose = TRUE` and `n.TIpred > 0`; OpenMx `%&%` is `t(A) %*% B %*% A`; unlike `TRAITVARstd` the 2017-era source adds ridging; the default ridge is 0; `dimnames` are `TIpredNames`; the scalar map is `v / v = 1`). Unstandardised `TIPREDVAR` is defined for a zero predictor and is not `TIPREDVARstd`. Zero `TIPREDVAR` makes `solve(sqrt(0))` fail and fails closed. Distinct positive `v` recover the same 1. `MANIFESTVARstd` `θ / θ = 1` recovers the same number and remains a distinct named quantity. Section 7.2 `addedTIPREDVAR` `(B / a)² v` is extra process variance, not this correlation. `TIPREDVAR` does not require `a < 0`. Page 16 `asymDIFFUSIONstd` is the correlation form `solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION` after strictly positive `asymDIFFUSION` `-q / (2 a)` (2017-era `summary.ctsemFit.R` forms it whenever `verbose = TRUE`; OpenMx `%&%` is `t(A) %*% B %*% A`; the 2017-era source adds ridging; the default ridge is 0; `dimnames` are `latentNames`; the scalar map is `p / p = 1`). Unstandardised `asymDIFFUSION` is defined for a zero process and is not `asymDIFFUSIONstd`. Zero `q` makes `solve(sqrt(0))` fail and fails closed. Distinct positive `p` recover the same 1. `TIPREDVARstd` `v / v = 1` recovers the same number and remains a distinct named quantity. `DIFFUSIONstd` `q / p = −2 a` is the continuous-diffusion ratio, not this correlation. Lasting `asymDIFFUSION` requires `a < 0`. Page 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION` `-q / (2 a)` (2017-era `summary.ctsemFit.R` forms unstandardised `discreteCINT` whenever `verbose = TRUE` as `solve(DRIFT) %*% (discreteDRIFT − I) %*% CINT`; that source does not form a `discreteCINTstd` matrix; the scalar map is the footnote 4 standardisation of that named discrete intercept). Unstandardised `discreteCINT` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteCINTstd`. Zero `q` has no positive process SD and fails closed. `κ / √p` does not depend on `Δt` and is not this finite-interval map. `(-κ / a) / √p` is the standardised asymptotic intercept and is not this map. Lasting `asymDIFFUSION` requires `a < 0`. Page 16 `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION` `-q / (2 a)` (2017-era `summary.ctsemFit.R` forms unstandardised `asymCINT` whenever `verbose = TRUE` as `-solve(DRIFT) %*% CINT`; that source does not form an `asymCINTstd` matrix; the scalar map is the footnote 4 standardisation of that named asymptotic intercept). Unstandardised `asymCINT` is defined for a zero process and is not `asymCINTstd`. Zero `q` has no positive process SD and fails closed. `κ / √p` is the continuous intercept standardisation and is not this total-change map. `A^{-1}[e^{A Δt} − I] κ / √p` depends on the event interval and is not this `Δt → ∞` map. Lasting `asymDIFFUSION` requires `a < 0`. Page 16 `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR` `p_0` (2017-era `summary.ctsemFit.R` forms unstandardised `T0MEANS` and does not form a `T0MEANSstd` matrix; the scalar map is the footnote 4 standardisation of that named first-occasion mean; relevant variance is free `T0VAR`, not `asymDIFFUSION`). Unstandardised `T0MEANS` is defined for a zero first-occasion variance and is not `T0MEANSstd`. Zero `p_0` has no positive SD and fails closed. `T0VARstd` `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. Free `T0MEANS` does not require `a < 0`. Page 16 `MANIFESTMEANSstd` is `τ / √θ` after strictly positive `MANIFESTVAR` `θ` (2017-era `summary.ctsemFit.R` forms unstandardised `MANIFESTMEANS` and does not form a `MANIFESTMEANSstd` matrix; the scalar map is the footnote 4 standardisation of that named measurement intercept; relevant variance is residual `MANIFESTVAR`, not total observed `Var(y)`). Unstandardised `MANIFESTMEANS` is defined for a zero residual and is not `MANIFESTMEANSstd`. Zero `θ` has no positive SD and fails closed. `MANIFESTVARstd` `θ / θ = 1` recovers the same number when `τ = √θ` and remains a distinct named quantity. `τ / √(λ² Var(η) + θ)` uses total observed variance and is not this residual map. `MANIFESTMEANS` does not require `a < 0`. ## Authoritative sources @@ -148,7 +146,6 @@ The Voelkle et al. (2012) ZORA accepted manuscript was re-opened 2026-08-18T21:0 - **Discrete latent variance.** Equations 3–4 write \(Q_{\Delta t}\) as the covariance of the stochastic integral, so \(Q_{\Delta t}=\operatorname{cov}(\eta_{ti}\mid\eta_{t-1,i})\) when \(\xi\) and \(z\) are given. The law of total variance on that pair is \(\operatorname{Var}(\eta_{ti})=A_{\Delta t}\operatorname{Var}(\eta_{t-1,i})A_{\Delta t}^{\top}+Q_{\Delta t}\). The scalar map is \(\mathrm{e}^{2a\Delta t}p+Q_{\Delta t}\). This is not a Kalman measurement update. A zero prior variance is exactly \(Q_{\Delta t}\). Binary64 underflow of \(\mathrm{e}^{2a\Delta t}\) keeps \(Q_{\Delta t}\). Finite-\(z\) exponential overflow rewrites as \(\exp(\ln p+z)+Q_{\Delta t}\). A zero diffusion is exactly \(Q_{\Delta t}=0\); that skip does not license a non-finite carried term when \(2(a\Delta t)\) overflows to \(+\infty\). Treating \(Q_{\Delta t}\) as \(\operatorname{Var}(\eta_{t})\) fails closed. - **Stationary within-subject variance.** Driver et al. (2017, Eq. 4 as \(\Delta t\to\infty\); §4.3 pp. 9–10; p. 16 `asymDIFFUSION`; JSS PDF re-opened 2026-08-19T04:10Z, p. 5): for stable \(a<0\), \(\lim_{\Delta t\to\infty}Q_{\Delta t}=-q/(2a)\). When \(2a\) is finite, form \(q/-(2a)\) so \(q/a\) overflow does not lose a finite Lyapunov solution (`q=MAX`, `a=-0.75` → `MAX/1.5`; CodeRabbit on `75ecdd3`). When \(2a\) overflows, form \((q/a)\times-0.5\). Forming \(2a\) as the only path overflows when \(|a|\) is at the binary64 extreme (`a=-1e308`, `q=1e308` → `0.5`). Forming \(0.5q\) first underflows at the minimum subnormal (`q=from_bits(1)`, `a=-from_bits(1)` → naive `+0`; representable solution `0.5`). Starting from that variance, \(\operatorname{Var}(\eta_{t})\) is invariant across finite event intervals. A zero diffusion is exactly zero. \(a\ge 0\) has no finite stationary variance (Brownian \(a=0\) grows as \(q\Delta t\)). An overflowing Lyapunov solution fails closed. Finite-interval \(Q_{\Delta t}\) is not that limit. This is not ctsem estimation and not a Kalman filter. - **Trait-plus-state variance.** Driver et al. (2017, §4.3, p. 9; JSS PDF re-opened 2026-08-18T21:07Z): a stable trait process has `DRIFT` and `DIFFUSION` fixed to zero. The scalar maps are \(\operatorname{Var}=\mathrm{trait}+\mathrm{state}\) and \(\operatorname{cov}(t,t-1)=\mathrm{trait}+\mathrm{e}^{a\Delta t}p\). The ctsem `TRAITVAR` rewrite that adds the trait to `DIFFUSION` does not license treating trait variance as \(Q_{\Delta t}\). Trait variance is not `asymDIFFUSION`. Evolving the summed variance as if it were all state is not this map. A zero trait is exactly the state. A zero state is exactly the trait. An overflowing sum fails closed. This is not RI-CLPM and not ctsem estimation. -- **Trait-plus-state expected autocorrelation.** Driver et al. (2017, §7.1, pp. 18–19; Eq. 3–4; §4.3; §7.2 `addedTIPREDVAR`; JSS PDF re-opened 2026-09-01T06:30Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104): traits are unit-level unobserved heterogeneity. Omitting `TRAITVAR` mixes between- and within-person information; Figure 4 shows auto-effects reduced after traits. The paper does not print this ratio; it is the covariance ratio of the §4.3 maps. Form `trait + e^{a Δt} p` first, then `trait + p`, then add `addedTIPREDVAR`, then \((\mathrm{trait}+e^{a\Delta t}p+\mathrm{added})/(\mathrm{trait}+p+\mathrm{added})\). Zero trait and zero added recovers \(e^{a\Delta t}\) numerically and remains a distinct named quantity. Zero state with positive trait or extra is exactly 1. Zero total variance fails closed. Growing \(a>0\) is a covariance ratio and is kept. Unstandardised `discreteDRIFT` \(e^{a\Delta t}\) is not this map. Page 16 `discreteDRIFTstd` uses only within-subject `asymDIFFUSION` (footnote 4) and is not this map. `TRAITVAR` is not the standardisation variance. The interval must be event time and strictly positive. This is not RI-CLPM, not a Kalman filter, and not ctsem estimation. - **Observed-indicator variance.** Driver et al. (2017, Eq. 5, p. 5; Table 2, p. 12; JSS PDF re-opened 2026-08-19T04:18Z): \(y_i(t)=\tau_i+\Lambda\eta_i(t)+\varepsilon_i(t)\) with \(\varepsilon\sim N(0,\Theta)\) and \(\tau_i\sim N(\mu_{\tau},\Psi_{\tau})\). Equation 1 (p. 4) is the latent SDE. Table 2 names \(\Theta\) `MANIFESTVAR` and \(\Psi_{\tau}\) `MANIFESTTRAITVAR`; p. 16 restates those names. The scalar map is \(\operatorname{Var}(y)=\lambda^{2}\operatorname{Var}(\eta)+\theta\) when \(\Psi_{\tau}=0\) and \(\lambda^{2}\operatorname{Var}(\eta)+\theta+\psi\) otherwise. Form \((\lambda p)\lambda\) then add \(\theta\), then add \(\psi\). Forming \(\lambda^{2}\) first overflows at \(\lambda=10^{308}\), \(p=10^{-308}\). A zero loading or zero latent variance is exactly \(\theta\) (\(\Psi_{\tau}=0\)) or \(\theta+\psi\). A zero measurement error is exactly \(\lambda^{2}p+\psi\). A zero manifest trait is exactly \(\lambda^{2}p+\theta\). `MANIFESTVAR` is not \(\operatorname{Var}(y)\). `MANIFESTTRAITVAR` is not `MANIFESTVAR`. `TRAITVAR` is latent additional variance and is scaled by \(\lambda^{2}\); `MANIFESTTRAITVAR` is not. \(\operatorname{Var}(\eta)\) is not \(\operatorname{Var}(y)\). An overflowing product or sum fails closed. This is not a Kalman filter and not ctsem estimation. - **Lagged observed-indicator covariance.** Driver et al. (2017, Eq. 5 with Eq. 3–4; JSS PDF re-opened 2026-08-19T04:18Z): independent \(\varepsilon_t\) does not enter \(\operatorname{cov}(y_t,y_{t-1})\). The scalar map is \(\lambda^{2}\operatorname{cov}(\eta_t,\eta_{t-1})+\psi\). Form \((\lambda c)\lambda\) then add \(\psi\). A zero loading or zero latent lagged covariance is exactly \(\psi\). A zero manifest trait is exactly \(\lambda^{2}c\). `MANIFESTVAR` is not lagged observed covariance. Lagged latent covariance is not lagged observed covariance. An overflowing product or sum fails closed. - **Observed-indicator mean.** Driver et al. (2017, Eq. 5, p. 5; Table 2, p. 12; JSS PDF re-opened 2026-08-19T14:08Z): \(y_i(t)=\Gamma+\Lambda\eta_i(t)+\zeta_i(t)\) with \(\zeta\sim N(0,\Theta)\) and \(\Gamma\sim N(\tau,\Psi)\). Table 2 names \(\tau\) `MANIFESTMEANS`, \(\kappa\) `CINT`, and the first-occasion latent mean `T0MEANS`. The scalar map is \(E(y)=\tau+\lambda\mu\). Form \(\lambda\mu\) then add \(\tau\). A zero loading or zero latent mean is exactly \(\tau\). A zero intercept is exactly \(\lambda\mu\). `MANIFESTMEANS` is not \(E(y)\). \(E(\eta)\) is not \(E(y)\). `CINT` is not `MANIFESTMEANS`. `T0MEANS` is not \(E(y)\). An overflowing product or sum fails closed. This is not a Kalman filter and not ctsem estimation. diff --git a/docs/research/trait-state-lagged-association.md b/docs/research/trait-state-lagged-association.md new file mode 100644 index 000000000..70d09c1d1 --- /dev/null +++ b/docs/research/trait-state-lagged-association.md @@ -0,0 +1,49 @@ +# Trait-plus-state lagged association boundary + +## Status + +Research boundary note for the Longitudinal Modeling bounded context. This note does not activate a production scoring or estimator target. + +## Scientific correction + +A lagged covariance divided only by the earlier marginal variance is not, in general, an autocorrelation. For event times `t` and `t + Δ`, a Pearson correlation requires both marginals: + +\[ +\rho_{t,t+\Delta}= +\frac{\operatorname{Cov}(Y_t,Y_{t+\Delta})} +{\sqrt{\operatorname{Var}(Y_t)\operatorname{Var}(Y_{t+\Delta})}}. +\] + +When the process is nonstationary, `Var(Y_t)` and `Var(Y_{t+Δ})` need not be equal. Consequently, a one-sided ratio such as + +\[ +\frac{\text{stable variance}+\text{lagged state covariance}} +{\text{stable variance}+\text{earlier state variance}} +\] + +can exceed one and must not be published as an autocorrelation. Driver, Oud, and Voelkle (2017) provide the continuous-time state-transition and covariance ingredients used by ctsem, but they do not print or validate that one-sided ratio as an autocorrelation. + +The unmerged implementation that introduced a public `expected_autocorrelation` API is therefore retired rather than patched with an arbitrary restriction such as rejecting only positive drift. That restriction would still be insufficient for a nonstationary initial state with negative drift because the later marginal variance remains necessary. + +## TEPP contract + +`longitudinal_core::recover_event_time_lagged_correlation` accepts a lagged covariance, the earlier marginal variance, the later marginal variance, and a strictly positive event-time interval. It performs only the temporal association standardization and does not infer state variance, process noise, or a psychometric response kernel. + +This preserves the DDD ownership boundary: + +- reusable static/generalized-mixed/dependence psychometric kernels remain owned by `ContextualWisdomLab/fast-mlsirm`; +- TEPP owns event-time composition and longitudinal semantics; +- measurement occasion, rater, and method facets are not substitutes for substantive event time; +- callers must assemble occasion-specific marginals from an identified temporal model before asking for a correlation. + +The function fails closed when either marginal is non-positive, the interval is non-positive, inputs are non-finite, or the supplied covariance violates the Cauchy-Schwarz bound. + +## Regression evidence + +The regression suite includes a nonstationary case with earlier variance `1`, later variance `4`, and lagged covariance `1.5`. The retired one-sided ratio would be `1.5`; correct standardization yields `0.75`. The suite also verifies exact `±1` boundaries, rejects incompatible covariance, rejects non-event intervals, and exercises very large representable variances without forming `Var_t * Var_t+Δ` directly. + +This is intentionally narrower than a state estimator. A future production autocorrelation derived from a specific DSEM/continuous-time state model must additionally identify and recover the state transition, process-noise contribution, both marginal variances, uncertainty, and rolling-origin leakage-safe performance before activation. + +## Research basis + +Driver, C. C., Oud, J. H. L., & Voelkle, M. C. (2017). Continuous time structural equation modeling with R package ctsem. *Journal of Statistical Software, 77*(5), 1–35. https://doi.org/10.18637/jss.v077.i05 From c8d9176882188238d676ca6fb099b5af77bca4da Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 18:28:10 +0900 Subject: [PATCH 009/296] test(longitudinal): expose exact covariance-bound rounding --- crates/longitudinal_core/src/association.rs | 62 ++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/crates/longitudinal_core/src/association.rs b/crates/longitudinal_core/src/association.rs index a50bdb99c..9beced564 100644 --- a/crates/longitudinal_core/src/association.rs +++ b/crates/longitudinal_core/src/association.rs @@ -94,6 +94,66 @@ mod tests { ); } + #[test] + fn exact_binary_bound_rejects_one_ulp_excess_for_both_signs() { + let variance = 2.0_f64; + let one_ulp_above = f64::from_bits(variance.to_bits() + 1); + assert_eq!( + recover_event_time_lagged_correlation(one_ulp_above, variance, variance, 1.0), + Err(LongitudinalError::CovarianceBoundViolation) + ); + assert_eq!( + recover_event_time_lagged_correlation(-one_ulp_above, variance, variance, 1.0), + Err(LongitudinalError::CovarianceBoundViolation) + ); + } + + #[test] + fn exact_binary_bound_accepts_extreme_and_subnormal_boundaries() { + assert_eq!( + recover_event_time_lagged_correlation(f64::MAX, f64::MAX, f64::MAX, 1.0), + Ok(1.0) + ); + assert_eq!( + recover_event_time_lagged_correlation(-f64::MAX, f64::MAX, f64::MAX, 1.0), + Ok(-1.0) + ); + + let minimum_subnormal = f64::from_bits(1); + assert_eq!( + recover_event_time_lagged_correlation( + minimum_subnormal, + minimum_subnormal, + minimum_subnormal, + 1.0, + ), + Ok(1.0) + ); + assert_eq!( + recover_event_time_lagged_correlation( + -minimum_subnormal, + minimum_subnormal, + minimum_subnormal, + 1.0, + ), + Ok(-1.0) + ); + } + + #[test] + fn gross_subnormal_bound_violation_is_classified_before_division() { + let minimum_subnormal = f64::from_bits(1); + assert_eq!( + recover_event_time_lagged_correlation( + 1.0, + minimum_subnormal, + minimum_subnormal, + 1.0, + ), + Err(LongitudinalError::CovarianceBoundViolation) + ); + } + #[test] fn exact_boundary_correlations_are_allowed() { assert_eq!( @@ -134,4 +194,4 @@ mod tests { Err(LongitudinalError::NonPositiveEventInterval) ); } -} +} \ No newline at end of file From eef06e7d176be97dff5726c4daf46b49a80cb886 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 18:29:14 +0900 Subject: [PATCH 010/296] fix(longitudinal): validate covariance bound exactly --- crates/longitudinal_core/src/association.rs | 118 ++++++++++++++++++-- 1 file changed, 107 insertions(+), 11 deletions(-) diff --git a/crates/longitudinal_core/src/association.rs b/crates/longitudinal_core/src/association.rs index 9beced564..4c2b4a5a9 100644 --- a/crates/longitudinal_core/src/association.rs +++ b/crates/longitudinal_core/src/association.rs @@ -7,6 +7,75 @@ use crate::LongitudinalError; +/// Decompose a positive finite binary64 value into an exact integer +/// significand and a power-of-two exponent. +fn positive_binary_components(value: f64) -> (u64, i32) { + let bits = value.to_bits(); + let exponent_bits = ((bits >> 52) & 0x7ff) as i32; + let fraction = bits & ((1_u64 << 52) - 1); + if exponent_bits == 0 { + (fraction, -1074) + } else { + ((1_u64 << 52) | fraction, exponent_bits - 1023 - 52) + } +} + +/// Compare two positive `u128 * 2^exponent` values without overflowing the +/// integer significands. +fn scaled_integer_leq( + left_significand: u128, + left_exponent: i32, + right_significand: u128, + right_exponent: i32, +) -> bool { + let left_bits = (u128::BITS - left_significand.leading_zeros()) as i32; + let right_bits = (u128::BITS - right_significand.leading_zeros()) as i32; + let left_order = left_exponent + left_bits; + let right_order = right_exponent + right_bits; + if left_order != right_order { + return left_order < right_order; + } + if left_exponent == right_exponent { + return left_significand <= right_significand; + } + if left_exponent > right_exponent { + let shift = (left_exponent - right_exponent) as u32; + return (left_significand << shift) <= right_significand; + } + let shift = (right_exponent - left_exponent) as u32; + left_significand <= (right_significand << shift) +} + +/// Test the Cauchy–Schwarz covariance bound exactly for the supplied binary64 +/// inputs rather than using a rounded floating-point square-root product. +fn covariance_within_binary_bound( + lagged_covariance: f64, + earlier_total_variance: f64, + later_total_variance: f64, +) -> bool { + let covariance_magnitude = lagged_covariance.abs(); + if covariance_magnitude == 0.0 { + return true; + } + let (covariance_significand, covariance_exponent) = + positive_binary_components(covariance_magnitude); + let (earlier_significand, earlier_exponent) = + positive_binary_components(earlier_total_variance); + let (later_significand, later_exponent) = + positive_binary_components(later_total_variance); + + let covariance_square = + u128::from(covariance_significand) * u128::from(covariance_significand); + let variance_product = + u128::from(earlier_significand) * u128::from(later_significand); + scaled_integer_leq( + covariance_square, + covariance_exponent * 2, + variance_product, + earlier_exponent + later_exponent, + ) +} + /// Recover a Pearson correlation for an event-time lag from its covariance and /// both marginal variances. /// @@ -52,25 +121,35 @@ pub fn recover_event_time_lagged_correlation( if event_interval <= 0.0 { return Err(LongitudinalError::NonPositiveEventInterval); } - - // sqrt(v1) * sqrt(v2) avoids the avoidable overflow of sqrt(v1 * v2). - let denominator = earlier_total_variance.sqrt() * later_total_variance.sqrt(); - if !denominator.is_finite() || denominator <= 0.0 { - return Err(LongitudinalError::InvalidTemporalAssociationInput); + if !covariance_within_binary_bound( + lagged_covariance, + earlier_total_variance, + later_total_variance, + ) { + return Err(LongitudinalError::CovarianceBoundViolation); } - let correlation = lagged_covariance / denominator; + + let earlier_scale = earlier_total_variance.sqrt(); + let later_scale = later_total_variance.sqrt(); + let (first_scale, second_scale) = if earlier_scale >= later_scale { + (earlier_scale, later_scale) + } else { + (later_scale, earlier_scale) + }; + let correlation = (lagged_covariance / first_scale) / second_scale; if !correlation.is_finite() { return Err(LongitudinalError::InvalidTemporalAssociationInput); } - if correlation.abs() > 1.0 { - return Err(LongitudinalError::CovarianceBoundViolation); - } - Ok(correlation) + + // The exact binary-input bound has already been checked above. Clamping + // only absorbs the final square-root/division rounding at a valid ±1 + // boundary; it cannot admit an over-bound covariance. + Ok(correlation.clamp(-1.0, 1.0)) } #[cfg(test)] mod tests { - use super::recover_event_time_lagged_correlation; + use super::{recover_event_time_lagged_correlation, scaled_integer_leq}; use crate::LongitudinalError; #[test] @@ -154,6 +233,23 @@ mod tests { ); } + #[test] + fn zero_covariance_is_valid() { + assert_eq!( + recover_event_time_lagged_correlation(0.0, 1.0, 4.0, 1.0), + Ok(0.0) + ); + } + + #[test] + fn scaled_integer_comparison_covers_alignment_directions() { + assert!(scaled_integer_leq(1, 2, 4, 0)); + assert!(!scaled_integer_leq(5, 0, 1, 2)); + assert!(scaled_integer_leq(3, 0, 6, -1)); + assert!(scaled_integer_leq(6, -1, 3, 0)); + assert!(!scaled_integer_leq(7, -1, 3, 0)); + } + #[test] fn exact_boundary_correlations_are_allowed() { assert_eq!( From 9597893088c6ff1f181ef811a9a4cdb9a159957d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 18:31:13 +0900 Subject: [PATCH 011/296] docs(research): trace lagged correlation claims --- .../trait-state-lagged-association.md | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/docs/research/trait-state-lagged-association.md b/docs/research/trait-state-lagged-association.md index 70d09c1d1..068f89ba8 100644 --- a/docs/research/trait-state-lagged-association.md +++ b/docs/research/trait-state-lagged-association.md @@ -6,7 +6,7 @@ Research boundary note for the Longitudinal Modeling bounded context. This note ## Scientific correction -A lagged covariance divided only by the earlier marginal variance is not, in general, an autocorrelation. For event times `t` and `t + Δ`, a Pearson correlation requires both marginals: +A Pearson correlation standardizes covariance by both marginal standard deviations; this normalization is the defining feature of the coefficient and is not replaceable by a one-sided variance ratio (Pearson, 1895; Rodgers & Nicewander, 1988). For event times `t` and `t + Δ`: \[ \rho_{t,t+\Delta}= @@ -14,20 +14,22 @@ A lagged covariance divided only by the earlier marginal variance is not, in gen {\sqrt{\operatorname{Var}(Y_t)\operatorname{Var}(Y_{t+\Delta})}}. \] -When the process is nonstationary, `Var(Y_t)` and `Var(Y_{t+Δ})` need not be equal. Consequently, a one-sided ratio such as +The Cauchy–Schwarz covariance inequality bounds that standardized coefficient to `[-1, 1]`; TEPP therefore treats a supplied covariance outside the exact binary64 representation of `Cov² <= Var_t Var_{t+Δ}` as incompatible input rather than rounding it into range (Rodgers & Nicewander, 1988). + +Continuous-time state models need not have equal occasion-specific marginal variances when the initial distribution is not stationary or when time-varying inputs alter state uncertainty. Driver, Oud, and Voelkle (2017, §§4.3, 7.1–7.2) explicitly distinguish initial variance, asymptotic diffusion, trait variance, predictor-related variance, and time evolution. Consequently, a one-sided ratio such as \[ \frac{\text{stable variance}+\text{lagged state covariance}} {\text{stable variance}+\text{earlier state variance}} \] -can exceed one and must not be published as an autocorrelation. Driver, Oud, and Voelkle (2017) provide the continuous-time state-transition and covariance ingredients used by ctsem, but they do not print or validate that one-sided ratio as an autocorrelation. +can exceed one and must not be published as an autocorrelation. Driver et al. (2017) provide the continuous-time state-transition and covariance ingredients used by ctsem, but they do not print or validate that one-sided ratio as an autocorrelation. -The unmerged implementation that introduced a public `expected_autocorrelation` API is therefore retired rather than patched with an arbitrary restriction such as rejecting only positive drift. That restriction would still be insufficient for a nonstationary initial state with negative drift because the later marginal variance remains necessary. +The unmerged implementation that introduced a public `expected_autocorrelation` API is therefore retained only as RED/scientific-failure lineage. Rejecting only positive drift would not repair the definition: a nonstationary initial state can still require a distinct later marginal variance even under negative drift (Driver et al., 2017, §§4.3, 7.1). ## TEPP contract -`longitudinal_core::recover_event_time_lagged_correlation` accepts a lagged covariance, the earlier marginal variance, the later marginal variance, and a strictly positive event-time interval. It performs only the temporal association standardization and does not infer state variance, process noise, or a psychometric response kernel. +`longitudinal_core::recover_event_time_lagged_correlation` accepts a lagged covariance, the earlier marginal variance, the later marginal variance, and a strictly positive event-time interval. It performs only temporal association standardization and does not infer state variance, process noise, or a psychometric response kernel. The function computes the correlation after an exact binary64 covariance-bound check, so floating-point rounding in `sqrt(variance)` cannot admit a covariance that is actually one ULP above the represented bound. This preserves the DDD ownership boundary: @@ -36,14 +38,18 @@ This preserves the DDD ownership boundary: - measurement occasion, rater, and method facets are not substitutes for substantive event time; - callers must assemble occasion-specific marginals from an identified temporal model before asking for a correlation. -The function fails closed when either marginal is non-positive, the interval is non-positive, inputs are non-finite, or the supplied covariance violates the Cauchy-Schwarz bound. +The function fails closed when either marginal is non-positive, the interval is non-positive, inputs are non-finite, or the supplied covariance violates the Cauchy–Schwarz covariance bound. The positive event-time interval is a semantic gate: Pearson standardization itself does not estimate or transform time. ## Regression evidence -The regression suite includes a nonstationary case with earlier variance `1`, later variance `4`, and lagged covariance `1.5`. The retired one-sided ratio would be `1.5`; correct standardization yields `0.75`. The suite also verifies exact `±1` boundaries, rejects incompatible covariance, rejects non-event intervals, and exercises very large representable variances without forming `Var_t * Var_t+Δ` directly. +The regression suite includes a nonstationary case with earlier variance `1`, later variance `4`, and lagged covariance `1.5`. The retired one-sided ratio would be `1.5`; correct standardization yields `0.75`. It also verifies exact `±1` boundaries at ordinary, `f64::MAX`, and minimum-subnormal scales; rejects one-ULP over-bound covariance for both signs; classifies gross subnormal violations before division; rejects non-event intervals; and avoids forming `Var_t * Var_{t+Δ}` directly. -This is intentionally narrower than a state estimator. A future production autocorrelation derived from a specific DSEM/continuous-time state model must additionally identify and recover the state transition, process-noise contribution, both marginal variances, uncertainty, and rolling-origin leakage-safe performance before activation. +These are arithmetic and contract regressions, not an estimator-recovery study. A future production autocorrelation derived from a specific DSEM or continuous-time state model must additionally identify and recover the state transition, process-noise contribution, both marginal variances, uncertainty, and leakage-safe rolling-origin performance. Production promotion therefore requires model-specific true-parameter RMSE, bias, interval coverage, irregular-gap behavior, delayed/retrospective-report handling, and temporal-ordering evidence rather than borrowing these standardization tests as scientific-estimator acceptance (Driver et al., 2017). ## Research basis Driver, C. C., Oud, J. H. L., & Voelkle, M. C. (2017). Continuous time structural equation modeling with R package ctsem. *Journal of Statistical Software, 77*(5), 1–35. https://doi.org/10.18637/jss.v077.i05 + +Pearson, K. (1895). Notes on regression and inheritance in the case of two parents. *Proceedings of the Royal Society of London, 58*, 240–242. + +Rodgers, J. L., & Nicewander, W. A. (1988). Thirteen ways to look at the correlation coefficient. *The American Statistician, 42*(1), 59–66. https://doi.org/10.1080/00031305.1988.10475524 From 21e369603f1a520370eab214e6643b28e31088f1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 18:36:43 +0900 Subject: [PATCH 012/296] test(longitudinal): expose unequal-scale correlation underflow --- crates/longitudinal_core/src/association.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/crates/longitudinal_core/src/association.rs b/crates/longitudinal_core/src/association.rs index 4c2b4a5a9..7050a5733 100644 --- a/crates/longitudinal_core/src/association.rs +++ b/crates/longitudinal_core/src/association.rs @@ -154,8 +154,6 @@ mod tests { #[test] fn nonstationary_marginals_do_not_use_the_earlier_variance_twice() { - // The retired one-sided ratio would be 1.5 and therefore impossible as - // an autocorrelation. Supplying the later marginal gives 0.75. let recovered = recover_event_time_lagged_correlation(1.5, 1.0, 4.0, 1.0) .expect("valid nonstationary covariance should standardize"); assert!((recovered - 0.75).abs() < f64::EPSILON * 4.0); @@ -197,7 +195,6 @@ mod tests { recover_event_time_lagged_correlation(-f64::MAX, f64::MAX, f64::MAX, 1.0), Ok(-1.0) ); - let minimum_subnormal = f64::from_bits(1); assert_eq!( recover_event_time_lagged_correlation( @@ -233,6 +230,19 @@ mod tests { ); } + #[test] + fn unequal_scales_do_not_underflow_a_representable_correlation() { + let recovered = recover_event_time_lagged_correlation( + f64::MIN_POSITIVE, + f64::MIN_POSITIVE, + f64::MAX, + 1.0, + ) + .expect("valid unequal-scale covariance should remain representable"); + assert!(recovered > 0.0); + assert!(recovered.is_finite()); + } + #[test] fn zero_covariance_is_valid() { assert_eq!( From f49309a20d4d2ca9377ca20e92ff474891e5aa1a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 18:37:16 +0900 Subject: [PATCH 013/296] fix(longitudinal): preserve unequal-scale correlations --- crates/longitudinal_core/src/association.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/longitudinal_core/src/association.rs b/crates/longitudinal_core/src/association.rs index 7050a5733..82adffc00 100644 --- a/crates/longitudinal_core/src/association.rs +++ b/crates/longitudinal_core/src/association.rs @@ -131,7 +131,11 @@ pub fn recover_event_time_lagged_correlation( let earlier_scale = earlier_total_variance.sqrt(); let later_scale = later_total_variance.sqrt(); - let (first_scale, second_scale) = if earlier_scale >= later_scale { + // Divide by the smaller scale first. The exact covariance-bound gate above + // guarantees the intermediate magnitude cannot exceed the remaining + // marginal scale, while this order avoids underflow when the marginals are + // separated by hundreds of binary exponents. + let (first_scale, second_scale) = if earlier_scale <= later_scale { (earlier_scale, later_scale) } else { (later_scale, earlier_scale) From 8f2b3554e8108ef5c7442231920566be0102fc20 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 19:03:53 +0900 Subject: [PATCH 014/296] chore: stage one-shot longitudinal capability-record repair --- ...r-longitudinal-capability-records-once.yml | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 .github/workflows/repair-longitudinal-capability-records-once.yml diff --git a/.github/workflows/repair-longitudinal-capability-records-once.yml b/.github/workflows/repair-longitudinal-capability-records-once.yml new file mode 100644 index 000000000..1f12ec14f --- /dev/null +++ b/.github/workflows/repair-longitudinal-capability-records-once.yml @@ -0,0 +1,68 @@ +name: One-shot longitudinal capability record repair + +on: + push: + branches: + - agent/psychometric-trait-plus-state-expected-autocorrelation + +permissions: + contents: write + +jobs: + repair: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@631c942040754b6e095e929c1677c07e10ed4f87 + with: + persist-credentials: true + fetch-depth: 0 + - name: Repair canonical capability records + shell: bash + run: | + set -euo pipefail + python3 - <<'PY' + from pathlib import Path + + architecture = Path("ARCHITECTURE.md") + text = architecture.read_text(encoding="utf-8") + old = "| `longitudinal_core` | active-PR: within/between decomposition; refuse between-as-within; component RMSE |" + new = "| `longitudinal_core` | within/between decomposition; refuse between-as-within; component RMSE; event-time lagged-correlation standardization from lagged covariance plus both occasion-specific marginal variances with exact binary64 covariance-bound validation; arithmetic boundary only, not a DSEM/ctsem/state-transition estimator |" + if text.count(old) != 1: + raise SystemExit("expected longitudinal_core capability row exactly once") + architecture.write_text(text.replace(old, new), encoding="utf-8") + + changelog = Path("CHANGELOG.md") + text = changelog.read_text(encoding="utf-8") + marker = "## [Unreleased]\n" + entry = ( + "\n- `longitudinal_core` adds event-time lagged-correlation standardization that requires lagged covariance plus both occasion-specific marginal variances, enforces the exact binary64 covariance bound, and uses bounded division order to avoid representable overflow/underflow. The invalid one-sided covariance/earlier-variance `expected_autocorrelation` API is removed; this arithmetic boundary does not claim a DSEM/ctsem/state-transition estimator.\n" + ) + if text.count(marker) != 1: + raise SystemExit("expected one Unreleased changelog section") + if "event-time lagged-correlation standardization" not in text: + text = text.replace(marker, marker + entry, 1) + changelog.write_text(text, encoding="utf-8") + PY + - name: Verify documentation contracts + shell: bash + run: | + set -euo pipefail + python3 scripts/validate_documentation.py + python3 scripts/check_docstrings.py + grep -F 'event-time lagged-correlation standardization' ARCHITECTURE.md + grep -F 'event-time lagged-correlation standardization' CHANGELOG.md + - name: Commit repair and remove this workflow + shell: bash + run: | + set -euo pipefail + git rm .github/workflows/repair-longitudinal-capability-records-once.yml + git add ARCHITECTURE.md CHANGELOG.md + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + if git diff --cached --quiet; then + echo "No repair delta to commit" >&2 + exit 1 + fi + git commit -m "docs(longitudinal): record lagged-correlation capability" + git push origin HEAD:agent/psychometric-trait-plus-state-expected-autocorrelation From 6f483224b3a03e8237c6f4f098a8b0e85e0a91f5 Mon Sep 17 00:00:00 2001 From: seonghobae Date: Tue, 1 Sep 2026 10:13:29 +0000 Subject: [PATCH 015/296] docs(longitudinal): record lagged-correlation capability Put the ARCHITECTURE.md and CHANGELOG.md records on the reviewed branch and remove the one-shot writer workflow that lacked Workflows permission and mixed repository-write credentials into verification. --- ...r-longitudinal-capability-records-once.yml | 68 ------------------- ARCHITECTURE.md | 2 +- CHANGELOG.md | 2 + 3 files changed, 3 insertions(+), 69 deletions(-) delete mode 100644 .github/workflows/repair-longitudinal-capability-records-once.yml diff --git a/.github/workflows/repair-longitudinal-capability-records-once.yml b/.github/workflows/repair-longitudinal-capability-records-once.yml deleted file mode 100644 index 1f12ec14f..000000000 --- a/.github/workflows/repair-longitudinal-capability-records-once.yml +++ /dev/null @@ -1,68 +0,0 @@ -name: One-shot longitudinal capability record repair - -on: - push: - branches: - - agent/psychometric-trait-plus-state-expected-autocorrelation - -permissions: - contents: write - -jobs: - repair: - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - uses: actions/checkout@631c942040754b6e095e929c1677c07e10ed4f87 - with: - persist-credentials: true - fetch-depth: 0 - - name: Repair canonical capability records - shell: bash - run: | - set -euo pipefail - python3 - <<'PY' - from pathlib import Path - - architecture = Path("ARCHITECTURE.md") - text = architecture.read_text(encoding="utf-8") - old = "| `longitudinal_core` | active-PR: within/between decomposition; refuse between-as-within; component RMSE |" - new = "| `longitudinal_core` | within/between decomposition; refuse between-as-within; component RMSE; event-time lagged-correlation standardization from lagged covariance plus both occasion-specific marginal variances with exact binary64 covariance-bound validation; arithmetic boundary only, not a DSEM/ctsem/state-transition estimator |" - if text.count(old) != 1: - raise SystemExit("expected longitudinal_core capability row exactly once") - architecture.write_text(text.replace(old, new), encoding="utf-8") - - changelog = Path("CHANGELOG.md") - text = changelog.read_text(encoding="utf-8") - marker = "## [Unreleased]\n" - entry = ( - "\n- `longitudinal_core` adds event-time lagged-correlation standardization that requires lagged covariance plus both occasion-specific marginal variances, enforces the exact binary64 covariance bound, and uses bounded division order to avoid representable overflow/underflow. The invalid one-sided covariance/earlier-variance `expected_autocorrelation` API is removed; this arithmetic boundary does not claim a DSEM/ctsem/state-transition estimator.\n" - ) - if text.count(marker) != 1: - raise SystemExit("expected one Unreleased changelog section") - if "event-time lagged-correlation standardization" not in text: - text = text.replace(marker, marker + entry, 1) - changelog.write_text(text, encoding="utf-8") - PY - - name: Verify documentation contracts - shell: bash - run: | - set -euo pipefail - python3 scripts/validate_documentation.py - python3 scripts/check_docstrings.py - grep -F 'event-time lagged-correlation standardization' ARCHITECTURE.md - grep -F 'event-time lagged-correlation standardization' CHANGELOG.md - - name: Commit repair and remove this workflow - shell: bash - run: | - set -euo pipefail - git rm .github/workflows/repair-longitudinal-capability-records-once.yml - git add ARCHITECTURE.md CHANGELOG.md - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - if git diff --cached --quiet; then - echo "No repair delta to commit" >&2 - exit 1 - fi - git commit -m "docs(longitudinal): record lagged-correlation capability" - git push origin HEAD:agent/psychometric-trait-plus-state-expected-autocorrelation diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index e6dafbcc2..e5f1e8cfb 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -102,7 +102,7 @@ boundaries above remain the target modular MSA architecture. | `operational_log` | operational logs; `try_record` is the only recording API; source text and source identity are not loggable; `persistence_postgres` `audit_event` inserts call the same gate | | `service_tls` | production TLS bind gates and rustls server config | | `derived_sensitivity` | derived topic/factor/relation outputs inherit source sensitivity | -| `longitudinal_core` | active-PR: within/between decomposition; refuse between-as-within; component RMSE | +| `longitudinal_core` | within/between decomposition; refuse between-as-within; component RMSE; event-time lagged-correlation standardization from lagged covariance plus both occasion-specific marginal variances with exact binary64 covariance-bound validation; arithmetic boundary only, not a DSEM/ctsem/state-transition estimator | | `topic_lineage` | global topic identity across active/dormant/reactivated states | | `network_analysis` | compositional cluster-pair gates; raw simplex is not Euclidean | | `interpretation_gateway` | evidence-bounded LLM interpretations; not estimators or observed facts | diff --git a/CHANGELOG.md b/CHANGELOG.md index 062a69412..70bf6ea1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,8 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang ## [Unreleased] +- `longitudinal_core` adds event-time lagged-correlation standardization that requires lagged covariance plus both occasion-specific marginal variances, enforces the exact binary64 covariance bound, and uses bounded division order to avoid representable overflow/underflow. The invalid one-sided covariance/earlier-variance `expected_autocorrelation` API is removed; this arithmetic boundary does not claim a DSEM/ctsem/state-transition estimator. + - `event_core` adds bounded Allen interval-consistency classification, atomic path-consistency closure, contradiction/resource refusals, and an explicit dependency-error fallback without claiming unrestricted global satisfiability. - `psychometric_core` recovers the Driver, Oud, and Voelkle (2017, Table 2, p. 12 `MANIFESTTRAITVAR`; §7.1, p. 19; p. 16 `MANIFESTTRAITVARstd`; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-27T14:20Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104) scalar standardised manifest-trait variance on current main after `0ce16e8` dropped the pre-consolidation code while research notes already named the map (register items 83–84). Table 2 names `MANIFESTTRAITVAR` `Ψ_τ` the additional time-invariant variance-covariance on the measurement level and sets it `NULL` when there is no manifest trait. Equation 5 writes `Γ ~ N(τ, Ψ)` and names that covariance the manifest traits. Section 7.1 names manifest traits stable individual differences in indicator levels, distinct from process-level `TRAITVAR` `φ_ξ`. Page 16 prints standardised matrices with the suffix `std` when appropriate. The printed example on p. 16 is `discreteDRIFTstd`, not `MANIFESTTRAITVARstd`. Footnote 4 standardises using only the relevant variance, not the total. The relevant variance for that named indicator-level correlation is `MANIFESTTRAITVAR`, not process-level `TRAITVAR` and not residual `MANIFESTVAR` `θ`. The 2017-era source forms `MANIFESTTRAITVARstd` only when `MANIFESTTRAITVAR != 0`, as `solve(sqrt(diag(MANIFESTTRAITVAR) + ridging)) %&% MANIFESTTRAITVAR` when `verbose = TRUE`. OpenMx `%&%` is `t(A) %*% B %*% A`. Unlike `TRAITVARstd`, that formation adds `diag(c(ridging), n.manifest)`. The default `ridging = FALSE` adds 0, not `0.0001`; that ridge is a numerical hack and is not this exact map. The scalar correlation is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR`. Form strictly positive `ψ` first, then `1 / √ψ`, then `(1 / √ψ) ψ (1 / √ψ)`. Unstandardised `MANIFESTTRAITVAR` is defined for a zero trait; standardised `MANIFESTTRAITVAR` is not. Zero `MANIFESTTRAITVAR` skips forming `MANIFESTTRAITVARstd` in the 2017-era source and fails closed here. Indicator-level trait variance is an event-time structural quantity, so a non-event clock fails closed. `MANIFESTTRAITVAR` does not require stable `a < 0`. Distinct positive `ψ` recover the same 1. `trait / trait = 1` is `TRAITVARstd` and recovers the same number and remains a distinct named quantity. `θ` is `MANIFESTVAR` and is measurement error, not this correlation. Meredith (1993) remains unread (web search 2026-08-27T14:20Z: Springer/Cambridge Core paywalled; Unpaywall historically `is_oa: false`; Springer `content/pdf` is an HTML stub). Mislevy (1991, *Psychometrika, 56*, 177–196) remains unread on the same terms (DOI `10.1007/bf02294457`). Still not a Kalman filter, not a matrix `expm`, not ESEM estimation, not DSEM, and not ctsem estimation. From 23959d1199f84554f4a5090cea2b9e9d70b55dd8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 01:15:56 +0900 Subject: [PATCH 016/296] fix(longitudinal): cover lagged-correlation validation branches --- crates/longitudinal_core/src/association.rs | 45 +++++++++++++++++---- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/crates/longitudinal_core/src/association.rs b/crates/longitudinal_core/src/association.rs index 82adffc00..af0eeda32 100644 --- a/crates/longitudinal_core/src/association.rs +++ b/crates/longitudinal_core/src/association.rs @@ -141,13 +141,11 @@ pub fn recover_event_time_lagged_correlation( (later_scale, earlier_scale) }; let correlation = (lagged_covariance / first_scale) / second_scale; - if !correlation.is_finite() { - return Err(LongitudinalError::InvalidTemporalAssociationInput); - } - // The exact binary-input bound has already been checked above. Clamping - // only absorbs the final square-root/division rounding at a valid ±1 - // boundary; it cannot admit an over-bound covariance. + // Finite positive marginals plus the exact covariance-bound gate guarantee + // that both divisions stay finite. Clamping only absorbs final + // square-root/division rounding at a valid ±1 boundary; it cannot admit an + // over-bound covariance. Ok(correlation.clamp(-1.0, 1.0)) } @@ -247,6 +245,13 @@ mod tests { assert!(recovered.is_finite()); } + #[test] + fn scale_order_is_symmetric_when_the_earlier_marginal_is_larger() { + let recovered = recover_event_time_lagged_correlation(1.5, 4.0, 1.0, 1.0) + .expect("reversing marginal scale order should still standardize"); + assert!((recovered - 0.75).abs() < f64::EPSILON * 4.0); + } + #[test] fn zero_covariance_is_valid() { assert_eq!( @@ -290,18 +295,42 @@ mod tests { } #[test] - fn invalid_inputs_fail_closed() { + fn every_non_finite_input_position_fails_closed() { assert_eq!( recover_event_time_lagged_correlation(f64::NAN, 1.0, 1.0, 1.0), Err(LongitudinalError::InvalidTemporalAssociationInput) ); + assert_eq!( + recover_event_time_lagged_correlation(0.0, f64::INFINITY, 1.0, 1.0), + Err(LongitudinalError::InvalidTemporalAssociationInput) + ); + assert_eq!( + recover_event_time_lagged_correlation(0.0, 1.0, f64::INFINITY, 1.0), + Err(LongitudinalError::InvalidTemporalAssociationInput) + ); + assert_eq!( + recover_event_time_lagged_correlation(0.0, 1.0, 1.0, f64::INFINITY), + Err(LongitudinalError::InvalidTemporalAssociationInput) + ); + } + + #[test] + fn either_non_positive_marginal_fails_closed() { assert_eq!( recover_event_time_lagged_correlation(0.0, 0.0, 1.0, 1.0), Err(LongitudinalError::NonPositiveMarginalVariance) ); + assert_eq!( + recover_event_time_lagged_correlation(0.0, 1.0, 0.0, 1.0), + Err(LongitudinalError::NonPositiveMarginalVariance) + ); + } + + #[test] + fn non_positive_event_interval_fails_closed() { assert_eq!( recover_event_time_lagged_correlation(0.0, 1.0, 1.0, 0.0), Err(LongitudinalError::NonPositiveEventInterval) ); } -} \ No newline at end of file +} From 8fc0b8e7a81cef917eeea13108f9a04576c0c27c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 01:52:44 +0900 Subject: [PATCH 017/296] refactor(longitudinal): move discreteDRIFTstd to owner context --- .../longitudinal_core/src/discrete_drift.rs | 122 ++++++++++++++++++ crates/longitudinal_core/src/error.rs | 56 +++++++- crates/longitudinal_core/src/lib.rs | 14 +- .../standardised_discrete_drift_contract.rs | 54 ++++++-- docs/research/standardised-discrete-drift.md | 37 ++++++ 5 files changed, 269 insertions(+), 14 deletions(-) create mode 100644 crates/longitudinal_core/src/discrete_drift.rs create mode 100644 docs/research/standardised-discrete-drift.md diff --git a/crates/longitudinal_core/src/discrete_drift.rs b/crates/longitudinal_core/src/discrete_drift.rs new file mode 100644 index 000000000..bc96ce202 --- /dev/null +++ b/crates/longitudinal_core/src/discrete_drift.rs @@ -0,0 +1,122 @@ +//! Event-time standardisation for scalar continuous-time drift. + +use crate::LongitudinalError; + +/// Recover the scalar p. 16 `discreteDRIFTstd` on event time. +/// +/// Driver, Oud, and Voelkle (2017) define the discrete-time transition over an +/// interval as `exp(A * delta_t)`. Their standardisation uses the relevant +/// within-person asymptotic variance. In the scalar stationary case the +/// affecting/affected standard-deviation ratio is one, so the standardised +/// auto-effect is numerically `exp(a * delta_t)` after a strictly positive +/// stationary within-person variance has been established. Equal numerical +/// values do not make unstandardised `discreteDRIFT` and `discreteDRIFTstd` +/// the same estimand. +/// +/// This function is temporal composition, not a ctsem/DSEM estimator. It does +/// not estimate `a`, process noise, uncertainty, or a latent state. The caller +/// supplies the continuous diffusion intensity, stable scalar drift, and a +/// strictly positive substantive event-time interval. +/// +/// # Errors +/// +/// Returns [`LongitudinalError::InvalidTemporalTransformInput`] for non-finite +/// diffusion/drift inputs, negative diffusion, non-representable stationary +/// variance, non-representable `a * delta_t`, or an exponential that underflows +/// to zero. Returns [`LongitudinalError::NonPositiveEventInterval`] when the +/// event-time interval is non-finite or not strictly positive. Returns +/// [`LongitudinalError::StationaryVarianceRequiresStableDrift`] unless `a < 0`. +/// Returns [`LongitudinalError::StandardisedDriftRequiresPositiveWithinVariance`] +/// when the stationary within-person variance is zero or underflows to zero. +pub fn recover_event_time_standardised_discrete_drift( + continuous_diffusion: f64, + log_rate: f64, + event_delta: f64, +) -> Result { + if !continuous_diffusion.is_finite() { + return Err(LongitudinalError::InvalidTemporalTransformInput); + } + if continuous_diffusion < 0.0 { + return Err(LongitudinalError::InvalidTemporalTransformInput); + } + if !log_rate.is_finite() { + return Err(LongitudinalError::InvalidTemporalTransformInput); + } + if !event_delta.is_finite() || event_delta <= 0.0 { + return Err(LongitudinalError::NonPositiveEventInterval); + } + if log_rate >= 0.0 { + return Err(LongitudinalError::StationaryVarianceRequiresStableDrift); + } + if continuous_diffusion == 0.0 { + return Err(LongitudinalError::StandardisedDriftRequiresPositiveWithinVariance); + } + + let stationary_denominator = -2.0 * log_rate; + if !stationary_denominator.is_finite() { + return Err(LongitudinalError::InvalidTemporalTransformInput); + } + let within_person_variance = continuous_diffusion / stationary_denominator; + if !within_person_variance.is_finite() { + return Err(LongitudinalError::InvalidTemporalTransformInput); + } + if within_person_variance <= 0.0 { + return Err(LongitudinalError::StandardisedDriftRequiresPositiveWithinVariance); + } + + let exponent = log_rate * event_delta; + if !exponent.is_finite() { + return Err(LongitudinalError::InvalidTemporalTransformInput); + } + let discrete_drift = exponent.exp(); + if discrete_drift == 0.0 { + return Err(LongitudinalError::InvalidTemporalTransformInput); + } + Ok(discrete_drift) +} + +/// Refuse treating unstandardised `discreteDRIFT` as `discreteDRIFTstd`. +/// +/// The scalar stationary values may coincide numerically while the named +/// quantities and their admissibility conditions remain distinct. +/// +/// # Errors +/// +/// Always returns [`LongitudinalError::UnstandardisedDriftIsNotStandardisedDrift`]. +pub fn refuse_unstandardised_discrete_drift_as_standardised_discrete_drift( + unstandardised_discrete_drift: f64, + standardised_discrete_drift: f64, +) -> Result { + let _ = (unstandardised_discrete_drift, standardised_discrete_drift); + Err(LongitudinalError::UnstandardisedDriftIsNotStandardisedDrift) +} + +/// Refuse treating a trait-plus-state lagged association as `discreteDRIFTstd`. +/// +/// A trait-plus-state association mixes stable between-unit variance with +/// within-person dynamics. Driver et al.'s drift standardisation uses the +/// relevant within-person variance instead. +/// +/// # Errors +/// +/// Always returns [`LongitudinalError::TraitStateAssociationIsNotStandardisedDrift`]. +pub fn refuse_trait_plus_state_association_as_standardised_discrete_drift( + trait_plus_state_association: f64, + standardised_discrete_drift: f64, +) -> Result { + let _ = (trait_plus_state_association, standardised_discrete_drift); + Err(LongitudinalError::TraitStateAssociationIsNotStandardisedDrift) +} + +/// Refuse using between-unit trait variance as the drift standardisation variance. +/// +/// # Errors +/// +/// Always returns [`LongitudinalError::TraitVarianceIsNotDriftStandardisationVariance`]. +pub fn refuse_trait_variance_as_standardisation_variance( + trait_variance: f64, + within_person_variance: f64, +) -> Result { + let _ = (trait_variance, within_person_variance); + Err(LongitudinalError::TraitVarianceIsNotDriftStandardisationVariance) +} diff --git a/crates/longitudinal_core/src/error.rs b/crates/longitudinal_core/src/error.rs index cea0aa33b..ce7549ca1 100644 --- a/crates/longitudinal_core/src/error.rs +++ b/crates/longitudinal_core/src/error.rs @@ -16,12 +16,24 @@ pub enum LongitudinalError { InvalidObservationPayload, /// A lagged covariance, marginal variance, or event interval was non-finite. InvalidTemporalAssociationInput, + /// A temporal transform input or intermediate value was not representable. + InvalidTemporalTransformInput, /// At least one marginal variance was not strictly positive. NonPositiveMarginalVariance, /// The event-time interval was not strictly positive. NonPositiveEventInterval, /// The covariance violates the Cauchy-Schwarz bound implied by the two marginals. CovarianceBoundViolation, + /// A stationary within-person variance was requested without stable negative drift. + StationaryVarianceRequiresStableDrift, + /// `discreteDRIFTstd` was requested without positive stationary within-person variance. + StandardisedDriftRequiresPositiveWithinVariance, + /// Unstandardised `discreteDRIFT` was substituted for `discreteDRIFTstd`. + UnstandardisedDriftIsNotStandardisedDrift, + /// A trait-plus-state association was substituted for `discreteDRIFTstd`. + TraitStateAssociationIsNotStandardisedDrift, + /// Between-unit trait variance was used as the drift standardisation variance. + TraitVarianceIsNotDriftStandardisationVariance, } impl fmt::Display for LongitudinalError { @@ -32,15 +44,31 @@ impl fmt::Display for LongitudinalError { Self::InvalidComponentPayload => "invalid longitudinal component payload", Self::InvalidObservationPayload => "invalid longitudinal observation payload", Self::InvalidTemporalAssociationInput => "invalid temporal association input", + Self::InvalidTemporalTransformInput => "invalid temporal transform input", Self::NonPositiveMarginalVariance => { "temporal correlation requires strictly positive marginal variances" } Self::NonPositiveEventInterval => { - "temporal correlation requires a strictly positive event-time interval" + "temporal composition requires a strictly positive event-time interval" } Self::CovarianceBoundViolation => { "lagged covariance is incompatible with the supplied marginal variances" } + Self::StationaryVarianceRequiresStableDrift => { + "stationary within-person variance requires strictly negative drift" + } + Self::StandardisedDriftRequiresPositiveWithinVariance => { + "standardised discrete drift requires positive stationary within-person variance" + } + Self::UnstandardisedDriftIsNotStandardisedDrift => { + "unstandardised discrete drift is not standardised discrete drift" + } + Self::TraitStateAssociationIsNotStandardisedDrift => { + "trait-plus-state association is not standardised discrete drift" + } + Self::TraitVarianceIsNotDriftStandardisationVariance => { + "trait variance is not the drift standardisation variance" + } }; formatter.write_str(message) } @@ -75,18 +103,42 @@ mod tests { LongitudinalError::InvalidTemporalAssociationInput, "invalid temporal association input", ), + ( + LongitudinalError::InvalidTemporalTransformInput, + "invalid temporal transform input", + ), ( LongitudinalError::NonPositiveMarginalVariance, "temporal correlation requires strictly positive marginal variances", ), ( LongitudinalError::NonPositiveEventInterval, - "temporal correlation requires a strictly positive event-time interval", + "temporal composition requires a strictly positive event-time interval", ), ( LongitudinalError::CovarianceBoundViolation, "lagged covariance is incompatible with the supplied marginal variances", ), + ( + LongitudinalError::StationaryVarianceRequiresStableDrift, + "stationary within-person variance requires strictly negative drift", + ), + ( + LongitudinalError::StandardisedDriftRequiresPositiveWithinVariance, + "standardised discrete drift requires positive stationary within-person variance", + ), + ( + LongitudinalError::UnstandardisedDriftIsNotStandardisedDrift, + "unstandardised discrete drift is not standardised discrete drift", + ), + ( + LongitudinalError::TraitStateAssociationIsNotStandardisedDrift, + "trait-plus-state association is not standardised discrete drift", + ), + ( + LongitudinalError::TraitVarianceIsNotDriftStandardisationVariance, + "trait variance is not the drift standardisation variance", + ), ] { assert_eq!(error.to_string(), message); } diff --git a/crates/longitudinal_core/src/lib.rs b/crates/longitudinal_core/src/lib.rs index a6d748046..713e5b99b 100644 --- a/crates/longitudinal_core/src/lib.rs +++ b/crates/longitudinal_core/src/lib.rs @@ -5,12 +5,14 @@ //! //! Stable between-unit components cannot be scored as within-unit change. //! Event-time lagged associations require both marginal variances before a -//! covariance can be standardized as a correlation. Recovery reports computed -//! component RMSE against known truth (ADR 0005). +//! covariance can be standardized as a correlation. Event-interval response +//! transforms live here rather than in a generic psychometric kernel. Recovery +//! reports computed component RMSE against known truth (ADR 0005). mod association; mod component; mod decompose; +mod discrete_drift; mod error; mod level; @@ -25,6 +27,14 @@ pub use component::component_root_mean_square_error; pub use decompose::OccasionObservation; /// Decompose occasion scores into unit means and within residuals. pub use decompose::decompose_within_between; +/// Recover scalar Driver p.16 `discreteDRIFTstd` on substantive event time. +pub use discrete_drift::recover_event_time_standardised_discrete_drift; +/// Refuse trait-plus-state association as `discreteDRIFTstd`. +pub use discrete_drift::refuse_trait_plus_state_association_as_standardised_discrete_drift; +/// Refuse trait variance as the drift standardisation variance. +pub use discrete_drift::refuse_trait_variance_as_standardisation_variance; +/// Refuse unstandardised `discreteDRIFT` as `discreteDRIFTstd`. +pub use discrete_drift::refuse_unstandardised_discrete_drift_as_standardised_discrete_drift; /// Fail-closed longitudinal-modeling errors. pub use error::LongitudinalError; /// Established longitudinal component level. diff --git a/crates/longitudinal_core/tests/standardised_discrete_drift_contract.rs b/crates/longitudinal_core/tests/standardised_discrete_drift_contract.rs index 724992ff2..9e70eed0e 100644 --- a/crates/longitudinal_core/tests/standardised_discrete_drift_contract.rs +++ b/crates/longitudinal_core/tests/standardised_discrete_drift_contract.rs @@ -17,12 +17,35 @@ fn driver_page_sixteen_scalar_standardised_drift_recovers_on_event_time() { assert!((longer - recovered).abs() > 1e-9); } +#[test] +fn known_truth_grid_has_machine_precision_rmse() { + let cases = [ + (0.1_f64, -0.15_f64, 0.25_f64), + (0.4, -0.5, 1.0), + (1.2, -1.25, 0.8), + (3.0, -0.05, 7.0), + ]; + let mut squared_error_sum = 0.0; + for (diffusion, rate, delta) in cases { + let recovered = recover_event_time_standardised_discrete_drift(diffusion, rate, delta) + .expect("known-truth case"); + let truth = (rate * delta).exp(); + squared_error_sum += (recovered - truth).powi(2); + } + let rmse = (squared_error_sum / cases.len() as f64).sqrt(); + assert!(rmse <= f64::EPSILON); +} + #[test] fn standardised_drift_fails_closed_without_positive_stationary_within_variance() { assert_eq!( recover_event_time_standardised_discrete_drift(0.0, -0.5, 1.0), Err(LongitudinalError::StandardisedDriftRequiresPositiveWithinVariance) ); + assert_eq!( + recover_event_time_standardised_discrete_drift(f64::from_bits(1), -1.0e307, 1.0e-307), + Err(LongitudinalError::StandardisedDriftRequiresPositiveWithinVariance) + ); assert_eq!( recover_event_time_standardised_discrete_drift(0.4, 0.0, 1.0), Err(LongitudinalError::StationaryVarianceRequiresStableDrift) @@ -42,20 +65,31 @@ fn standardised_drift_fails_closed_without_positive_stationary_within_variance() } #[test] -fn standardised_drift_rejects_nonfinite_or_unrepresentable_inputs() { - for (diffusion, rate, delta) in [ - (f64::NAN, -0.5, 1.0), - (f64::INFINITY, -0.5, 1.0), - (-0.1, -0.5, 1.0), - (0.4, f64::NAN, 1.0), - (0.4, f64::NEG_INFINITY, 1.0), - ] { +fn standardised_drift_rejects_nonfinite_negative_or_unrepresentable_inputs() { + for diffusion in [f64::NAN, f64::INFINITY, -0.1] { assert_eq!( - recover_event_time_standardised_discrete_drift(diffusion, rate, delta), + recover_event_time_standardised_discrete_drift(diffusion, -0.5, 1.0), Err(LongitudinalError::InvalidTemporalTransformInput) ); } - + for rate in [f64::NAN, f64::NEG_INFINITY] { + assert_eq!( + recover_event_time_standardised_discrete_drift(0.4, rate, 1.0), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); + } + assert_eq!( + recover_event_time_standardised_discrete_drift(0.4, -f64::MAX, 1.0), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); + assert_eq!( + recover_event_time_standardised_discrete_drift(f64::MAX, -f64::MIN_POSITIVE, 1.0), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); + assert_eq!( + recover_event_time_standardised_discrete_drift(0.4, -2.0, f64::MAX), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); assert_eq!( recover_event_time_standardised_discrete_drift(0.4, -800.0, 1.0), Err(LongitudinalError::InvalidTemporalTransformInput) diff --git a/docs/research/standardised-discrete-drift.md b/docs/research/standardised-discrete-drift.md new file mode 100644 index 000000000..d4f7af00d --- /dev/null +++ b/docs/research/standardised-discrete-drift.md @@ -0,0 +1,37 @@ +# Scalar event-time `discreteDRIFTstd` recovery + +## Domain owner + +This contract belongs to TEPP's **Longitudinal Modeling** bounded context because it composes a continuous-time drift over a substantive event-time interval. It is not a reusable static psychometric response/dependence kernel and does not establish `psychometric_core` as an owner. Reusable generalized-mixed, LSIRM/MLSIRM/DLSJM numerical kernels remain fast-mlsirm-owned. + +## Estimand + +For scalar continuous-time drift `a` and positive event interval `Δt`, Driver, Oud, and Voelkle's discrete-time transition is + +`φ(Δt) = exp(a Δt)`. + +The 2017 ctsem standardisation uses the relevant within-person asymptotic variance. For a scalar stationary process with continuous diffusion intensity `q > 0` and stable `a < 0`, + +`p = -q / (2a) > 0`. + +The affecting/affected standard-deviation ratio is therefore `sqrt(p) / sqrt(p) = 1`, so the scalar p. 16 `discreteDRIFTstd` is numerically `exp(a Δt)`. Numerical equality does not collapse the named estimands: unstandardised `discreteDRIFT`, `discreteDRIFTstd`, trait-plus-state lagged association, trait variance, process noise and a fitted ctsem/DSEM model remain different contracts. + +`recover_event_time_standardised_discrete_drift` accepts only an event interval; it does not accept a generic clock enum. This makes the event-time ownership structural at the API boundary rather than relying on a caller-supplied clock label. + +## Identification and admissibility + +This deterministic transform requires finite `q`, finite `a`, finite positive `Δt`, `q > 0`, stable `a < 0`, and a representable positive stationary within-person variance. If `exp(a Δt)` underflows to zero, the binary64 result is refused because zero cannot be inverted through the corresponding real log-rate map. The function does not estimate `a`, `q`, latent states, measurement parameters, or uncertainty. + +No claim is made for matrix standardisation, time-varying drift, nonstationary state variance, ESEM, DSEM, or ctsem estimation. Those require their own equations, identification, estimators and recovery evidence. + +## Recovery evidence + +The integration contract uses deterministic known truth over irregular positive intervals and multiple stable drift/diffusion settings. It computes `truth = exp(a Δt)` independently and requires machine-precision RMSE. Additional cases cover zero/underflowed within-person variance, unstable drift, non-positive/non-finite intervals, negative/non-finite diffusion, non-finite/overflowed intermediates, exponential underflow, and named-estimand refusal. + +This is exact arithmetic recovery rather than a fitted stochastic estimator, so Monte Carlo interval coverage is not manufactured for this function. Monte Carlo RMSE/bias/coverage remains mandatory for estimators that infer drift, diffusion, latent states or uncertainty from sampled data. + +## Traceability + +Driver, C. C., Oud, J. H. L., & Voelkle, M. C. (2017). Continuous time structural equation modeling with R package ctsem. *Journal of Statistical Software, 77*(5), 1–35. https://doi.org/10.18637/jss.v077.i05 + +Relevant evidence: Eq. 3 discrete transition; Table 2 continuous/discrete parameter names; p. 16 standardised output; footnote 4 standardisation rule; §7.1 separation of stable trait and within-person dynamics. The historical #310 branch also records inspection of the 2017-era `summary.ctsemFit.R`; that implementation lineage is preserved in Git history rather than keeping the temporal transform in the wrong bounded context. From c52c436c6b075e3982c8195b7862ea07063930b2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 02:06:35 +0900 Subject: [PATCH 018/296] test(longitudinal): require typed event-time drift intervals --- .../standardised_discrete_drift_contract.rs | 74 ++++++++++++++----- 1 file changed, 54 insertions(+), 20 deletions(-) diff --git a/crates/longitudinal_core/tests/standardised_discrete_drift_contract.rs b/crates/longitudinal_core/tests/standardised_discrete_drift_contract.rs index 9e70eed0e..e3d5392ce 100644 --- a/crates/longitudinal_core/tests/standardised_discrete_drift_contract.rs +++ b/crates/longitudinal_core/tests/standardised_discrete_drift_contract.rs @@ -1,17 +1,21 @@ use longitudinal_core::{ - LongitudinalError, recover_event_time_standardised_discrete_drift, + EventTimeInterval, LongitudinalError, recover_event_time_standardised_discrete_drift, refuse_trait_plus_state_association_as_standardised_discrete_drift, refuse_trait_variance_as_standardisation_variance, refuse_unstandardised_discrete_drift_as_standardised_discrete_drift, }; +fn event_time(value: f64) -> EventTimeInterval { + EventTimeInterval::new(value).expect("test interval must be valid event time") +} + #[test] fn driver_page_sixteen_scalar_standardised_drift_recovers_on_event_time() { - let recovered = recover_event_time_standardised_discrete_drift(0.4, -0.5, 1.0) + let recovered = recover_event_time_standardised_discrete_drift(0.4, -0.5, event_time(1.0)) .expect("positive stationary within-person variance"); assert!((recovered - (-0.5_f64).exp()).abs() < 1e-15); - let longer = recover_event_time_standardised_discrete_drift(0.4, -0.5, 2.5) + let longer = recover_event_time_standardised_discrete_drift(0.4, -0.5, event_time(2.5)) .expect("irregular positive event interval"); assert!((longer - (-1.25_f64).exp()).abs() < 1e-15); assert!((longer - recovered).abs() > 1e-9); @@ -27,8 +31,9 @@ fn known_truth_grid_has_machine_precision_rmse() { ]; let mut squared_error_sum = 0.0; for (diffusion, rate, delta) in cases { - let recovered = recover_event_time_standardised_discrete_drift(diffusion, rate, delta) - .expect("known-truth case"); + let recovered = + recover_event_time_standardised_discrete_drift(diffusion, rate, event_time(delta)) + .expect("known-truth case"); let truth = (rate * delta).exp(); squared_error_sum += (recovered - truth).powi(2); } @@ -36,30 +41,59 @@ fn known_truth_grid_has_machine_precision_rmse() { assert!(rmse <= f64::EPSILON); } +#[test] +fn extreme_stable_rate_preserves_representable_stationary_result() { + let delta = 1.0 / f64::MAX; + let recovered = recover_event_time_standardised_discrete_drift( + f64::MAX, + -f64::MAX, + event_time(delta), + ) + .expect("doubling the drift must not create an avoidable overflow"); + let truth = (-f64::MAX * delta).exp(); + assert!((recovered - truth).abs() <= f64::EPSILON); +} + #[test] fn standardised_drift_fails_closed_without_positive_stationary_within_variance() { assert_eq!( - recover_event_time_standardised_discrete_drift(0.0, -0.5, 1.0), + recover_event_time_standardised_discrete_drift(0.0, -0.5, event_time(1.0)), Err(LongitudinalError::StandardisedDriftRequiresPositiveWithinVariance) ); assert_eq!( - recover_event_time_standardised_discrete_drift(f64::from_bits(1), -1.0e307, 1.0e-307), + recover_event_time_standardised_discrete_drift( + f64::from_bits(1), + -1.0e307, + event_time(1.0e-307), + ), Err(LongitudinalError::StandardisedDriftRequiresPositiveWithinVariance) ); assert_eq!( - recover_event_time_standardised_discrete_drift(0.4, 0.0, 1.0), + recover_event_time_standardised_discrete_drift(0.4, 0.0, event_time(1.0)), Err(LongitudinalError::StationaryVarianceRequiresStableDrift) ); assert_eq!( - recover_event_time_standardised_discrete_drift(0.4, 0.5, 1.0), + recover_event_time_standardised_discrete_drift(0.4, 0.5, event_time(1.0)), Err(LongitudinalError::StationaryVarianceRequiresStableDrift) ); +} + +#[test] +fn event_time_value_object_rejects_wrong_clock_payload_shape() { assert_eq!( - recover_event_time_standardised_discrete_drift(0.4, -0.5, 0.0), + EventTimeInterval::new(0.0), Err(LongitudinalError::NonPositiveEventInterval) ); assert_eq!( - recover_event_time_standardised_discrete_drift(0.4, -0.5, f64::NAN), + EventTimeInterval::new(-1.0), + Err(LongitudinalError::NonPositiveEventInterval) + ); + assert_eq!( + EventTimeInterval::new(f64::NAN), + Err(LongitudinalError::NonPositiveEventInterval) + ); + assert_eq!( + EventTimeInterval::new(f64::INFINITY), Err(LongitudinalError::NonPositiveEventInterval) ); } @@ -68,30 +102,30 @@ fn standardised_drift_fails_closed_without_positive_stationary_within_variance() fn standardised_drift_rejects_nonfinite_negative_or_unrepresentable_inputs() { for diffusion in [f64::NAN, f64::INFINITY, -0.1] { assert_eq!( - recover_event_time_standardised_discrete_drift(diffusion, -0.5, 1.0), + recover_event_time_standardised_discrete_drift(diffusion, -0.5, event_time(1.0)), Err(LongitudinalError::InvalidTemporalTransformInput) ); } for rate in [f64::NAN, f64::NEG_INFINITY] { assert_eq!( - recover_event_time_standardised_discrete_drift(0.4, rate, 1.0), + recover_event_time_standardised_discrete_drift(0.4, rate, event_time(1.0)), Err(LongitudinalError::InvalidTemporalTransformInput) ); } assert_eq!( - recover_event_time_standardised_discrete_drift(0.4, -f64::MAX, 1.0), - Err(LongitudinalError::InvalidTemporalTransformInput) - ); - assert_eq!( - recover_event_time_standardised_discrete_drift(f64::MAX, -f64::MIN_POSITIVE, 1.0), + recover_event_time_standardised_discrete_drift( + f64::MAX, + -f64::MIN_POSITIVE, + event_time(1.0), + ), Err(LongitudinalError::InvalidTemporalTransformInput) ); assert_eq!( - recover_event_time_standardised_discrete_drift(0.4, -2.0, f64::MAX), + recover_event_time_standardised_discrete_drift(0.4, -2.0, event_time(f64::MAX)), Err(LongitudinalError::InvalidTemporalTransformInput) ); assert_eq!( - recover_event_time_standardised_discrete_drift(0.4, -800.0, 1.0), + recover_event_time_standardised_discrete_drift(0.4, -800.0, event_time(1.0)), Err(LongitudinalError::InvalidTemporalTransformInput) ); } From 0a2fd4f5d0d57500428594b6cc782d6dcef80446 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 02:07:06 +0900 Subject: [PATCH 019/296] feat(longitudinal): type event-time intervals --- crates/longitudinal_core/src/event_time.rs | 64 ++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 crates/longitudinal_core/src/event_time.rs diff --git a/crates/longitudinal_core/src/event_time.rs b/crates/longitudinal_core/src/event_time.rs new file mode 100644 index 000000000..a4813fd4d --- /dev/null +++ b/crates/longitudinal_core/src/event_time.rs @@ -0,0 +1,64 @@ +//! Event-time value objects for longitudinal composition. + +use crate::LongitudinalError; + +/// A finite, strictly positive interval on TEPP's substantive event-time clock. +/// +/// Constructing this value is the admission boundary between generic numeric +/// durations and Longitudinal Modeling operations that are explicitly defined +/// on event time. Measurement-occasion, assertion, document, system, and +/// availability-clock durations must not be re-labelled as this type without +/// an owning-context conversion that proves the semantic mapping. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct EventTimeInterval(f64); + +impl EventTimeInterval { + /// Admit a finite, strictly positive interval as substantive event time. + /// + /// # Errors + /// + /// Returns [`LongitudinalError::NonPositiveEventInterval`] when `value` is + /// non-finite, zero, or negative. + pub fn new(value: f64) -> Result { + if !value.is_finite() || value <= 0.0 { + return Err(LongitudinalError::NonPositiveEventInterval); + } + Ok(Self(value)) + } + + /// Return the admitted interval in the model's declared event-time unit. + #[must_use] + pub const fn as_f64(self) -> f64 { + self.0 + } +} + +#[cfg(test)] +mod tests { + use super::EventTimeInterval; + use crate::LongitudinalError; + + #[test] + fn admits_only_finite_positive_event_intervals() { + assert_eq!( + EventTimeInterval::new(0.0), + Err(LongitudinalError::NonPositiveEventInterval) + ); + assert_eq!( + EventTimeInterval::new(-1.0), + Err(LongitudinalError::NonPositiveEventInterval) + ); + assert_eq!( + EventTimeInterval::new(f64::NAN), + Err(LongitudinalError::NonPositiveEventInterval) + ); + assert_eq!( + EventTimeInterval::new(f64::INFINITY), + Err(LongitudinalError::NonPositiveEventInterval) + ); + assert_eq!( + EventTimeInterval::new(0.25).map(EventTimeInterval::as_f64), + Ok(0.25) + ); + } +} From 59983e586fb325c491bc42dacb9add796024f390 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 02:07:26 +0900 Subject: [PATCH 020/296] feat(longitudinal): export event-time interval value object --- crates/longitudinal_core/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/longitudinal_core/src/lib.rs b/crates/longitudinal_core/src/lib.rs index 713e5b99b..f1792ce20 100644 --- a/crates/longitudinal_core/src/lib.rs +++ b/crates/longitudinal_core/src/lib.rs @@ -14,6 +14,7 @@ mod component; mod decompose; mod discrete_drift; mod error; +mod event_time; mod level; /// Recover a valid event-time lagged correlation from covariance and both @@ -37,6 +38,8 @@ pub use discrete_drift::refuse_trait_variance_as_standardisation_variance; pub use discrete_drift::refuse_unstandardised_discrete_drift_as_standardised_discrete_drift; /// Fail-closed longitudinal-modeling errors. pub use error::LongitudinalError; +/// A finite, strictly positive interval admitted on substantive event time. +pub use event_time::EventTimeInterval; /// Established longitudinal component level. pub use level::ComponentLevel; /// Refuse to treat a between-unit component as within-unit change. From 8114af136f31de4d3992029cb0893f4c67883666 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 02:08:00 +0900 Subject: [PATCH 021/296] fix(longitudinal): preserve extreme stable drift and event-time typing --- .../longitudinal_core/src/discrete_drift.rs | 43 +++++++++---------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/crates/longitudinal_core/src/discrete_drift.rs b/crates/longitudinal_core/src/discrete_drift.rs index bc96ce202..7d91ae734 100644 --- a/crates/longitudinal_core/src/discrete_drift.rs +++ b/crates/longitudinal_core/src/discrete_drift.rs @@ -1,6 +1,6 @@ //! Event-time standardisation for scalar continuous-time drift. -use crate::LongitudinalError; +use crate::{EventTimeInterval, LongitudinalError}; /// Recover the scalar p. 16 `discreteDRIFTstd` on event time. /// @@ -15,36 +15,29 @@ use crate::LongitudinalError; /// /// This function is temporal composition, not a ctsem/DSEM estimator. It does /// not estimate `a`, process noise, uncertainty, or a latent state. The caller -/// supplies the continuous diffusion intensity, stable scalar drift, and a -/// strictly positive substantive event-time interval. +/// supplies the continuous diffusion intensity, stable scalar drift, and an +/// [`EventTimeInterval`] admitted by the Longitudinal Modeling bounded context. /// /// # Errors /// /// Returns [`LongitudinalError::InvalidTemporalTransformInput`] for non-finite /// diffusion/drift inputs, negative diffusion, non-representable stationary /// variance, non-representable `a * delta_t`, or an exponential that underflows -/// to zero. Returns [`LongitudinalError::NonPositiveEventInterval`] when the -/// event-time interval is non-finite or not strictly positive. Returns -/// [`LongitudinalError::StationaryVarianceRequiresStableDrift`] unless `a < 0`. -/// Returns [`LongitudinalError::StandardisedDriftRequiresPositiveWithinVariance`] -/// when the stationary within-person variance is zero or underflows to zero. +/// to zero. Returns [`LongitudinalError::StationaryVarianceRequiresStableDrift`] +/// unless `a < 0`. Returns +/// [`LongitudinalError::StandardisedDriftRequiresPositiveWithinVariance`] when +/// the stationary within-person variance is zero or underflows to zero. pub fn recover_event_time_standardised_discrete_drift( continuous_diffusion: f64, log_rate: f64, - event_delta: f64, + event_interval: EventTimeInterval, ) -> Result { - if !continuous_diffusion.is_finite() { - return Err(LongitudinalError::InvalidTemporalTransformInput); - } - if continuous_diffusion < 0.0 { + if !continuous_diffusion.is_finite() || continuous_diffusion < 0.0 { return Err(LongitudinalError::InvalidTemporalTransformInput); } if !log_rate.is_finite() { return Err(LongitudinalError::InvalidTemporalTransformInput); } - if !event_delta.is_finite() || event_delta <= 0.0 { - return Err(LongitudinalError::NonPositiveEventInterval); - } if log_rate >= 0.0 { return Err(LongitudinalError::StationaryVarianceRequiresStableDrift); } @@ -52,11 +45,17 @@ pub fn recover_event_time_standardised_discrete_drift( return Err(LongitudinalError::StandardisedDriftRequiresPositiveWithinVariance); } - let stationary_denominator = -2.0 * log_rate; - if !stationary_denominator.is_finite() { - return Err(LongitudinalError::InvalidTemporalTransformInput); - } - let within_person_variance = continuous_diffusion / stationary_denominator; + // Prefer the direct scalar stationary-variance expression when doubling the + // stable rate remains representable. If only that intermediate overflows, + // divide by the finite original rate first and apply the factor 1/2 after + // the division. This preserves valid extreme-rate cases such as + // q = f64::MAX, a = -f64::MAX without changing the mathematical estimand. + let twice_rate = log_rate * 2.0; + let within_person_variance = if twice_rate.is_finite() { + continuous_diffusion / -twice_rate + } else { + (continuous_diffusion / -log_rate) * 0.5 + }; if !within_person_variance.is_finite() { return Err(LongitudinalError::InvalidTemporalTransformInput); } @@ -64,7 +63,7 @@ pub fn recover_event_time_standardised_discrete_drift( return Err(LongitudinalError::StandardisedDriftRequiresPositiveWithinVariance); } - let exponent = log_rate * event_delta; + let exponent = log_rate * event_interval.as_f64(); if !exponent.is_finite() { return Err(LongitudinalError::InvalidTemporalTransformInput); } From d229a969bfb60a1f065100192b4faf30aaa8529e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 02:09:00 +0900 Subject: [PATCH 022/296] feat(longitudinal): type lagged-correlation event-time boundary --- .../src/temporal_association.rs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 crates/longitudinal_core/src/temporal_association.rs diff --git a/crates/longitudinal_core/src/temporal_association.rs b/crates/longitudinal_core/src/temporal_association.rs new file mode 100644 index 000000000..0a824671d --- /dev/null +++ b/crates/longitudinal_core/src/temporal_association.rs @@ -0,0 +1,46 @@ +//! Typed public boundary for event-time lagged association. + +use crate::{EventTimeInterval, LongitudinalError, association}; + +/// Recover a Pearson correlation for a substantive event-time lag. +/// +/// The caller supplies the lagged covariance and both occasion-specific +/// marginal variances. [`EventTimeInterval`] makes the public clock ownership +/// explicit; assertion-, document-, system-, availability-, or method-occasion +/// intervals cannot enter this API as bare numeric durations. +/// +/// # Errors +/// +/// Returns [`LongitudinalError::InvalidTemporalAssociationInput`] for invalid +/// covariance or marginal inputs, [`LongitudinalError::NonPositiveMarginalVariance`] +/// when either marginal variance is non-positive, and +/// [`LongitudinalError::CovarianceBoundViolation`] when the covariance exceeds +/// the exact binary64 Cauchy–Schwarz bound. +pub fn recover_event_time_lagged_correlation( + lagged_covariance: f64, + earlier_total_variance: f64, + later_total_variance: f64, + event_interval: EventTimeInterval, +) -> Result { + association::recover_event_time_lagged_correlation( + lagged_covariance, + earlier_total_variance, + later_total_variance, + event_interval.as_f64(), + ) +} + +#[cfg(test)] +mod tests { + use super::recover_event_time_lagged_correlation; + use crate::EventTimeInterval; + + #[test] + fn public_boundary_requires_admitted_event_time() { + let interval = EventTimeInterval::new(1.0).expect("valid event time"); + assert_eq!( + recover_event_time_lagged_correlation(2.0, 1.0, 4.0, interval), + Ok(1.0) + ); + } +} From a90a9a089aeff8675f32fb3bbc94f883efe38cb7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 02:09:30 +0900 Subject: [PATCH 023/296] refactor(longitudinal): expose typed temporal association boundary --- crates/longitudinal_core/src/lib.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/longitudinal_core/src/lib.rs b/crates/longitudinal_core/src/lib.rs index f1792ce20..fd6bbcdee 100644 --- a/crates/longitudinal_core/src/lib.rs +++ b/crates/longitudinal_core/src/lib.rs @@ -16,10 +16,8 @@ mod discrete_drift; mod error; mod event_time; mod level; +mod temporal_association; -/// Recover a valid event-time lagged correlation from covariance and both -/// marginal variances. -pub use association::recover_event_time_lagged_correlation; /// One unit-specific within or between component. pub use component::ComponentValue; /// RMSE of recovered components against known truth. @@ -44,3 +42,6 @@ pub use event_time::EventTimeInterval; pub use level::ComponentLevel; /// Refuse to treat a between-unit component as within-unit change. pub use level::refuse_between_as_within_change; +/// Recover a valid event-time lagged correlation from covariance and both +/// marginal variances through the typed event-time boundary. +pub use temporal_association::recover_event_time_lagged_correlation; From 1be84a37f4e46102d596aa505eb93eb0d2116732 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 02:10:15 +0900 Subject: [PATCH 024/296] docs(adr): record longitudinal modeling ownership migration --- ...ongitudinal-modeling-ownership-addendum.md | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 docs/adr/longitudinal-modeling-ownership-addendum.md diff --git a/docs/adr/longitudinal-modeling-ownership-addendum.md b/docs/adr/longitudinal-modeling-ownership-addendum.md new file mode 100644 index 000000000..43f394473 --- /dev/null +++ b/docs/adr/longitudinal-modeling-ownership-addendum.md @@ -0,0 +1,29 @@ +# ADR 0005 ownership addendum — Longitudinal Modeling + +**Parent decision:** ADR 0005 — Posterior-aware ESEM/DSEM and structural interpretation +**Decision identity:** ADR 0005; this addendum does not mint a new ADR number +**Status:** Accepted clarification +**Recorded:** 2026-09-01 + +## Decision clarification + +Temporal/event composition of longitudinal psychometric quantities belongs to the TEPP Longitudinal Modeling bounded context. Public operations whose meaning depends on substantive event time must therefore expose an event-time domain type at the context boundary rather than accepting an unqualified numeric duration. + +`longitudinal_core` owns this TEPP temporal composition. `fast-mlsirm` remains the canonical owner of reusable static/generalized-mixed/dependence-aware psychometric kernels. A numerically reusable primitive must migrate through the fast-mlsirm Published Language/ACL boundary rather than turning `longitudinal_core` into a second static psychometric kernel. + +For the scalar Driver, Oud, and Voelkle (2017) p. 16 `discreteDRIFTstd` special case, TEPP owns only the event-time composition and admissibility policy. The function requires stable negative drift, positive stationary within-person variance, and an admitted `EventTimeInterval`. It does not estimate ctsem/DSEM parameters, state trajectories, process noise, or uncertainty. + +The lagged-correlation boundary similarly requires both occasion-specific marginal variances and an `EventTimeInterval`; a covariance divided only by the earlier variance is not exposed as an autocorrelation. + +## DDD consequences + +- `psychometric_core` is not the authority for temporal transforms merely because an earlier branch placed them there. +- `EventTimeInterval` is a value object of Longitudinal Modeling. Assertion-, document-, system-, availability-, and method-occasion intervals require explicit owning-context conversion before they can be admitted as substantive event time. +- One transform, route, clock, or refusal does not create a bounded context or a new ADR identity. +- Compatibility adapters may preserve public callers during a landed migration, but domain ownership and dependency direction must remain explicit. + +## Verification + +PR #310 is the current landing vehicle for this clarification. Its RED lineage includes an extreme stable-drift case that failed because `-2a` overflowed despite a representable stationary variance, and a typed event-time contract that could not compile before the value object existed. The repaired source avoids the unnecessary doubling overflow and moves public event-time admission behind `EventTimeInterval`. + +Protected-main maturity is not claimed until the exact landing head passes the live ruleset and is merged. From 2d44b89ed4cd63181cdc738ff6222ede002a4f3f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 02:10:34 +0900 Subject: [PATCH 025/296] docs(prd): version longitudinal event-time ownership requirement --- ....4-amendment-longitudinal-time-ownership.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 docs/product/prd-v0.4-amendment-longitudinal-time-ownership.md diff --git a/docs/product/prd-v0.4-amendment-longitudinal-time-ownership.md b/docs/product/prd-v0.4-amendment-longitudinal-time-ownership.md new file mode 100644 index 000000000..328a781f3 --- /dev/null +++ b/docs/product/prd-v0.4-amendment-longitudinal-time-ownership.md @@ -0,0 +1,18 @@ +# TEPP PRD v0.4 amendment — Longitudinal event-time ownership + +**Parent product baseline:** `docs/product/prd-v0.4-approved.md` +**Amendment:** v0.4-LONGITUDINAL-TIME-1 +**Status:** Approved clarification; protected-main implementation remains pending +**Recorded:** 2026-09-01 + +This amendment narrows implementation authority without changing TEPP's approved product thesis or scientific claims. + +TEPP Longitudinal Modeling owns temporal/event composition for longitudinal psychometrics. Product APIs that interpret a duration as substantive event time must accept an event-time domain value rather than a bare numeric duration. This preserves the PRD's distinction among event/valid, assertion, document, system, availability time, and knowledge cutoff and prevents measurement occasion or another clock from being silently substituted for substantive event time. + +For event-time lagged association, the product contract requires lagged covariance, both occasion-specific marginal variances, and an admitted positive finite event-time interval. Nonstationary correlation must not replace the later marginal variance with the earlier one. + +For the scalar stationary Driver, Oud, and Voelkle (2017) p. 16 `discreteDRIFTstd` mapping, TEPP may expose a deterministic event-time transform only when stable drift and positive stationary within-person variance are established. Numerical equality with `exp(a Δt)` in that scalar stationary special case does not make unstandardised `discreteDRIFT` and `discreteDRIFTstd` the same estimand. + +Reusable static/generalized-mixed/dependence-aware psychometric arithmetic remains owned by `ContextualWisdomLab/fast-mlsirm`; TEPP consumes versioned contracts through an anti-corruption layer and does not retain duplicate production kernels. LLM execution remains `contextual-orchestrator`-owned and cannot substitute for numerical estimation or scientific acceptance. + +Acceptance for this amendment requires typed event-time admission in the public longitudinal boundary, regression coverage for wrong-clock-shaped numeric input and extreme finite binary64 cases, current-head Rust/documentation/security evidence, and normal protected-main integration. Branch-local implementation does not constitute released product capability. From b6d7594208f1c469382e7d540a5507280de6a196 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 03:01:49 +0900 Subject: [PATCH 026/296] test(longitudinal): expose event-time exponent underflow --- .../tests/standardised_discrete_drift_contract.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/crates/longitudinal_core/tests/standardised_discrete_drift_contract.rs b/crates/longitudinal_core/tests/standardised_discrete_drift_contract.rs index e3d5392ce..835b63e9b 100644 --- a/crates/longitudinal_core/tests/standardised_discrete_drift_contract.rs +++ b/crates/longitudinal_core/tests/standardised_discrete_drift_contract.rs @@ -18,7 +18,7 @@ fn driver_page_sixteen_scalar_standardised_drift_recovers_on_event_time() { let longer = recover_event_time_standardised_discrete_drift(0.4, -0.5, event_time(2.5)) .expect("irregular positive event interval"); assert!((longer - (-1.25_f64).exp()).abs() < 1e-15); - assert!((longer - recovered).abs() > 1e-9); + assert!(longer < recovered); } #[test] @@ -54,6 +54,18 @@ fn extreme_stable_rate_preserves_representable_stationary_result() { assert!((recovered - truth).abs() <= f64::EPSILON); } +#[test] +fn event_time_exponent_underflow_fails_closed_instead_of_becoming_one() { + assert_eq!( + recover_event_time_standardised_discrete_drift( + f64::MIN_POSITIVE, + -f64::MIN_POSITIVE, + event_time(f64::MIN_POSITIVE), + ), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); +} + #[test] fn standardised_drift_fails_closed_without_positive_stationary_within_variance() { assert_eq!( From 1347bfb7726fd1cb3196f6cad306aa00fe41d112 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 03:02:44 +0900 Subject: [PATCH 027/296] fix(longitudinal): fail closed on exponent underflow --- crates/longitudinal_core/src/discrete_drift.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/crates/longitudinal_core/src/discrete_drift.rs b/crates/longitudinal_core/src/discrete_drift.rs index 7d91ae734..927f4efb3 100644 --- a/crates/longitudinal_core/src/discrete_drift.rs +++ b/crates/longitudinal_core/src/discrete_drift.rs @@ -22,11 +22,11 @@ use crate::{EventTimeInterval, LongitudinalError}; /// /// Returns [`LongitudinalError::InvalidTemporalTransformInput`] for non-finite /// diffusion/drift inputs, negative diffusion, non-representable stationary -/// variance, non-representable `a * delta_t`, or an exponential that underflows -/// to zero. Returns [`LongitudinalError::StationaryVarianceRequiresStableDrift`] -/// unless `a < 0`. Returns -/// [`LongitudinalError::StandardisedDriftRequiresPositiveWithinVariance`] when -/// the stationary within-person variance is zero or underflows to zero. +/// variance, an `a * delta_t` product that overflows or underflows to signed +/// zero, or an exponential that underflows to zero. Returns +/// [`LongitudinalError::StationaryVarianceRequiresStableDrift`] unless `a < 0`. +/// Returns [`LongitudinalError::StandardisedDriftRequiresPositiveWithinVariance`] +/// when the stationary within-person variance is zero or underflows to zero. pub fn recover_event_time_standardised_discrete_drift( continuous_diffusion: f64, log_rate: f64, @@ -64,7 +64,11 @@ pub fn recover_event_time_standardised_discrete_drift( } let exponent = log_rate * event_interval.as_f64(); - if !exponent.is_finite() { + // `log_rate` is strictly negative and EventTimeInterval is strictly + // positive, so an exact product cannot be zero. A signed zero therefore + // proves binary64 multiplication underflow and must fail closed instead of + // silently becoming exp(-0.0) == 1.0. + if !exponent.is_finite() || exponent == 0.0 { return Err(LongitudinalError::InvalidTemporalTransformInput); } let discrete_drift = exponent.exp(); From d4a7be2ebae757401aede7799c1bd3ba049bb0eb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 03:04:25 +0900 Subject: [PATCH 028/296] refactor(longitudinal): type event time through association core --- crates/longitudinal_core/src/association.rs | 107 +++++++++++--------- 1 file changed, 57 insertions(+), 50 deletions(-) diff --git a/crates/longitudinal_core/src/association.rs b/crates/longitudinal_core/src/association.rs index af0eeda32..8e288a7f3 100644 --- a/crates/longitudinal_core/src/association.rs +++ b/crates/longitudinal_core/src/association.rs @@ -5,7 +5,7 @@ //! available. A one-sided covariance/initial-variance ratio is deliberately //! not exposed as an autocorrelation. -use crate::LongitudinalError; +use crate::{EventTimeInterval, LongitudinalError}; /// Decompose a positive finite binary64 value into an exact integer /// significand and a power-of-two exponent. @@ -89,38 +89,33 @@ fn covariance_within_binary_bound( /// they do not justify replacing the second marginal variance with the first /// when the process is nonstationary. /// -/// `event_interval` is semantically required to be strictly positive so a -/// measurement-occasion or method facet cannot be passed as an untyped lag. -/// This function does not infer either marginal variance and does not estimate -/// a state process. +/// [`EventTimeInterval`] makes substantive event-time ownership explicit. This +/// function does not infer either marginal variance and does not estimate a +/// state process. /// /// # Errors /// /// Returns [`LongitudinalError::InvalidTemporalAssociationInput`] for -/// non-finite inputs, [`LongitudinalError::NonPositiveMarginalVariance`] when -/// either marginal variance is not strictly positive, -/// [`LongitudinalError::NonPositiveEventInterval`] for a non-positive event -/// interval, and [`LongitudinalError::CovarianceBoundViolation`] when the -/// supplied covariance is incompatible with the two marginal variances. -pub fn recover_event_time_lagged_correlation( +/// non-finite covariance or marginal inputs, +/// [`LongitudinalError::NonPositiveMarginalVariance`] when either marginal +/// variance is not strictly positive, and +/// [`LongitudinalError::CovarianceBoundViolation`] when the supplied covariance +/// is incompatible with the two marginal variances. +pub(crate) fn recover_event_time_lagged_correlation( lagged_covariance: f64, earlier_total_variance: f64, later_total_variance: f64, - event_interval: f64, + _event_interval: EventTimeInterval, ) -> Result { if !lagged_covariance.is_finite() || !earlier_total_variance.is_finite() || !later_total_variance.is_finite() - || !event_interval.is_finite() { return Err(LongitudinalError::InvalidTemporalAssociationInput); } if earlier_total_variance <= 0.0 || later_total_variance <= 0.0 { return Err(LongitudinalError::NonPositiveMarginalVariance); } - if event_interval <= 0.0 { - return Err(LongitudinalError::NonPositiveEventInterval); - } if !covariance_within_binary_bound( lagged_covariance, earlier_total_variance, @@ -152,11 +147,15 @@ pub fn recover_event_time_lagged_correlation( #[cfg(test)] mod tests { use super::{recover_event_time_lagged_correlation, scaled_integer_leq}; - use crate::LongitudinalError; + use crate::{EventTimeInterval, LongitudinalError}; + + fn event_time(value: f64) -> EventTimeInterval { + EventTimeInterval::new(value).expect("test interval must be valid event time") + } #[test] fn nonstationary_marginals_do_not_use_the_earlier_variance_twice() { - let recovered = recover_event_time_lagged_correlation(1.5, 1.0, 4.0, 1.0) + let recovered = recover_event_time_lagged_correlation(1.5, 1.0, 4.0, event_time(1.0)) .expect("valid nonstationary covariance should standardize"); assert!((recovered - 0.75).abs() < f64::EPSILON * 4.0); } @@ -164,11 +163,11 @@ mod tests { #[test] fn covariance_bound_is_fail_closed() { assert_eq!( - recover_event_time_lagged_correlation(2.000_000_000_1, 1.0, 4.0, 1.0), + recover_event_time_lagged_correlation(2.000_000_000_1, 1.0, 4.0, event_time(1.0)), Err(LongitudinalError::CovarianceBoundViolation) ); assert_eq!( - recover_event_time_lagged_correlation(-2.000_000_000_1, 1.0, 4.0, 1.0), + recover_event_time_lagged_correlation(-2.000_000_000_1, 1.0, 4.0, event_time(1.0)), Err(LongitudinalError::CovarianceBoundViolation) ); } @@ -178,11 +177,21 @@ mod tests { let variance = 2.0_f64; let one_ulp_above = f64::from_bits(variance.to_bits() + 1); assert_eq!( - recover_event_time_lagged_correlation(one_ulp_above, variance, variance, 1.0), + recover_event_time_lagged_correlation( + one_ulp_above, + variance, + variance, + event_time(1.0), + ), Err(LongitudinalError::CovarianceBoundViolation) ); assert_eq!( - recover_event_time_lagged_correlation(-one_ulp_above, variance, variance, 1.0), + recover_event_time_lagged_correlation( + -one_ulp_above, + variance, + variance, + event_time(1.0), + ), Err(LongitudinalError::CovarianceBoundViolation) ); } @@ -190,11 +199,21 @@ mod tests { #[test] fn exact_binary_bound_accepts_extreme_and_subnormal_boundaries() { assert_eq!( - recover_event_time_lagged_correlation(f64::MAX, f64::MAX, f64::MAX, 1.0), + recover_event_time_lagged_correlation( + f64::MAX, + f64::MAX, + f64::MAX, + event_time(1.0), + ), Ok(1.0) ); assert_eq!( - recover_event_time_lagged_correlation(-f64::MAX, f64::MAX, f64::MAX, 1.0), + recover_event_time_lagged_correlation( + -f64::MAX, + f64::MAX, + f64::MAX, + event_time(1.0), + ), Ok(-1.0) ); let minimum_subnormal = f64::from_bits(1); @@ -203,7 +222,7 @@ mod tests { minimum_subnormal, minimum_subnormal, minimum_subnormal, - 1.0, + event_time(1.0), ), Ok(1.0) ); @@ -212,7 +231,7 @@ mod tests { -minimum_subnormal, minimum_subnormal, minimum_subnormal, - 1.0, + event_time(1.0), ), Ok(-1.0) ); @@ -226,7 +245,7 @@ mod tests { 1.0, minimum_subnormal, minimum_subnormal, - 1.0, + event_time(1.0), ), Err(LongitudinalError::CovarianceBoundViolation) ); @@ -238,7 +257,7 @@ mod tests { f64::MIN_POSITIVE, f64::MIN_POSITIVE, f64::MAX, - 1.0, + event_time(1.0), ) .expect("valid unequal-scale covariance should remain representable"); assert!(recovered > 0.0); @@ -247,7 +266,7 @@ mod tests { #[test] fn scale_order_is_symmetric_when_the_earlier_marginal_is_larger() { - let recovered = recover_event_time_lagged_correlation(1.5, 4.0, 1.0, 1.0) + let recovered = recover_event_time_lagged_correlation(1.5, 4.0, 1.0, event_time(1.0)) .expect("reversing marginal scale order should still standardize"); assert!((recovered - 0.75).abs() < f64::EPSILON * 4.0); } @@ -255,7 +274,7 @@ mod tests { #[test] fn zero_covariance_is_valid() { assert_eq!( - recover_event_time_lagged_correlation(0.0, 1.0, 4.0, 1.0), + recover_event_time_lagged_correlation(0.0, 1.0, 4.0, event_time(1.0)), Ok(0.0) ); } @@ -272,11 +291,11 @@ mod tests { #[test] fn exact_boundary_correlations_are_allowed() { assert_eq!( - recover_event_time_lagged_correlation(2.0, 1.0, 4.0, 1.0), + recover_event_time_lagged_correlation(2.0, 1.0, 4.0, event_time(1.0)), Ok(1.0) ); assert_eq!( - recover_event_time_lagged_correlation(-2.0, 1.0, 4.0, 1.0), + recover_event_time_lagged_correlation(-2.0, 1.0, 4.0, event_time(1.0)), Ok(-1.0) ); } @@ -288,7 +307,7 @@ mod tests { variance / 2.0, variance, variance, - 1.0, + event_time(1.0), ) .expect("representable standardized covariance should remain representable"); assert!((recovered - 0.5).abs() < 1.0e-15); @@ -297,19 +316,15 @@ mod tests { #[test] fn every_non_finite_input_position_fails_closed() { assert_eq!( - recover_event_time_lagged_correlation(f64::NAN, 1.0, 1.0, 1.0), - Err(LongitudinalError::InvalidTemporalAssociationInput) - ); - assert_eq!( - recover_event_time_lagged_correlation(0.0, f64::INFINITY, 1.0, 1.0), + recover_event_time_lagged_correlation(f64::NAN, 1.0, 1.0, event_time(1.0)), Err(LongitudinalError::InvalidTemporalAssociationInput) ); assert_eq!( - recover_event_time_lagged_correlation(0.0, 1.0, f64::INFINITY, 1.0), + recover_event_time_lagged_correlation(0.0, f64::INFINITY, 1.0, event_time(1.0)), Err(LongitudinalError::InvalidTemporalAssociationInput) ); assert_eq!( - recover_event_time_lagged_correlation(0.0, 1.0, 1.0, f64::INFINITY), + recover_event_time_lagged_correlation(0.0, 1.0, f64::INFINITY, event_time(1.0)), Err(LongitudinalError::InvalidTemporalAssociationInput) ); } @@ -317,20 +332,12 @@ mod tests { #[test] fn either_non_positive_marginal_fails_closed() { assert_eq!( - recover_event_time_lagged_correlation(0.0, 0.0, 1.0, 1.0), + recover_event_time_lagged_correlation(0.0, 0.0, 1.0, event_time(1.0)), Err(LongitudinalError::NonPositiveMarginalVariance) ); assert_eq!( - recover_event_time_lagged_correlation(0.0, 1.0, 0.0, 1.0), + recover_event_time_lagged_correlation(0.0, 1.0, 0.0, event_time(1.0)), Err(LongitudinalError::NonPositiveMarginalVariance) ); } - - #[test] - fn non_positive_event_interval_fails_closed() { - assert_eq!( - recover_event_time_lagged_correlation(0.0, 1.0, 1.0, 0.0), - Err(LongitudinalError::NonPositiveEventInterval) - ); - } } From 76269fdaf908f2e3b392e4ce9118e411580a8fe8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 03:05:00 +0900 Subject: [PATCH 029/296] fix(longitudinal): preserve typed lag boundary end to end --- .../src/temporal_association.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/crates/longitudinal_core/src/temporal_association.rs b/crates/longitudinal_core/src/temporal_association.rs index 0a824671d..c5ebdb6c7 100644 --- a/crates/longitudinal_core/src/temporal_association.rs +++ b/crates/longitudinal_core/src/temporal_association.rs @@ -26,14 +26,14 @@ pub fn recover_event_time_lagged_correlation( lagged_covariance, earlier_total_variance, later_total_variance, - event_interval.as_f64(), + event_interval, ) } #[cfg(test)] mod tests { use super::recover_event_time_lagged_correlation; - use crate::EventTimeInterval; + use crate::{EventTimeInterval, LongitudinalError}; #[test] fn public_boundary_requires_admitted_event_time() { @@ -43,4 +43,16 @@ mod tests { Ok(1.0) ); } + + #[test] + fn wrong_clock_shaped_numeric_values_fail_at_value_object_admission() { + assert_eq!( + EventTimeInterval::new(0.0), + Err(LongitudinalError::NonPositiveEventInterval) + ); + assert_eq!( + EventTimeInterval::new(f64::NAN), + Err(LongitudinalError::NonPositiveEventInterval) + ); + } } From 58c1ba7f085260ee8efa90e2089828cc469581ba Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 03:06:15 +0900 Subject: [PATCH 030/296] docs(research): ground covariance bound in primary source --- docs/research/trait-state-lagged-association.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/research/trait-state-lagged-association.md b/docs/research/trait-state-lagged-association.md index 068f89ba8..ae86eebee 100644 --- a/docs/research/trait-state-lagged-association.md +++ b/docs/research/trait-state-lagged-association.md @@ -14,7 +14,7 @@ A Pearson correlation standardizes covariance by both marginal standard deviatio {\sqrt{\operatorname{Var}(Y_t)\operatorname{Var}(Y_{t+\Delta})}}. \] -The Cauchy–Schwarz covariance inequality bounds that standardized coefficient to `[-1, 1]`; TEPP therefore treats a supplied covariance outside the exact binary64 representation of `Cov² <= Var_t Var_{t+Δ}` as incompatible input rather than rounding it into range (Rodgers & Nicewander, 1988). +The Cauchy–Bunyakovsky–Schwarz inequality gives the corresponding covariance bound. Bouniakowsky (1859, pp. 3–4) states the integral form that bounds a squared cross-product integral by the product of the two squared-function integrals; applying that inner-product inequality to centered random variables yields `Cov² <= Var_t Var_{t+Δ}`. TEPP therefore treats a supplied covariance outside the exact binary64 representation of that bound as incompatible input rather than rounding it into range. Rodgers and Nicewander (1988) remain supplementary statistical context, not the primary source for the inequality. Continuous-time state models need not have equal occasion-specific marginal variances when the initial distribution is not stationary or when time-varying inputs alter state uncertainty. Driver, Oud, and Voelkle (2017, §§4.3, 7.1–7.2) explicitly distinguish initial variance, asymptotic diffusion, trait variance, predictor-related variance, and time evolution. Consequently, a one-sided ratio such as @@ -29,7 +29,7 @@ The unmerged implementation that introduced a public `expected_autocorrelation` ## TEPP contract -`longitudinal_core::recover_event_time_lagged_correlation` accepts a lagged covariance, the earlier marginal variance, the later marginal variance, and a strictly positive event-time interval. It performs only temporal association standardization and does not infer state variance, process noise, or a psychometric response kernel. The function computes the correlation after an exact binary64 covariance-bound check, so floating-point rounding in `sqrt(variance)` cannot admit a covariance that is actually one ULP above the represented bound. +`longitudinal_core::recover_event_time_lagged_correlation` accepts a lagged covariance, the earlier marginal variance, the later marginal variance, and an admitted `EventTimeInterval`. The typed value object is carried through the public boundary into the internal association implementation rather than being erased back to a bare duration. The operation performs only temporal association standardization and does not infer state variance, process noise, or a psychometric response kernel. The function computes the correlation after an exact binary64 covariance-bound check, so floating-point rounding in `sqrt(variance)` cannot admit a covariance that is actually one ULP above the represented bound. This preserves the DDD ownership boundary: @@ -38,16 +38,20 @@ This preserves the DDD ownership boundary: - measurement occasion, rater, and method facets are not substitutes for substantive event time; - callers must assemble occasion-specific marginals from an identified temporal model before asking for a correlation. -The function fails closed when either marginal is non-positive, the interval is non-positive, inputs are non-finite, or the supplied covariance violates the Cauchy–Schwarz covariance bound. The positive event-time interval is a semantic gate: Pearson standardization itself does not estimate or transform time. +The function fails closed when either marginal is non-positive, covariance or marginal inputs are non-finite, or the supplied covariance violates the Cauchy–Bunyakovsky–Schwarz covariance bound. `EventTimeInterval` itself fails admission for non-finite or non-positive durations. Pearson standardization does not estimate or transform time; the event-time value object exists to preserve clock ownership through the API. ## Regression evidence -The regression suite includes a nonstationary case with earlier variance `1`, later variance `4`, and lagged covariance `1.5`. The retired one-sided ratio would be `1.5`; correct standardization yields `0.75`. It also verifies exact `±1` boundaries at ordinary, `f64::MAX`, and minimum-subnormal scales; rejects one-ULP over-bound covariance for both signs; classifies gross subnormal violations before division; rejects non-event intervals; and avoids forming `Var_t * Var_{t+Δ}` directly. +The regression suite includes a nonstationary case with earlier variance `1`, later variance `4`, and lagged covariance `1.5`. The retired one-sided ratio would be `1.5`; correct standardization yields `0.75`. It also verifies exact `±1` boundaries at ordinary, `f64::MAX`, and minimum-subnormal scales; rejects one-ULP over-bound covariance for both signs; classifies gross subnormal violations before division; rejects invalid event-time value construction; and avoids forming `Var_t * Var_{t+Δ}` directly. + +The scalar `discreteDRIFTstd` regressions separately require monotone temporal ordering for stable negative drift and fail closed when a finite negative drift multiplied by a positive admitted event interval underflows to signed zero. That case must not silently become `exp(-0.0) == 1.0`. These are arithmetic and contract regressions, not an estimator-recovery study. A future production autocorrelation derived from a specific DSEM or continuous-time state model must additionally identify and recover the state transition, process-noise contribution, both marginal variances, uncertainty, and leakage-safe rolling-origin performance. Production promotion therefore requires model-specific true-parameter RMSE, bias, interval coverage, irregular-gap behavior, delayed/retrospective-report handling, and temporal-ordering evidence rather than borrowing these standardization tests as scientific-estimator acceptance (Driver et al., 2017). ## Research basis +Bouniakowsky, V. (1859). Sur quelques inégalités concernant les intégrales ordinaires et les intégrales aux différences finies. *Mémoires de l’Académie Impériale des Sciences de Saint-Pétersbourg, VIIe Série, 1*(9), 1–18. + Driver, C. C., Oud, J. H. L., & Voelkle, M. C. (2017). Continuous time structural equation modeling with R package ctsem. *Journal of Statistical Software, 77*(5), 1–35. https://doi.org/10.18637/jss.v077.i05 Pearson, K. (1895). Notes on regression and inheritance in the case of two parents. *Proceedings of the Royal Society of London, 58*, 240–242. From 0a919245596ef28b4969ea317673e3c41a1457cc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 05:05:44 +0900 Subject: [PATCH 031/296] test(longitudinal): RED standardised diffusion owner contract --- .../tests/standardised_diffusion_contract.rs | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 crates/longitudinal_core/tests/standardised_diffusion_contract.rs diff --git a/crates/longitudinal_core/tests/standardised_diffusion_contract.rs b/crates/longitudinal_core/tests/standardised_diffusion_contract.rs new file mode 100644 index 000000000..a5c19f6f3 --- /dev/null +++ b/crates/longitudinal_core/tests/standardised_diffusion_contract.rs @@ -0,0 +1,101 @@ +//! RED contract for scalar standardised diffusion maps in Longitudinal Modeling. +//! +//! Driver, Oud, and Voelkle (2017) print the underlying continuous/discrete +//! diffusion transformations and describe relevant-variance standardisation, +//! but the 2017 ctsem summary source does not emit named `DIFFUSIONstd` or +//! `discreteDIFFUSIONstd` matrices. These scalar maps therefore remain explicit +//! research-candidate extensions rather than canonical ctsem output. + +use longitudinal_core::{ + EventTimeInterval, LongitudinalError, + recover_event_time_standardised_continuous_diffusion, + recover_event_time_standardised_discrete_diffusion, + refuse_standardised_continuous_diffusion_as_standardised_discrete_diffusion, + refuse_total_variance_scaled_diffusion_as_standardised_diffusion, + refuse_unstandardised_diffusion_as_standardised_diffusion, +}; + +#[test] +fn continuous_diffusion_candidate_recovers_relevant_variance_ratio() { + let recovered = recover_event_time_standardised_continuous_diffusion(0.4, -0.25) + .expect("positive stationary within variance"); + assert!((recovered - 0.5).abs() < 1e-15); + + let scale_invariant = recover_event_time_standardised_continuous_diffusion(1.6, -0.25) + .expect("same scalar standardisation at a different q scale"); + assert!((scale_invariant - recovered).abs() < 1e-15); + + let max_path = recover_event_time_standardised_continuous_diffusion(f64::MAX, -0.75) + .expect("representable q/p must not fail on an avoidable intermediate overflow"); + assert!((max_path - 1.5).abs() < 1e-15); +} + +#[test] +fn discrete_diffusion_candidate_preserves_event_interval_semantics() { + let one = EventTimeInterval::new(1.0).expect("positive event interval"); + let two = EventTimeInterval::new(2.0).expect("positive event interval"); + + let recovered = recover_event_time_standardised_discrete_diffusion(0.4, -0.25, one) + .expect("positive stationary within variance"); + let expected = 1.0 - (-0.5_f64).exp(); + assert!((recovered - expected).abs() < 1e-15); + + let later = recover_event_time_standardised_discrete_diffusion(0.4, -0.25, two) + .expect("later event interval"); + assert!(later > recovered, "stable-process noise fraction must accumulate with event time"); + assert!(later < 1.0); + + let rescaled = recover_event_time_standardised_discrete_diffusion(1.6, -0.25, one) + .expect("same scalar standardisation at a different q scale"); + assert!((rescaled - recovered).abs() < 1e-15); +} + +#[test] +fn standardised_diffusion_candidates_fail_closed_without_positive_stationarity() { + let one = EventTimeInterval::new(1.0).expect("positive event interval"); + + assert_eq!( + recover_event_time_standardised_continuous_diffusion(0.0, -0.25), + Err(LongitudinalError::StandardisedDiffusionRequiresPositiveWithinVariance) + ); + assert_eq!( + recover_event_time_standardised_discrete_diffusion(0.0, -0.25, one), + Err(LongitudinalError::StandardisedDiffusionRequiresPositiveWithinVariance) + ); + assert_eq!( + recover_event_time_standardised_continuous_diffusion(0.4, 0.0), + Err(LongitudinalError::StationaryVarianceRequiresStableDrift) + ); + assert_eq!( + recover_event_time_standardised_discrete_diffusion(0.4, 0.25, one), + Err(LongitudinalError::StationaryVarianceRequiresStableDrift) + ); + assert_eq!( + recover_event_time_standardised_continuous_diffusion(f64::NAN, -0.25), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); +} + +#[test] +fn equal_numbers_do_not_collapse_named_diffusion_estimands() { + let one = EventTimeInterval::new(1.0).expect("positive event interval"); + let continuous = recover_event_time_standardised_continuous_diffusion(0.4, -0.5) + .expect("continuous candidate"); + let discrete = recover_event_time_standardised_discrete_diffusion(0.4, -0.5, one) + .expect("discrete candidate"); + + assert_eq!( + refuse_standardised_continuous_diffusion_as_standardised_discrete_diffusion( + continuous, discrete + ), + Err(LongitudinalError::ContinuousDiffusionIsNotDiscreteDiffusion) + ); + assert_eq!( + refuse_unstandardised_diffusion_as_standardised_diffusion(continuous, continuous), + Err(LongitudinalError::UnstandardisedDiffusionIsNotStandardisedDiffusion) + ); + assert_eq!( + refuse_total_variance_scaled_diffusion_as_standardised_diffusion(discrete, discrete), + Err(LongitudinalError::TotalVarianceScaledDiffusionIsNotStandardisedDiffusion) + ); +} From 292c5aaf97496641b2dd62df8b4197363b36c918 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 05:06:04 +0900 Subject: [PATCH 032/296] refactor(longitudinal): share stationary within-variance primitive --- crates/longitudinal_core/src/stationary.rs | 62 ++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 crates/longitudinal_core/src/stationary.rs diff --git a/crates/longitudinal_core/src/stationary.rs b/crates/longitudinal_core/src/stationary.rs new file mode 100644 index 000000000..5d1fd3d54 --- /dev/null +++ b/crates/longitudinal_core/src/stationary.rs @@ -0,0 +1,62 @@ +//! Internal stationary within-person variance arithmetic. + +use crate::LongitudinalError; + +/// Recover scalar stationary within-person variance `p = -q / (2a)`. +/// +/// This primitive is intentionally private to Longitudinal Modeling. It admits +/// finite non-negative continuous diffusion and strictly negative drift while +/// avoiding an otherwise unnecessary overflow in the intermediate `2a`. +/// Callers decide whether zero stationary variance is admissible for their +/// named estimand. +pub(crate) fn recover_stationary_within_variance( + continuous_diffusion: f64, + log_rate: f64, +) -> Result { + if !continuous_diffusion.is_finite() || continuous_diffusion < 0.0 || !log_rate.is_finite() { + return Err(LongitudinalError::InvalidTemporalTransformInput); + } + if log_rate >= 0.0 { + return Err(LongitudinalError::StationaryVarianceRequiresStableDrift); + } + if continuous_diffusion == 0.0 { + return Ok(0.0); + } + + let twice_rate = log_rate * 2.0; + let stationary = if twice_rate.is_finite() { + continuous_diffusion / -twice_rate + } else { + (continuous_diffusion / -log_rate) * 0.5 + }; + if !stationary.is_finite() { + return Err(LongitudinalError::InvalidTemporalTransformInput); + } + Ok(stationary) +} + +#[cfg(test)] +mod tests { + use super::recover_stationary_within_variance; + use crate::LongitudinalError; + + #[test] + fn avoids_doubling_overflow_when_final_stationary_variance_is_representable() { + let stationary = recover_stationary_within_variance(f64::MAX, -f64::MAX) + .expect("q / (-2a) remains representable"); + assert_eq!(stationary, 0.5); + } + + #[test] + fn stationary_variance_admission_is_fail_closed() { + assert_eq!(recover_stationary_within_variance(0.0, -0.5), Ok(0.0)); + assert_eq!( + recover_stationary_within_variance(1.0, 0.0), + Err(LongitudinalError::StationaryVarianceRequiresStableDrift) + ); + assert_eq!( + recover_stationary_within_variance(-1.0, -0.5), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); + } +} From 17abbf3aaf06db0465bdc3c4d526c8e1021162de Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 05:06:36 +0900 Subject: [PATCH 033/296] feat(longitudinal): fold scalar standardised diffusion candidates --- crates/longitudinal_core/src/diffusion.rs | 180 ++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 crates/longitudinal_core/src/diffusion.rs diff --git a/crates/longitudinal_core/src/diffusion.rs b/crates/longitudinal_core/src/diffusion.rs new file mode 100644 index 000000000..13684377c --- /dev/null +++ b/crates/longitudinal_core/src/diffusion.rs @@ -0,0 +1,180 @@ +//! Scalar diffusion standardisation on substantive event time. +//! +//! These functions preserve the scientific evidence from TEPP PRs #476 and +//! #477 while moving temporal/state composition out of `psychometric_core`. +//! Driver, Oud, and Voelkle (2017) print the underlying continuous/discrete +//! diffusion transformations and describe relevant-variance standardisation, +//! but the 2017 ctsem summary source does not emit named `DIFFUSIONstd` or +//! `discreteDIFFUSIONstd` matrices. The scalar maps below are therefore +//! research-candidate extensions, not canonical ctsem output and not a DSEM or +//! ctsem estimator. + +use crate::{EventTimeInterval, LongitudinalError, stationary::recover_stationary_within_variance}; + +/// Recover the scalar research-candidate `DIFFUSIONstd = q / p` map. +/// +/// `q` is the continuous diffusion variance-rate input and `p` is the strictly +/// positive stationary within-person variance `-q/(2a)`. The ratio is formed +/// from those two quantities instead of returning a hard-coded `-2a`, which +/// preserves the named composition and exercises the stationary-variance +/// admission contract. Equal numeric values do not collapse this estimand into +/// `asymDIFFUSIONstd` or another variance standardisation. +/// +/// # Errors +/// +/// Returns [`LongitudinalError::InvalidTemporalTransformInput`] for non-finite +/// inputs, negative diffusion, or a non-finite quotient. Returns +/// [`LongitudinalError::StationaryVarianceRequiresStableDrift`] unless `a < 0`. +/// Returns [`LongitudinalError::StandardisedDiffusionRequiresPositiveWithinVariance`] +/// when the stationary within-person variance is zero or underflows to zero. +pub fn recover_event_time_standardised_continuous_diffusion( + continuous_diffusion: f64, + log_rate: f64, +) -> Result { + let stationary = recover_stationary_within_variance(continuous_diffusion, log_rate)?; + if stationary <= 0.0 { + return Err(LongitudinalError::StandardisedDiffusionRequiresPositiveWithinVariance); + } + let ratio = continuous_diffusion / stationary; + if !ratio.is_finite() { + return Err(LongitudinalError::InvalidTemporalTransformInput); + } + Ok(ratio) +} + +/// Recover the scalar research-candidate `discreteDIFFUSIONstd = Q_delta / p` map. +/// +/// For a stable scalar continuous-time process, dividing discrete process noise +/// over an event interval by stationary within-person variance yields +/// `1 - exp(2 a delta)`. The implementation evaluates this ratio directly with +/// `exp_m1` after independently proving that positive stationary variance +/// exists. This avoids multiplying by `p` only to divide by `p` again, which can +/// overflow even when the final standardized ratio is representable. The +/// [`EventTimeInterval`] value object prevents measurement occasion, document, +/// assertion, system, or availability durations from being passed as event time +/// accidentally. +/// +/// # Errors +/// +/// Returns [`LongitudinalError::InvalidTemporalTransformInput`] for invalid +/// diffusion/drift, an event-time product that underflows to signed zero, or a +/// non-representable final ratio. Returns +/// [`LongitudinalError::StationaryVarianceRequiresStableDrift`] unless `a < 0`. +/// Returns [`LongitudinalError::StandardisedDiffusionRequiresPositiveWithinVariance`] +/// when the stationary within-person variance is zero or underflows to zero. +pub fn recover_event_time_standardised_discrete_diffusion( + continuous_diffusion: f64, + log_rate: f64, + event_interval: EventTimeInterval, +) -> Result { + let stationary = recover_stationary_within_variance(continuous_diffusion, log_rate)?; + if stationary <= 0.0 { + return Err(LongitudinalError::StandardisedDiffusionRequiresPositiveWithinVariance); + } + + let half_exponent = log_rate * event_interval.as_f64(); + if half_exponent == 0.0 { + return Err(LongitudinalError::InvalidTemporalTransformInput); + } + if half_exponent == f64::NEG_INFINITY { + return Ok(1.0); + } + if !half_exponent.is_finite() { + return Err(LongitudinalError::InvalidTemporalTransformInput); + } + + let exponent = half_exponent * 2.0; + let ratio = if exponent == f64::NEG_INFINITY { + 1.0 + } else { + -exponent.exp_m1() + }; + if !ratio.is_finite() || ratio <= 0.0 || ratio > 1.0 { + return Err(LongitudinalError::InvalidTemporalTransformInput); + } + Ok(ratio) +} + +/// Refuse treating a continuous standardised diffusion ratio as a discrete one. +/// +/// `q/p` is interval-independent while `Q_delta/p` is an event-interval +/// quantity. Numerical equality at a particular parameter value does not make +/// the named estimands interchangeable. +/// +/// # Errors +/// +/// Always returns [`LongitudinalError::ContinuousDiffusionIsNotDiscreteDiffusion`]. +pub fn refuse_standardised_continuous_diffusion_as_standardised_discrete_diffusion( + continuous_standardised_diffusion: f64, + discrete_standardised_diffusion: f64, +) -> Result { + let _ = ( + continuous_standardised_diffusion, + discrete_standardised_diffusion, + ); + Err(LongitudinalError::ContinuousDiffusionIsNotDiscreteDiffusion) +} + +/// Refuse treating an unstandardised diffusion quantity as a standardised one. +/// +/// # Errors +/// +/// Always returns [`LongitudinalError::UnstandardisedDiffusionIsNotStandardisedDiffusion`]. +pub fn refuse_unstandardised_diffusion_as_standardised_diffusion( + unstandardised_diffusion: f64, + standardised_diffusion: f64, +) -> Result { + let _ = (unstandardised_diffusion, standardised_diffusion); + Err(LongitudinalError::UnstandardisedDiffusionIsNotStandardisedDiffusion) +} + +/// Refuse scaling diffusion by total trait-plus-state variance as standardisation. +/// +/// Driver et al.'s relevant-variance rule for these scalar research candidates +/// uses within-person stationary variance, not a total that also contains +/// between-unit trait or added time-independent-predictor variance. +/// +/// # Errors +/// +/// Always returns [`LongitudinalError::TotalVarianceScaledDiffusionIsNotStandardisedDiffusion`]. +pub fn refuse_total_variance_scaled_diffusion_as_standardised_diffusion( + total_variance_scaled_diffusion: f64, + standardised_diffusion: f64, +) -> Result { + let _ = (total_variance_scaled_diffusion, standardised_diffusion); + Err(LongitudinalError::TotalVarianceScaledDiffusionIsNotStandardisedDiffusion) +} + +#[cfg(test)] +mod tests { + use super::{ + recover_event_time_standardised_continuous_diffusion, + recover_event_time_standardised_discrete_diffusion, + }; + use crate::{EventTimeInterval, LongitudinalError}; + + #[test] + fn discrete_candidate_fails_closed_on_signed_zero_event_product() { + let tiny = EventTimeInterval::new(f64::from_bits(1)).expect("minimum subnormal interval"); + assert_eq!( + recover_event_time_standardised_discrete_diffusion(1.0, -0.5, tiny), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); + } + + #[test] + fn very_large_stable_event_product_has_representable_unit_limit() { + let interval = EventTimeInterval::new(f64::MAX).expect("finite positive interval"); + let recovered = recover_event_time_standardised_discrete_diffusion(1.0, -1.0, interval) + .expect("the final standardized noise fraction tends to one"); + assert_eq!(recovered, 1.0); + } + + #[test] + fn continuous_candidate_rejects_nonrepresentable_ratio() { + assert_eq!( + recover_event_time_standardised_continuous_diffusion(f64::MAX, -f64::MAX), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); + } +} From 2b3516347b57beefc76e201ebc3377f61a851cec Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 05:07:06 +0900 Subject: [PATCH 034/296] feat(longitudinal): add diffusion candidate refusal errors --- crates/longitudinal_core/src/error.rs | 36 +++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/crates/longitudinal_core/src/error.rs b/crates/longitudinal_core/src/error.rs index ce7549ca1..5d597969a 100644 --- a/crates/longitudinal_core/src/error.rs +++ b/crates/longitudinal_core/src/error.rs @@ -26,6 +26,8 @@ pub enum LongitudinalError { CovarianceBoundViolation, /// A stationary within-person variance was requested without stable negative drift. StationaryVarianceRequiresStableDrift, + /// A standardised diffusion candidate was requested without positive within variance. + StandardisedDiffusionRequiresPositiveWithinVariance, /// `discreteDRIFTstd` was requested without positive stationary within-person variance. StandardisedDriftRequiresPositiveWithinVariance, /// Unstandardised `discreteDRIFT` was substituted for `discreteDRIFTstd`. @@ -34,6 +36,12 @@ pub enum LongitudinalError { TraitStateAssociationIsNotStandardisedDrift, /// Between-unit trait variance was used as the drift standardisation variance. TraitVarianceIsNotDriftStandardisationVariance, + /// A continuous diffusion ratio was substituted for a discrete diffusion ratio. + ContinuousDiffusionIsNotDiscreteDiffusion, + /// An unstandardised diffusion quantity was substituted for a standardised one. + UnstandardisedDiffusionIsNotStandardisedDiffusion, + /// Diffusion was scaled by total variance instead of relevant within variance. + TotalVarianceScaledDiffusionIsNotStandardisedDiffusion, } impl fmt::Display for LongitudinalError { @@ -57,6 +65,9 @@ impl fmt::Display for LongitudinalError { Self::StationaryVarianceRequiresStableDrift => { "stationary within-person variance requires strictly negative drift" } + Self::StandardisedDiffusionRequiresPositiveWithinVariance => { + "standardised diffusion requires positive stationary within-person variance" + } Self::StandardisedDriftRequiresPositiveWithinVariance => { "standardised discrete drift requires positive stationary within-person variance" } @@ -69,6 +80,15 @@ impl fmt::Display for LongitudinalError { Self::TraitVarianceIsNotDriftStandardisationVariance => { "trait variance is not the drift standardisation variance" } + Self::ContinuousDiffusionIsNotDiscreteDiffusion => { + "continuous standardised diffusion is not discrete standardised diffusion" + } + Self::UnstandardisedDiffusionIsNotStandardisedDiffusion => { + "unstandardised diffusion is not standardised diffusion" + } + Self::TotalVarianceScaledDiffusionIsNotStandardisedDiffusion => { + "total-variance-scaled diffusion is not relevant-variance-standardised diffusion" + } }; formatter.write_str(message) } @@ -123,6 +143,10 @@ mod tests { LongitudinalError::StationaryVarianceRequiresStableDrift, "stationary within-person variance requires strictly negative drift", ), + ( + LongitudinalError::StandardisedDiffusionRequiresPositiveWithinVariance, + "standardised diffusion requires positive stationary within-person variance", + ), ( LongitudinalError::StandardisedDriftRequiresPositiveWithinVariance, "standardised discrete drift requires positive stationary within-person variance", @@ -139,6 +163,18 @@ mod tests { LongitudinalError::TraitVarianceIsNotDriftStandardisationVariance, "trait variance is not the drift standardisation variance", ), + ( + LongitudinalError::ContinuousDiffusionIsNotDiscreteDiffusion, + "continuous standardised diffusion is not discrete standardised diffusion", + ), + ( + LongitudinalError::UnstandardisedDiffusionIsNotStandardisedDiffusion, + "unstandardised diffusion is not standardised diffusion", + ), + ( + LongitudinalError::TotalVarianceScaledDiffusionIsNotStandardisedDiffusion, + "total-variance-scaled diffusion is not relevant-variance-standardised diffusion", + ), ] { assert_eq!(error.to_string(), message); } From 385862091e7796e924771d37ed00e1091a9cb396 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 05:07:22 +0900 Subject: [PATCH 035/296] refactor(longitudinal): reuse stationary variance primitive --- .../longitudinal_core/src/discrete_drift.rs | 33 +++---------------- 1 file changed, 5 insertions(+), 28 deletions(-) diff --git a/crates/longitudinal_core/src/discrete_drift.rs b/crates/longitudinal_core/src/discrete_drift.rs index 927f4efb3..64f1495d6 100644 --- a/crates/longitudinal_core/src/discrete_drift.rs +++ b/crates/longitudinal_core/src/discrete_drift.rs @@ -1,6 +1,8 @@ //! Event-time standardisation for scalar continuous-time drift. -use crate::{EventTimeInterval, LongitudinalError}; +use crate::{ + EventTimeInterval, LongitudinalError, stationary::recover_stationary_within_variance, +}; /// Recover the scalar p. 16 `discreteDRIFTstd` on event time. /// @@ -32,33 +34,8 @@ pub fn recover_event_time_standardised_discrete_drift( log_rate: f64, event_interval: EventTimeInterval, ) -> Result { - if !continuous_diffusion.is_finite() || continuous_diffusion < 0.0 { - return Err(LongitudinalError::InvalidTemporalTransformInput); - } - if !log_rate.is_finite() { - return Err(LongitudinalError::InvalidTemporalTransformInput); - } - if log_rate >= 0.0 { - return Err(LongitudinalError::StationaryVarianceRequiresStableDrift); - } - if continuous_diffusion == 0.0 { - return Err(LongitudinalError::StandardisedDriftRequiresPositiveWithinVariance); - } - - // Prefer the direct scalar stationary-variance expression when doubling the - // stable rate remains representable. If only that intermediate overflows, - // divide by the finite original rate first and apply the factor 1/2 after - // the division. This preserves valid extreme-rate cases such as - // q = f64::MAX, a = -f64::MAX without changing the mathematical estimand. - let twice_rate = log_rate * 2.0; - let within_person_variance = if twice_rate.is_finite() { - continuous_diffusion / -twice_rate - } else { - (continuous_diffusion / -log_rate) * 0.5 - }; - if !within_person_variance.is_finite() { - return Err(LongitudinalError::InvalidTemporalTransformInput); - } + let within_person_variance = + recover_stationary_within_variance(continuous_diffusion, log_rate)?; if within_person_variance <= 0.0 { return Err(LongitudinalError::StandardisedDriftRequiresPositiveWithinVariance); } From 93e7f4aca8b66ecd5b546e95328b472eb06be501 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 05:07:35 +0900 Subject: [PATCH 036/296] feat(longitudinal): export diffusion candidate contracts --- crates/longitudinal_core/src/lib.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/crates/longitudinal_core/src/lib.rs b/crates/longitudinal_core/src/lib.rs index fd6bbcdee..62df9a079 100644 --- a/crates/longitudinal_core/src/lib.rs +++ b/crates/longitudinal_core/src/lib.rs @@ -6,16 +6,19 @@ //! Stable between-unit components cannot be scored as within-unit change. //! Event-time lagged associations require both marginal variances before a //! covariance can be standardized as a correlation. Event-interval response -//! transforms live here rather than in a generic psychometric kernel. Recovery -//! reports computed component RMSE against known truth (ADR 0005). +//! transforms and scalar diffusion standardisation candidates live here rather +//! than in a generic psychometric kernel. Recovery reports computed component +//! RMSE against known truth (ADR 0005). mod association; mod component; mod decompose; +mod diffusion; mod discrete_drift; mod error; mod event_time; mod level; +mod stationary; mod temporal_association; /// One unit-specific within or between component. @@ -26,6 +29,16 @@ pub use component::component_root_mean_square_error; pub use decompose::OccasionObservation; /// Decompose occasion scores into unit means and within residuals. pub use decompose::decompose_within_between; +/// Recover the scalar research-candidate continuous diffusion standardisation on event time. +pub use diffusion::recover_event_time_standardised_continuous_diffusion; +/// Recover the scalar research-candidate discrete diffusion standardisation on event time. +pub use diffusion::recover_event_time_standardised_discrete_diffusion; +/// Refuse continuous standardised diffusion as discrete standardised diffusion. +pub use diffusion::refuse_standardised_continuous_diffusion_as_standardised_discrete_diffusion; +/// Refuse total-variance scaling as relevant-variance diffusion standardisation. +pub use diffusion::refuse_total_variance_scaled_diffusion_as_standardised_diffusion; +/// Refuse unstandardised diffusion as standardised diffusion. +pub use diffusion::refuse_unstandardised_diffusion_as_standardised_diffusion; /// Recover scalar Driver p.16 `discreteDRIFTstd` on substantive event time. pub use discrete_drift::recover_event_time_standardised_discrete_drift; /// Refuse trait-plus-state association as `discreteDRIFTstd`. From 46865f3b45a44905c3475d2e7966bb735f9b5d67 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 05:11:12 +0900 Subject: [PATCH 037/296] test(longitudinal): RED subnormal diffusion cancellation --- .../tests/standardised_diffusion_contract.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/crates/longitudinal_core/tests/standardised_diffusion_contract.rs b/crates/longitudinal_core/tests/standardised_diffusion_contract.rs index a5c19f6f3..5f5c3b234 100644 --- a/crates/longitudinal_core/tests/standardised_diffusion_contract.rs +++ b/crates/longitudinal_core/tests/standardised_diffusion_contract.rs @@ -1,4 +1,4 @@ -//! RED contract for scalar standardised diffusion maps in Longitudinal Modeling. +//! RED/GREEN contract for scalar standardised diffusion maps in Longitudinal Modeling. //! //! Driver, Oud, and Voelkle (2017) print the underlying continuous/discrete //! diffusion transformations and describe relevant-variance standardisation, @@ -30,6 +30,20 @@ fn continuous_diffusion_candidate_recovers_relevant_variance_ratio() { assert!((max_path - 1.5).abs() < 1e-15); } +#[test] +fn continuous_diffusion_candidate_does_not_lose_cancellation_to_subnormal_rounding() { + let minimum_subnormal = f64::from_bits(1); + let recovered = recover_event_time_standardised_continuous_diffusion(minimum_subnormal, -0.75) + .expect("positive rounded stationary variance remains admissible"); + assert_eq!(recovered, 1.5); + + let slightly_larger_subnormal = f64::from_bits(3); + let recovered_larger = + recover_event_time_standardised_continuous_diffusion(slightly_larger_subnormal, -0.75) + .expect("scale must not alter the standardized scalar identity"); + assert_eq!(recovered_larger, 1.5); +} + #[test] fn discrete_diffusion_candidate_preserves_event_interval_semantics() { let one = EventTimeInterval::new(1.0).expect("positive event interval"); From ff3380748a18f69e888ee01a539ee07416135d67 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 05:11:54 +0900 Subject: [PATCH 038/296] fix(longitudinal): preserve subnormal diffusion cancellation --- crates/longitudinal_core/src/diffusion.rs | 24 +++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/crates/longitudinal_core/src/diffusion.rs b/crates/longitudinal_core/src/diffusion.rs index 13684377c..5c4385f41 100644 --- a/crates/longitudinal_core/src/diffusion.rs +++ b/crates/longitudinal_core/src/diffusion.rs @@ -14,16 +14,20 @@ use crate::{EventTimeInterval, LongitudinalError, stationary::recover_stationary /// Recover the scalar research-candidate `DIFFUSIONstd = q / p` map. /// /// `q` is the continuous diffusion variance-rate input and `p` is the strictly -/// positive stationary within-person variance `-q/(2a)`. The ratio is formed -/// from those two quantities instead of returning a hard-coded `-2a`, which -/// preserves the named composition and exercises the stationary-variance -/// admission contract. Equal numeric values do not collapse this estimand into -/// `asymDIFFUSIONstd` or another variance standardisation. +/// positive stationary within-person variance `-q/(2a)`. The implementation +/// first recovers `p` to enforce the named estimand's positive-stationarity +/// admission contract. It then evaluates the algebraically identical scalar +/// ratio as `-2a` instead of dividing by the rounded binary64 representation of +/// `p`. That distinction matters for subnormal `q`/`p`: rounding `p` before +/// `q/p` can destroy the cancellation and make the standardized result depend +/// spuriously on diffusion scale. Equal numeric values still do not collapse +/// this estimand into `asymDIFFUSIONstd` or another variance standardisation. /// /// # Errors /// /// Returns [`LongitudinalError::InvalidTemporalTransformInput`] for non-finite -/// inputs, negative diffusion, or a non-finite quotient. Returns +/// inputs, negative diffusion, a non-representable stationary variance, or a +/// non-representable final `-2a` ratio. Returns /// [`LongitudinalError::StationaryVarianceRequiresStableDrift`] unless `a < 0`. /// Returns [`LongitudinalError::StandardisedDiffusionRequiresPositiveWithinVariance`] /// when the stationary within-person variance is zero or underflows to zero. @@ -35,8 +39,12 @@ pub fn recover_event_time_standardised_continuous_diffusion( if stationary <= 0.0 { return Err(LongitudinalError::StandardisedDiffusionRequiresPositiveWithinVariance); } - let ratio = continuous_diffusion / stationary; - if !ratio.is_finite() { + + // Algebraically q / (-q/(2a)) == -2a for every positive q. Evaluating + // q / rounded(p) is numerically wrong when q and p are subnormal because + // the rounded stationary variance no longer preserves that cancellation. + let ratio = -2.0 * log_rate; + if !ratio.is_finite() || ratio <= 0.0 { return Err(LongitudinalError::InvalidTemporalTransformInput); } Ok(ratio) From 0529facff6156acf8639c6d531e129ba917da1f7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 05:17:55 +0900 Subject: [PATCH 039/296] fix(longitudinal): remove unreachable diffusion guard --- crates/longitudinal_core/src/diffusion.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/longitudinal_core/src/diffusion.rs b/crates/longitudinal_core/src/diffusion.rs index 5c4385f41..d8c50ac94 100644 --- a/crates/longitudinal_core/src/diffusion.rs +++ b/crates/longitudinal_core/src/diffusion.rs @@ -43,8 +43,10 @@ pub fn recover_event_time_standardised_continuous_diffusion( // Algebraically q / (-q/(2a)) == -2a for every positive q. Evaluating // q / rounded(p) is numerically wrong when q and p are subnormal because // the rounded stationary variance no longer preserves that cancellation. + // The admission gate has already established finite log_rate < 0, so the + // only possible invalid result here is overflow to +infinity. let ratio = -2.0 * log_rate; - if !ratio.is_finite() || ratio <= 0.0 { + if !ratio.is_finite() { return Err(LongitudinalError::InvalidTemporalTransformInput); } Ok(ratio) From 4e7435f6dd232ae8e1e019f1393e7285e32c6527 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 05:23:50 +0900 Subject: [PATCH 040/296] docs(research): register correlation-bound primary sources --- docs/research/standards-and-literature.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/research/standards-and-literature.md b/docs/research/standards-and-literature.md index 8ce0ecc77..66e3c5955 100644 --- a/docs/research/standards-and-literature.md +++ b/docs/research/standards-and-literature.md @@ -447,3 +447,11 @@ Nielsen, S., Cetin, E., Schwendeman, P., Sun, Q., Xu, J., & Tang, Y. (2026). Lea Tang, Y., Cetin, E., Xu, J., Sun, Q., Nielsen, S., Richard, V., Goda, H., Tymchenko, I., Nguyen, N., Lee, H., Ashiga, M., Kotyan, S., Kuroki, S., & Clanuwat, T. (2026). *Sakana Fugu technical report* [Preprint]. arXiv. https://arxiv.org/abs/2606.21228 TRINITY motivates lightweight learned model/role delegation over multiple turns; Conductor motivates query-adaptive natural-language workflow/topology/instruction generation and recursive test-time scaling; Fugu demonstrates a production-oriented family of query-adaptive agentic scaffolds building on these research lines (Xu et al., 2026; Nielsen et al., 2026; Tang et al., 2026). TEPP therefore treats direct routing, verification, fixed multi-agent workflows, adaptive orchestration, stage count, decomposition, recursion, access lists, role-specific reasoning effort, and total test-time budget as explicit experimental variables. `tepp_api::route_orchestration` is the deterministic selector for those variables, while `orchestrator_live` records the selected mode on `POST /v1/interpretation-runs` and refuses scientific-authority promotion. Deeper/more-agent orchestration is never assumed better by default. Comparable-budget ablation, evidence support, calibration, disagreement, safety, cost, and failure behavior are required before a production claim. See `docs/research/adaptive-orchestration-router.md` and `docs/research/orchestrator-live-http.md`. + +## Correlation and covariance-bound foundations + +Bouniakowsky, V. (1859). Sur quelques inégalités concernant les intégrales ordinaires et les intégrales aux différences finies. *Mémoires de l’Académie Impériale des Sciences de Saint-Pétersbourg, VIIe Série, 1*(9), 1–18. + +Pearson, K. (1895). Notes on regression and inheritance in the case of two parents. *Proceedings of the Royal Society of London, 58*, 240–242. + +Rodgers, J. L., & Nicewander, W. A. (1988). Thirteen ways to look at the correlation coefficient. *The American Statistician, 42*(1), 59–66. https://doi.org/10.1080/00031305.1988.10475524 From 6ee41a17f5b180ca98f8bf54df16170c33cf3de7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 22:40:24 +0000 Subject: [PATCH 041/296] feat(longitudinal): fold CWC-then-irregular residual log-rate from #327 Unique pairwise-mean-after-CWC evidence belongs on Longitudinal Modeling landing vehicle #310 with typed EventTimeInterval. Already-centered pairs recover the Driver, Oud, and Voelkle (2017, Eq. 3) scalar inverse, including ln(0.5). CWC of a raw AR path is not raw-process drift (Curran & Bauer, 2011, Eq. 36). Overflowing residuals after a finite unit mean fail closed. This is not Newton LS, not DSEM, and not a second invariance crate. --- ARCHITECTURE.md | 2 +- CHANGELOG.md | 2 + CLAUDE.md | 2 +- crates/longitudinal_core/Cargo.toml | 2 +- crates/longitudinal_core/src/error.rs | 9 + .../src/irregular_residual.rs | 781 ++++++++++++++++++ crates/longitudinal_core/src/lib.rs | 18 +- .../tests/cwc_irregular_residual_contract.rs | 46 ++ .../standardised_discrete_drift_contract.rs | 11 +- ...ongitudinal-modeling-ownership-addendum.md | 4 +- ...4-amendment-longitudinal-time-ownership.md | 2 + .../cwc-irregular-residual-log-rate.md | 79 ++ docs/research/longitudinal-within-between.md | 4 +- .../multilevel-event-time-recovery.md | 1 + 14 files changed, 950 insertions(+), 13 deletions(-) create mode 100644 crates/longitudinal_core/src/irregular_residual.rs create mode 100644 crates/longitudinal_core/tests/cwc_irregular_residual_contract.rs create mode 100644 docs/research/cwc-irregular-residual-log-rate.md diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index e5f1e8cfb..cc37d1923 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -102,7 +102,7 @@ boundaries above remain the target modular MSA architecture. | `operational_log` | operational logs; `try_record` is the only recording API; source text and source identity are not loggable; `persistence_postgres` `audit_event` inserts call the same gate | | `service_tls` | production TLS bind gates and rustls server config | | `derived_sensitivity` | derived topic/factor/relation outputs inherit source sensitivity | -| `longitudinal_core` | within/between decomposition; refuse between-as-within; component RMSE; event-time lagged-correlation standardization from lagged covariance plus both occasion-specific marginal variances with exact binary64 covariance-bound validation; arithmetic boundary only, not a DSEM/ctsem/state-transition estimator | +| `longitudinal_core` | within/between decomposition; refuse between-as-within; component RMSE; event-time lagged-correlation standardization from lagged covariance plus both occasion-specific marginal variances with exact binary64 covariance-bound validation; scalar event-time `discreteDRIFTstd` and research-candidate diffusion standardisation; CWC-then-irregular residual pairwise-mean log-rate with Curran refusal (not raw-process drift); arithmetic boundary only, not a DSEM/ctsem/state-transition estimator | | `topic_lineage` | global topic identity across active/dormant/reactivated states | | `network_analysis` | compositional cluster-pair gates; raw simplex is not Euclidean | | `interpretation_gateway` | evidence-bounded LLM interpretations; not estimators or observed facts | diff --git a/CHANGELOG.md b/CHANGELOG.md index 70bf6ea1a..3f2b9d42c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,8 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang ## [Unreleased] +- `longitudinal_core` folds Draft #327 unique CWC-then-irregular residual log-rate onto the Longitudinal Modeling landing vehicle. Consecutive unit-mean-centered pairs keep typed `EventTimeInterval`. The pairwise mean of the Driver, Oud, and Voelkle (2017, Eq. 3; JSS PDF opened 2026-09-02) scalar inverse `a = ln(|later| / |earlier|) / Δt` is taken on nonzero same-sign residuals, with a stable logarithm when that ratio overflows or underflows, and an incremental mean so two finite rates whose raw sum overflows stay representable. Already-centered pairs recover `ln(0.5)`; CWC of a raw AR path does not. Curran and Bauer (2011, pp. 583–619; PMC3059070 XML opened 2026-09-02; Eq. 36) show that person-mean centering of a time-related TVC is biased for the within-person effect; `refuse_cwc_residual_log_rate_as_raw_process_drift` always fails closed. T=2 CWC is `r, −r` (empty admissible). This is not Newton LS, not DSEM, and not a second invariance crate. Voelkle et al. (2012, Eq. 7) remains previously opened lineage; the ZORA PDF was not re-opened this cycle. Meredith (1993) remains unread. Mislevy (1991) remains unread. + - `longitudinal_core` adds event-time lagged-correlation standardization that requires lagged covariance plus both occasion-specific marginal variances, enforces the exact binary64 covariance bound, and uses bounded division order to avoid representable overflow/underflow. The invalid one-sided covariance/earlier-variance `expected_autocorrelation` API is removed; this arithmetic boundary does not claim a DSEM/ctsem/state-transition estimator. - `event_core` adds bounded Allen interval-consistency classification, atomic path-consistency closure, contradiction/resource refusals, and an explicit dependency-error fallback without claiming unrestricted global satisfiability. diff --git a/CLAUDE.md b/CLAUDE.md index 339692cf0..8cc640fd2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,7 +16,7 @@ Read and follow `AGENTS.md` before changing this repository. The repository-wide - Do not treat raw topic proportions as ordinary Euclidean indicators. Use logistic-normal coordinates or valid log-ratio coordinates and propagate posterior uncertainty into ESEM/DSEM. - Do not treat metric/weak invariance as a latent-mean license. Strong (equal loading and intercept) or strict is required; `#84` `metric` licenses shared metric meaning only. Putnick and Bornstein (2016, PMC5145197 opened 2026-08-19T22:15Z) require scalar invariance before latent-mean comparison; residual invariance is not a prerequisite. Two-observation series have no residual degrees of freedom (`ordinary_least_squares_fit` returns residual variance `0`) and cap at strong/scalar; they still license means. This is two-group OLS, not MGCFA. Meredith (1993) names remain unread labels (Unpaywall/OpenAlex 2026-08-25T11:32Z: closed). - Do not use the difference quotient as a continuous-time rate. The scalar map is `a = ln(φ) / Δt` on event time. Discrete lags from unequal event intervals are not one coefficient; remap them through that log-rate. Binary64 `exp(a Δt) = 0` is not a discrete lag. A constant predictor's discrete effect is Voelkle et al. (2012, Eq. 12), evaluated as `a_yx (expm1(z) / a_xx)` with `z = a_xx Δt` so a finite result is not lost when `z` overflows to `-∞` or when `a_yx Δt` overflows. When `expm1(z)` overflows at a finite `z`, rewrite in log space; a zero continuous effect is exactly zero; an overflowing `a_yx/a_xx` rewrite term fails closed. The first-order product is the underflow limit of that equation, not the general constant-predictor discrete effect. A time-varying predictor whose sampling interval equals its constancy interval uses Voelkle et al. (2012, Eq. 14): `b* = a_yx Δt`. Unmatched intervals fail closed (Oud & Jansen, 2000, unread). Discrete process noise is Driver et al. (2017, Eq. 3): `Q_Δt = 0.5 q (expm1(z) / a)` with `z = 2 (a Δt)` and `q = G G⊤ ≥ 0`; do not form `2 a` first; `a = 0` and `z → 0` recover `q Δt`; a zero diffusion is exactly zero; an overflowing rewrite scale `0.5 q / a` fails closed; this is not a Kalman filter. `Q_Δt` is `cov(η_t | η_{t-1})`, not `Var(η_t)`. The lagged covariance is `exp(a Δt) p` and the unconditional variance is `exp(2 a Δt) p + Q_Δt` (Driver et al., 2017, Eq. 3–4, pp. 4–5; JSS has no numbered §2.2). A zero diffusion whose `2 (a Δt)` overflows to `+∞` is not a finite `Var(η_t)`. The stationary within-subject variance is the `Δt → ∞` limit of Eq. 4: `-q / (2 a)` for stable `a < 0` (JSS p. 16 `asymDIFFUSION`; §4.3). When `2 a` is finite, form `q / -(2 a)` so `q / a` overflow does not lose a finite result (`q = MAX`, `a = -0.75` → `MAX / 1.5`). When `2 a` overflows, form `(q / a) * -0.5`. Do not form `0.5 q` first (`q = from_bits(1)` underflows). `a ≥ 0` has no finite stationary variance. Finite-interval `Q_Δt` is not that limit. Trait-plus-state variance is `trait + state` and lagged covariance is `trait + exp(a Δt) p` (Driver et al., 2017, §4.3, p. 9). Trait variance is not process noise and not `asymDIFFUSION`. Evolving the summed variance as if it were all state is not that map. This is not RI-CLPM. Observed-indicator variance is `λ² Var(η) + θ` when `MANIFESTTRAITVAR` is zero and `λ² Var(η) + θ + ψ` otherwise (Driver et al., 2017, Eq. 5, p. 5; Table 2, p. 12). Lagged observed covariance is `λ² cov(η_t, η_{t-1}) + ψ`; `MANIFESTVAR` does not enter. Observed-indicator mean is `τ + λ μ` (Driver et al., 2017, Eq. 5; Table 2, p. 12). `MANIFESTMEANS` is `τ`, not `E(y)`. `E(η)` is not `E(y)`. `CINT` is not `MANIFESTMEANS`. `T0MEANS` is not `E(y)`. The discrete latent mean is `μ_t = exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ` (Driver et al., 2017, Eq. 3, p. 4; Table 2, p. 12). `T0MEANS` is not `μ_t`. `CINT` is not that discrete increment. A zero drift is `κ Δt`. Underflow of `exp(a Δt)` to `+0` drops the carried `T0MEANS` and keeps `−κ / a`. The evolved observed mean is `τ + λ μ_t` (Driver et al., 2017, Eq. 5 of that Eq. 3 map). The first-occasion map `τ + λ μ_0` is not `E(y_t)`. `μ_t` is not `E(y_t)`. The contemporaneous time-dependent predictor impulse is `m x` (Driver et al., 2017, Eq. 3 fourth summand; Table 2 `TDPREDEFFECT` is `M`). Form `μ_t` first, then add `m x`. `TDPREDEFFECT` is not `CINT`. `M x` is not `A^{-1}[e^{A Δt} − I] B z` and is not Voelkle et al. (2012, Eq. 14). The §7.2 level-change form is not that impulse. The observed mean of that contemporaneous impulse is `τ + λ(μ_t + m x)` (Driver et al., 2017, Eq. 5 of the Eq. 3 fourth-summand composition). The evolved map `τ + λ μ_t` is not that observed mean. The carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`. The evolved-plus-impulse latent mean is not `E(y_t)`. The time-independent predictor increment is `A^{-1}[e^{A Δt} − I] B z` (Driver et al., 2017, Eq. 3 second summand; Table 2 `TIPREDEFFECT` is `B`). Form `B z` first, then the discrete intercept map. A zero drift is `B z Δt`. `TIPREDEFFECT` is `B`, not that discrete increment. `A^{-1}[e^{A Δt} − I] B z` is not `CINT`, not `M x`, and not Voelkle et al. (2012, Eq. 14). The observed mean of that increment is `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` (Driver et al., 2017, Eq. 5 of the Eq. 3 printed addend after the `T0MEANS` carry and the `CINT` increment). The evolved map `τ + λ μ_t` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`. The evolved-plus-increment latent mean is not `E(y_t)`. The within-interval time-dependent impulse carry is `e^{A(t−u)} M x` for `t0 < u < t` (Driver et al., 2017, Eq. 1–2 Green-function integral of Eq. 2; §7.2 dissipation). Form `m x` first, then `e^{a(t−u)} m x`. A zero drift is `m x` with no dissipation. Underflow of `e^{a(t−u)}` to `+0` is vanishing dissipation and is kept. `e^{A(t−u)} M x` is not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle et al. (2012, Eq. 14). An impulse at `u = t` is the contemporaneous map. An impulse at `u ≤ t0` is already in `η(t0)`. The observed mean of that carry is `τ + λ(μ_t + e^{a(t−u)} m x)` (Driver et al., 2017, Eq. 5 of the Eq. 1–2 carried latent mean). The evolved map `τ + λ μ_t` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean when `u ≠ t`. `MANIFESTMEANS` is not `E(y_t)`. The carried latent mean is not `E(y_t)`. The first-occasion time-independent predictor shift is `t0_b z` (Driver et al., 2017, Table 3 `T0TIPREDEFFECT`; Eq. 3 first summand). Form `t0_b z` first, then `e^{a Δt} t0_b z`. Form `μ_t` first, then add that carry. A zero drift is `t0_b z`. Underflow of `e^{a Δt}` to `+0` is a vanishing carry of the first-occasion shift and is kept. `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`. `e^{A Δt} t0_b z` is not `t0_b z`. `T0TIPREDEFFECT` is the coefficient, not the shift. The observed mean of that first-occasion carry is `τ + λ(μ_t + e^{a Δt} t0_b z)` (Driver et al., 2017, Eq. 5 of the Table 3 / Eq. 3 first-summand composition). The evolved map `τ + λ μ_t` is not that observed mean. The process-increment map `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The impulse-carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t0`. The evolved-plus-carry latent mean is not `E(y_t)`. The first-occasion time-dependent predictor shift is `t0_m x0` (Driver et al., 2017, Table 3 `T0TDPREDEFFECT`; Eq. 3 first summand; JSS PDF re-opened 2026-08-20T19:10Z). Form `t0_m x0` first, then `e^{a Δt} t0_m x0`. Form `μ_t` first, then add that carry. A zero drift is `t0_m x0`. Underflow of `e^{a Δt}` to `+0` is a vanishing carry of the first-occasion shift and is kept. `t0_m x0` is not `M x`, not `e^{A(t−u)} M x` for `t0 < u < t`, not `t0_b z`, not `A^{-1}[e^{A Δt} − I] B z`, and not `CINT`. `e^{A Δt} t0_m x0` is not `t0_m x0`. `T0TDPREDEFFECT` is the coefficient, not the shift. An impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`. The observed mean of that first-occasion TD carry is `τ + λ(μ_t + e^{a Δt} t0_m x0)` (Driver et al., 2017, Eq. 5 of the Table 3 / Eq. 3 first-summand TD composition; JSS PDF re-opened 2026-08-20T19:07Z). The evolved map `τ + λ μ_t` is not that observed mean. The process-increment map `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The impulse-carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t0`. The first-occasion TI map `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean. The evolved-plus-carry latent mean is not `E(y_t)`. The lasting level-change `CINT` is `κ = −a m x` (Driver et al., 2017, §7.2, pp. 20–21; JSS PDF re-opened 2026-08-20T19:45Z). Form `m x` first, then multiply by `−a`. Stable `a < 0` is required so `−κ / a = m x` is an equilibrium offset. `a ≥ 0` cannot hold a new process mean. `−a m x` is not the dissipating Dirac `m x`, not a free `CINT`, and not `A^{-1}[e^{A Δt} − I] B z`. The extra near-zero-drift latent process also named in §7.2 is a different specification and is not this `CINT` setting. Equation 3 maps that intercept as `(1 − e^{a Δt}) m x` (JSS PDF re-opened 2026-08-20T19:50Z). Form the level-change `CINT` first, then the discrete intercept map. Underflow of `e^{a Δt}` to `+0` keeps `m x`. `(1 − e^{a Δt}) m x` is not `m x`, not `κ`, and not `A^{-1}[e^{A Δt} − I] B z`. The printed §7.2 lasting level change is an extra near-zero-drift latent process (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-20T23:10Z). `T0MEANS`, `CINT`, `T0VAR`, `DIFFUSION`, and `TRAITVAR` of that process are fixed to 0; `TDPREDEFFECT` on it is fixed to 1; its `DRIFT` diagonal is very close to 0 (printed example `−0.000001`; precisely 0 causes computational problems); the original process is driven by the `DRIFT` coupling `a_{ηξ}`. After a unit identification impulse the scalar contribution is `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (`ε = a` is `a_{ηξ} x Δt e^{a Δt}`). Form `a_{ηξ} x` first. A zero coupling or zero predictor is exactly zero. `ε ≥ 0` fails closed. That contribution is not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`. The observed mean of that extra-process contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a))` (Driver et al., 2017, Eq. 5 of that §7.2 contribution; JSS PDF re-opened 2026-08-21T06:12Z). The extra process has `LAMBDA` 0 and is not an observed indicator. Original indicators load on the original process after the `DRIFT` coupling. The evolved map `τ + λ μ_t` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The contribution is not `E(y_t)`. The evolved-plus-contribution latent mean is not `E(y_t)`. `T0TDPREDEFFECT` on the extra process begins at `t = 0` and uses `Δt = t − t0` for both the original-process evolution and the extra drive. `TDPREDEFFECT` after `t0` uses `t − u` with `t0 < u < t` for the extra drive while `μ_t` still uses `Δt`. The observed mean of that after-t0 extra-process contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a))` (Driver et al., 2017, Eq. 5 of that §7.2 after-t0 contribution; JSS PDF re-opened 2026-08-21T06:32Z). The first-occasion extra-process observed mean is not that observed mean when `u ≠ t0`. The impulse-carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is a Dirac on the original process and is not that `DRIFT` drive. An impulse at `u = t0` or `u = t` is not interior. The asymptotic time-independent predictor effect is `-B z / a` (Driver et al., 2017, §7.2, pp. 20–21; JSS PDF opened 2026-08-21T13:08Z). Form `B z` first, then divide by `-a`. Stable `a < 0` is required. `a ≥ 0` cannot hold a finite process-mean change. `-B z / a` is not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`. The asymptotic time-independent predictor variance is `(B / a)² v` (Driver et al., 2017, §7.2, pp. 20–21 `addedTIPREDVAR`). Form the unit asymptotic effect first, then square, then multiply by `v`. `(B / a)² v` is not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`. The asymptotic continuous intercept is `-κ / a` (Driver et al., 2017, Table 2, p. 12 `asymCINT`; Eq. 3 as `Δt → ∞`; JSS PDF opened 2026-08-21T16:13Z). Form `κ` first, then divide by `-a`. Stable `a < 0` is required. `-κ / a` is not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`. The p. 16 stationary `T0MEANS` constraint is `-κ / a + −B z / a`. Form the intercept contribution first, then include the TI extra effect, then add. That constrained first-occasion mean is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean. Equation 5 of that constrained mean is `τ + λ(−κ / a + −B z / a)` (Driver et al., 2017, §4.3, pp. 9–10; Eq. 5, p. 5; JSS PDF re-opened 2026-08-21T20:07Z). Form the stationary latent mean first, then `τ + λ` of that mean. `τ + λ μ_0` for free `T0MEANS` is not that composition. `τ + λ(−κ / a)` is not that composition when `B z ≠ 0`. `τ + λ μ_t` is not that composition. `MANIFESTMEANS` is not `E(y_0)`. The constrained latent mean is not `E(y_0)`. The p. 16 constrained first-occasion variance `trait + −q / (2 a) + (B / a)² v` is not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`). The lagged covariance of that constrained process is `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T19:13Z). Trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`. Contemporaneous `T0VAR` is not that lagged map. Decaying the constrained total as if it were all state is not that lagged map. Equation 5 of that lagged covariance is `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ`. `Θ` does not enter. Contemporaneous `Var(y_0)` is not that lagged observed covariance. The lagged latent covariance is not that observed covariance. The later-occasion variance of that constrained process is `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T23:12Z). Trait and `addedTIPREDVAR` do not enter `Q_Δt`. Under stationarity that composition equals contemporaneous `T0VAR`. Evolving the constrained total as if it were all state is not that later map. The lagged covariance omits `Q_Δt` and is not that later map. `Q_Δt` is not that later map. Equation 5 of that later-occasion variance is `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ`. The lagged observed covariance omits `Q_Δt` and `θ`. `MANIFESTVAR` is not `Var(y_t)`. The later-occasion latent variance is not `Var(y_t)`. The later-occasion variance of §4.3 predetermined `T0VAR` is `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T05:12Z). Trait and `addedTIPREDVAR` do not enter `Q_Δt`. Free `T0VAR` `p_0` is not that later map. Setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map. Stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free. Evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map. As `Δt → ∞` with stable `a < 0` the composition approaches contemporaneous stationary `T0VAR`. As `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`. Nonzero diffusion with `a ≥ 0` is a growing process and is kept. Equation 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ`. `MANIFESTVAR` is not `Var(y_t)`. The predetermined later-occasion latent variance is not `Var(y_t)`. Stationary later observed variance is not that observed variance when `p_0` is free. The lagged covariance of §4.3 predetermined `T0VAR` is `trait + e^{a Δt} p_0 + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T09:04Z). Trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`. Free `T0VAR` `p_0` is not that lagged map. Setting `p_0 = −q / (2 a)` recovers the stationary lagged map. Stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free. Evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map. Later-occasion variance includes `Q_Δt` and is not that lagged map. As `Δt → ∞` with stable `a < 0` the state term vanishes. As `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`. Equation 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ`. `MANIFESTVAR` does not enter. The predetermined lagged latent covariance is not that observed covariance. Predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance. Stationary lagged observed covariance is not that observed covariance when `p_0` is free. The predetermined first-occasion variance of §4.3 predetermined `T0VAR` is `trait + p_0 + (B / a)² v`. Free `p_0` is not that map. Stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free. Lagged covariance decays the state and is not that map. Later-occasion variance includes `Q_Δt` and is not that map. Equation 5 of that predetermined first-occasion variance is `λ²(trait + p_0 + (B / a)² v) + θ + ψ`. `MANIFESTVAR` is not that first-occasion observed variance. The predetermined first-occasion latent variance is not that observed variance. Stationary first-occasion observed variance is not that observed variance when `p_0` is free. Predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance. Later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z). First-occasion lagged omits `e^{a s} Q_u`. Later-occasion variance does not lag. Stationary lagged uses `−q / (2 a)`. Decaying the later total is not that map. Equation 5 of that later-start lagged covariance is `λ²` of it plus `ψ`. Independent `ε_t` does not enter. First-occasion lagged observed omits `e^{a s} Q_u`. Predetermined later observed variance includes `Q_u` and `θ` and is not that later-start lagged observed covariance. Later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z). Later-occasion variance at `u` omits `Q_s`. Later-start lagged covariance omits `Q_s`. Stationary later uses `−q / (2 a)`. Evolving the later total as if it were all state is not that map. Ignoring `startoffset` omits `e^{2 a s} Q_u`. Equation 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`. `MANIFESTVAR` is not that observed variance. Page 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; footnote 4; §7.1; JSS PDF re-opened 2026-08-23T11:40Z). Unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`. The §7.1 trait-plus-state autocorrelation `(trait + e^{a Δt} p + added) / (trait + p + added)` uses `TRAITVAR` and is not `discreteDRIFTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:06Z). Unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`. The continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`. `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`. `TRAITVAR` is not the standardisation variance. Page 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z). Unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`. The discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`. `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`. `TRAITVAR` is not the standardisation variance. Page 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z). Unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`. The discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`. `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z). Unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`. The finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`. `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z). Unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`. The asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`. The finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`. `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Page 16 / Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z). The affected variance is free first-occasion `T0VAR`, not `asymDIFFUSION`. Unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`. `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`. `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`. `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. 2017-era `addedT0TIPREDVAR` is `t0_b² v` after a first-occasion time-independent predictor (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z). Form `t0_b` first, then square, then multiply by `v`. A zero coefficient or zero predictor variance is exactly zero. Free `T0TIPREDEFFECT` does not require `a < 0`. `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map. `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance. Free `T0VAR` is not this extra TI variance. `TRAITVAR` is not this extra TI variance. Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z). Form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`. A zero loading or zero extra is exactly zero. `t0_b² v` is the latent extra, not the observed extra. `λ² p_0 + θ` is first-occasion observed variance, not this extra. `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra. `MANIFESTVAR` `θ` is not this extra. Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v` (Driver et al., 2017, Eq. 5, p. 5; Table 2, p. 12; §7.2, pp. 20–21; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:23Z). Form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`. A zero loading or zero extra is exactly zero. Lasting asymptotic extra requires `a < 0`. `(B / a)² v` is the latent extra, not the observed extra. `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra. `λ² p + θ` is stationary observed variance, not this extra. `MANIFESTVAR` `θ` is not this extra. Page 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance. Unstandardised `M` is not `TDPREDEFFECTstd`. `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`. intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`. `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`. Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance. Unstandardised `t0_m` is not `T0TDPREDEFFECTstd`. `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`. `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`. `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`. Free `T0VAR` does not require `a < 0`. Page 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; the default ridge is 0). Unstandardised `T0VAR` is not `T0VARstd`. `T0TDPREDEFFECTstd` is not `T0VARstd`. `addedT0TIPREDVAR` is not `T0VARstd`. Page 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend). Unstandardised `TRAITVAR` is not `TRAITVARstd`. `T0VARstd` is not `TRAITVARstd` even when both equal 1. `addedT0TIPREDVAR` is not `TRAITVARstd`. Page 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0). Unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`. `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1. `MANIFESTVAR` is not `MANIFESTTRAITVARstd`. Page 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug). Unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`. `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1. Equation 5 `Var(y)` is not `MANIFESTVARstd`. Page 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`). Unstandardised `TIPREDVAR` is not `TIPREDVARstd`. `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1. Section 7.2 `addedTIPREDVAR` is not `TIPREDVARstd`. Page 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`). Unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`. `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1. `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`. Page 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`. Unstandardised `discreteCINT` is not `discreteCINTstd`. `κ / √p` is not `discreteCINTstd`. `(-κ / a) / √p` is not `discreteCINTstd`. `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`. Unstandardised `asymCINT` is not `asymCINTstd`. `κ / √p` is not `asymCINTstd`. `discreteCINTstd` is not `asymCINTstd`. `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`. Unstandardised `T0MEANS` is not `T0MEANSstd`. `T0VARstd` is not `T0MEANSstd`. `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`. Page 16 `MANIFESTMEANSstd` is `τ / √θ` after strictly positive `MANIFESTVAR`. Unstandardised `MANIFESTMEANS` is not `MANIFESTMEANSstd`. `MANIFESTVARstd` is not `MANIFESTMEANSstd`. `τ / √(λ² Var(η) + θ)` is not `MANIFESTMEANSstd`. Page 16 `CINTstd` is `κ / √p` after strictly positive `asymDIFFUSION`. Unstandardised `CINT` is not `CINTstd`. `asymCINTstd` is not `CINTstd`. `discreteCINTstd` is not `CINTstd`. `κ / √(trait + p + added)` is not `CINTstd`. Evolving from that stationary start with `CINT` and `TIPREDEFFECT` stays at the stationary mean. Equation 1 is the latent SDE, not the measurement model. Form `(λ p) λ` then add `θ`, then add `ψ`. `MANIFESTVAR` is `Θ`, not `Var(y)`. `MANIFESTTRAITVAR` is `Ψ_τ`, not `Θ`. `TRAITVAR` is latent and scaled by `λ²`. `Var(η)` is not `Var(y)`. -- Separate cluster means before within-unit lag. CWC plus an event-time lag is not DSEM. Subtracting the person-specific mean from a raw autoregressive series does not isolate the lagged within-person effect (Curran & Bauer, 2011, pp. 607–608); already-centered residuals with irregular event intervals use the exact scalar map. +- Separate cluster means before within-unit lag. CWC plus an event-time lag is not DSEM. Subtracting the person-specific mean from a raw autoregressive series does not isolate the lagged within-person effect (Curran & Bauer, 2011, pp. 583–619; PMC3059070 XML opened 2026-09-02; Eq. 36). `longitudinal_core` owns CWC-then-pairwise-mean residual log-rate on typed `EventTimeInterval`; already-centered residuals recover the Driver et al. (2017, Eq. 3) inverse, including `ln(0.5)`. CWC log-rate is not raw-process drift. - Do not treat the CWC cluster-mean coefficient as the between-cluster effect. It is the contextual effect `between − within` (Enders & Tofighi, 2007, Table 2, pp. 124–127). - Never use future-available evidence in historical model fits. - Do not blanket-mask PII when identity/role/linkage is scientifically required. Follow the purpose-bound separation, opaque-ID, encryption, retention, and audit contract in `docs/PRIVACY_DATA_GOVERNANCE.md`. diff --git a/crates/longitudinal_core/Cargo.toml b/crates/longitudinal_core/Cargo.toml index 19b9635bc..b50d6dfe0 100644 --- a/crates/longitudinal_core/Cargo.toml +++ b/crates/longitudinal_core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "longitudinal_core" -description = "Longitudinal modeling gates, within/between decomposition, temporal association standardization, and recovery metrics." +description = "Longitudinal modeling gates, within/between decomposition, CWC-then-irregular residual log-rate, temporal association standardization, and recovery metrics." version.workspace = true edition.workspace = true rust-version.workspace = true diff --git a/crates/longitudinal_core/src/error.rs b/crates/longitudinal_core/src/error.rs index 5d597969a..5f52ba837 100644 --- a/crates/longitudinal_core/src/error.rs +++ b/crates/longitudinal_core/src/error.rs @@ -42,6 +42,8 @@ pub enum LongitudinalError { UnstandardisedDiffusionIsNotStandardisedDiffusion, /// Diffusion was scaled by total variance instead of relevant within variance. TotalVarianceScaledDiffusionIsNotStandardisedDiffusion, + /// Cluster-mean-centered residual log-rate was treated as raw-process AR drift. + CwcResidualLogRateIsNotRawProcessDrift, } impl fmt::Display for LongitudinalError { @@ -89,6 +91,9 @@ impl fmt::Display for LongitudinalError { Self::TotalVarianceScaledDiffusionIsNotStandardisedDiffusion => { "total-variance-scaled diffusion is not relevant-variance-standardised diffusion" } + Self::CwcResidualLogRateIsNotRawProcessDrift => { + "cluster-mean-centered residual log-rate is not the raw-process autoregressive drift" + } }; formatter.write_str(message) } @@ -175,6 +180,10 @@ mod tests { LongitudinalError::TotalVarianceScaledDiffusionIsNotStandardisedDiffusion, "total-variance-scaled diffusion is not relevant-variance-standardised diffusion", ), + ( + LongitudinalError::CwcResidualLogRateIsNotRawProcessDrift, + "cluster-mean-centered residual log-rate is not the raw-process autoregressive drift", + ), ] { assert_eq!(error.to_string(), message); } diff --git a/crates/longitudinal_core/src/irregular_residual.rs b/crates/longitudinal_core/src/irregular_residual.rs new file mode 100644 index 000000000..96ac7229f --- /dev/null +++ b/crates/longitudinal_core/src/irregular_residual.rs @@ -0,0 +1,781 @@ +//! CWC-then-irregular residual log-rate on substantive event time. +//! +//! Unique evidence folded from Draft #327. Temporal composition belongs here, +//! not in a generic psychometric kernel. This is not DSEM, not Newton LS, and +//! not raw-process autoregressive drift. + +use std::collections::BTreeMap; + +use crate::{EventTimeInterval, LongitudinalError}; + +/// One unit's score at one event-time occasion. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct EventTimedObservation { + unit_index: u32, + event_time: f64, + score: f64, +} + +impl EventTimedObservation { + /// Construct an event-timed observation. + /// + /// The constructor stores the fields as given. Admission of finite scores, + /// strictly positive consecutive intervals, and at least two units happens + /// in [`center_within_unit_event_lags`]. + #[must_use] + pub const fn new(unit_index: u32, event_time: f64, score: f64) -> Self { + Self { + unit_index, + event_time, + score, + } + } + + /// Return the unit index. + #[must_use] + pub const fn unit_index(self) -> u32 { + self.unit_index + } + + /// Return the event time. + #[must_use] + pub const fn event_time(self) -> f64 { + self.event_time + } + + /// Return the observed score. + #[must_use] + pub const fn score(self) -> f64 { + self.score + } +} + +/// One already-formed lagged within residual pair on event time. +/// +/// The interval is admitted event time. Residuals are stored as given; the +/// recover functions decide whether a pair is an admissible log-rate input. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct LaggedWithinResidual { + earlier_residual: f64, + later_residual: f64, + event_interval: EventTimeInterval, +} + +impl LaggedWithinResidual { + /// Construct a lagged within-residual pair on an admitted event interval. + #[must_use] + pub const fn new( + earlier_residual: f64, + later_residual: f64, + event_interval: EventTimeInterval, + ) -> Self { + Self { + earlier_residual, + later_residual, + event_interval, + } + } + + /// Return the earlier within residual. + #[must_use] + pub const fn earlier_residual(self) -> f64 { + self.earlier_residual + } + + /// Return the later within residual. + #[must_use] + pub const fn later_residual(self) -> f64 { + self.later_residual + } + + /// Return the admitted event-time interval. + #[must_use] + pub const fn event_interval(self) -> EventTimeInterval { + self.event_interval + } +} + +/// Cluster-mean-center consecutive event-time lags inside each unit. +/// +/// Stable between-unit means are removed first (CWC). Consecutive within-unit +/// residuals then become [`LaggedWithinResidual`] pairs on possibly irregular +/// event intervals. Singleton units are skipped. Curran and Bauer (2011, +/// pp. 583–619; PMC3059070 XML opened 2026-09-02; Eq. 36) show that +/// person-mean subtraction of a time-varying covariate related to time is +/// biased for the within-person effect. The returned pairs are therefore not a +/// license to recover raw-process drift `a`. +/// +/// # Errors +/// +/// Returns [`LongitudinalError::InvalidObservationPayload`] for empty, +/// singleton-only, fewer-than-two-unit, or non-finite rows, including +/// overflowed unit means and overflowing CWC residuals after a finite mean, +/// and [`LongitudinalError::NonPositiveEventInterval`] when any consecutive +/// event interval is not strictly positive. +pub fn center_within_unit_event_lags( + rows: &[EventTimedObservation], +) -> Result, LongitudinalError> { + if rows.len() < 2 { + return Err(LongitudinalError::InvalidObservationPayload); + } + let mut groups: BTreeMap> = BTreeMap::new(); + for &row in rows { + if !row.event_time().is_finite() || !row.score().is_finite() { + return Err(LongitudinalError::InvalidObservationPayload); + } + groups.entry(row.unit_index()).or_default().push(row); + } + if groups.len() < 2 { + return Err(LongitudinalError::InvalidObservationPayload); + } + let mut pairs = Vec::new(); + for occasions in groups.values_mut() { + if occasions.len() < 2 { + continue; + } + let count = occasions.len() as f64; + let mut total = 0.0_f64; + for row in occasions.iter() { + total += row.score(); + } + let mean = total / count; + if !mean.is_finite() { + return Err(LongitudinalError::InvalidObservationPayload); + } + occasions.sort_by(|left, right| left.event_time().total_cmp(&right.event_time())); + for window in occasions.windows(2) { + let earlier_residual = window[0].score() - mean; + let later_residual = window[1].score() - mean; + if !earlier_residual.is_finite() || !later_residual.is_finite() { + return Err(LongitudinalError::InvalidObservationPayload); + } + let event_delta = window[1].event_time() - window[0].event_time(); + let event_interval = EventTimeInterval::new(event_delta)?; + pairs.push(LaggedWithinResidual::new( + earlier_residual, + later_residual, + event_interval, + )); + } + } + if pairs.is_empty() { + return Err(LongitudinalError::InvalidObservationPayload); + } + Ok(pairs) +} + +/// Pairwise-mean exact log-rate after CWC on irregular event intervals. +/// +/// This is [`center_within_unit_event_lags`] then the pairwise mean of the +/// Driver, Oud, and Voelkle (2017, Eq. 3) scalar inverse +/// `a = ln(|later| / |earlier|) / Δt` on nonzero same-sign residuals. When +/// `|later| / |earlier|` is finite and positive the finite ratio logarithm is +/// used so near-equal large residuals do not collapse to zero. Overflowed or +/// underflowed ratios fall back to `ln|later| − ln|earlier|`. Opposite-sign +/// and zero residuals have no real logarithm and are skipped. The pairwise +/// mean is incremental so two finite rates whose raw sum overflows stay +/// representable. An empty admissible set fails closed. This is not Newton LS +/// and does not recover raw-process drift from CWC of a raw AR path (Curran +/// & Bauer, 2011, pp. 583–619; Eq. 36). +/// +/// # Errors +/// +/// Propagates centering errors from [`center_within_unit_event_lags`]. A +/// non-finite log-rate or an empty admissible list is +/// [`LongitudinalError::InvalidTemporalTransformInput`]. +pub fn recover_within_unit_irregular_residual_log_rate( + rows: &[EventTimedObservation], +) -> Result { + let lagged = center_within_unit_event_lags(rows)?; + pairwise_same_sign_log_rate(&lagged) +} + +/// Mean exact scalar log-rate on already-centered residuals. +/// +/// Each pair is `a = ln(later / earlier) / Δt` (Driver et al., 2017, Eq. 3 +/// inverse). The function does **not** center again. The signed residual ratio +/// must be strictly positive. This is the known-truth path that recovers +/// `ln(0.5)` from already-centered pairs `(1, 0.5)` over unit event time. +/// +/// # Errors +/// +/// Returns [`LongitudinalError::InvalidObservationPayload`] for an empty +/// series or non-finite residuals, and +/// [`LongitudinalError::InvalidTemporalTransformInput`] when a residual ratio +/// is not strictly positive or the log-rate is not representable. +pub fn recover_centered_irregular_residual_log_rate( + pairs: &[LaggedWithinResidual], +) -> Result { + if pairs.is_empty() { + return Err(LongitudinalError::InvalidObservationPayload); + } + let mut mean = 0.0_f64; + let mut count = 0.0_f64; + for pair in pairs { + if !pair.earlier_residual().is_finite() || !pair.later_residual().is_finite() { + return Err(LongitudinalError::InvalidObservationPayload); + } + if pair.earlier_residual() == 0.0 { + return Err(LongitudinalError::InvalidTemporalTransformInput); + } + let ratio = pair.later_residual() / pair.earlier_residual(); + if !ratio.is_finite() || ratio <= 0.0 { + return Err(LongitudinalError::InvalidTemporalTransformInput); + } + let rate = require_finite(ratio.ln() / pair.event_interval().as_f64())?; + (mean, count) = overflow_safe_running_mean(mean, count, rate); + } + require_finite(mean) +} + +/// Refuse treating a CWC residual log-rate as raw-process AR drift. +/// +/// Always fails closed. Curran and Bauer (2011, pp. 583–619; PMC3059070 XML +/// opened 2026-09-02) show that person-mean centering of a time-varying +/// covariate related to time is biased for the within-person effect. Licensed +/// detrend is the person-specific OLS residual of the covariate on time +/// (Eq. 36). Use [`recover_centered_irregular_residual_log_rate`] on +/// already-centered residuals for the raw-process estimand. +/// +/// # Errors +/// +/// Always returns [`LongitudinalError::CwcResidualLogRateIsNotRawProcessDrift`]. +pub fn refuse_cwc_residual_log_rate_as_raw_process_drift( + cwc_log_rate: f64, + raw_process_drift: f64, +) -> Result { + let _ = (cwc_log_rate, raw_process_drift); + Err(LongitudinalError::CwcResidualLogRateIsNotRawProcessDrift) +} + +fn pairwise_same_sign_log_rate(lagged: &[LaggedWithinResidual]) -> Result { + let mut mean = 0.0_f64; + let mut count = 0.0_f64; + for pair in lagged { + if !same_sign_nonzero(pair.earlier_residual(), pair.later_residual()) { + continue; + } + let rate = driver_same_sign_log_rate( + pair.earlier_residual(), + pair.later_residual(), + pair.event_interval(), + )?; + (mean, count) = overflow_safe_running_mean(mean, count, rate); + } + if count <= 0.0 { + return Err(LongitudinalError::InvalidTemporalTransformInput); + } + require_finite(mean) +} + +/// Incremental mean that stays finite when the raw sum of finite values overflows. +/// +/// Form `mean * (n − 1) / n + value / n` so two rates near `1.45e308` keep a +/// representable pairwise mean. Mixed signs scale each term by `1 / n` before +/// adding, so `MAX + (−MAX)` is not formed. Caller supplies finite values. +pub(crate) fn overflow_safe_running_mean(mean: f64, count: f64, value: f64) -> (f64, f64) { + let next_count = count + 1.0; + if count <= 0.0 { + (value, next_count) + } else { + let inv = 1.0 / next_count; + (mean.mul_add(1.0 - inv, value * inv), next_count) + } +} + +/// Nonzero residuals of equal sign admit a real Driver Eq. 3 logarithm. +pub(crate) fn same_sign_nonzero(earlier: f64, later: f64) -> bool { + earlier != 0.0 && later != 0.0 && earlier.is_sign_positive() == later.is_sign_positive() +} + +/// Driver et al. (2017, Eq. 3) inverse `a = ln(|later| / |earlier|) / Δt`. +/// +/// Caller already established same-sign nonzero residuals and an admitted +/// event interval. Prefer the finite ratio logarithm so near-equal large +/// residuals keep a nonzero rate. Fall back to `ln|later| − ln|earlier|` +/// only when that ratio overflows or underflows. +pub(crate) fn driver_same_sign_log_rate( + earlier: f64, + later: f64, + event_interval: EventTimeInterval, +) -> Result { + let ratio = later.abs() / earlier.abs(); + let log_ratio = if ratio.is_finite() && ratio > 0.0 { + ratio.ln() + } else { + later.abs().ln() - earlier.abs().ln() + }; + require_finite(log_ratio / event_interval.as_f64()) +} + +fn require_finite(value: f64) -> Result { + if value.is_finite() { + Ok(value) + } else { + Err(LongitudinalError::InvalidTemporalTransformInput) + } +} + +#[cfg(test)] +mod tests { + use super::{ + EventTimedObservation, LaggedWithinResidual, center_within_unit_event_lags, + driver_same_sign_log_rate, overflow_safe_running_mean, + recover_centered_irregular_residual_log_rate, + recover_within_unit_irregular_residual_log_rate, + refuse_cwc_residual_log_rate_as_raw_process_drift, same_sign_nonzero, + }; + use crate::{EventTimeInterval, LongitudinalError}; + + fn timed(unit: u32, event_time: f64, score: f64) -> EventTimedObservation { + EventTimedObservation::new(unit, event_time, score) + } + + fn unit_interval() -> EventTimeInterval { + EventTimeInterval::new(1.0).expect("unit interval") + } + + fn lagged(earlier: f64, later: f64, delta: f64) -> LaggedWithinResidual { + LaggedWithinResidual::new( + earlier, + later, + EventTimeInterval::new(delta).expect("test interval"), + ) + } + + fn decaying_scores(drift: f64) -> [EventTimedObservation; 8] { + [ + timed(0, 0.0, 10.0 + 1.0), + timed(0, 1.0, 10.0 + drift.exp()), + timed(0, 2.0, 10.0 + (drift * 2.0).exp()), + timed(0, 3.0, 10.0 + (drift * 3.0).exp()), + timed(1, 0.0, 4.0 + 1.0), + timed(1, 1.0, 4.0 + drift.exp()), + timed(1, 2.0, 4.0 + (drift * 2.0).exp()), + timed(1, 3.0, 4.0 + (drift * 3.0).exp()), + ] + } + + #[test] + fn already_centered_irregular_pairs_recover_true_log_rate() { + let drift = -0.35_f64; + let pairs = [ + lagged(1.4, 1.4 * (drift * 0.4).exp(), 0.4), + lagged(0.9, 0.9 * (drift * 1.6).exp(), 1.6), + lagged(-0.7, -0.7 * (drift * 2.2).exp(), 2.2), + ]; + let recovered = recover_centered_irregular_residual_log_rate(&pairs).expect("centered"); + assert!((recovered - drift).abs() < 1e-12); + let half = recover_centered_irregular_residual_log_rate(&[lagged(1.0, 0.5, 1.0)]) + .expect("ln(0.5)"); + assert!((half - 0.5_f64.ln()).abs() < 1e-15); + } + + #[test] + fn cwc_of_raw_ar_does_not_recover_process_drift() { + let drift = -0.3_f64; + let rows = decaying_scores(drift); + let extracted = center_within_unit_event_lags(&rows).expect("cwc pairs"); + let composed = + recover_within_unit_irregular_residual_log_rate(&rows).expect("cwc pairwise"); + assert!((composed - drift).abs() > 1e-6); + assert_eq!( + refuse_cwc_residual_log_rate_as_raw_process_drift(composed, drift), + Err(LongitudinalError::CwcResidualLogRateIsNotRawProcessDrift) + ); + let admissible: Vec = extracted + .iter() + .copied() + .filter(|pair| same_sign_nonzero(pair.earlier_residual(), pair.later_residual())) + .collect(); + let from_pairs = + recover_centered_irregular_residual_log_rate(&admissible).expect("admissible"); + assert!((composed - from_pairs).abs() < 1e-15); + assert_eq!( + extracted[0].event_interval().as_f64().to_bits(), + 1.0_f64.to_bits() + ); + assert_eq!(timed(9, 8.0, 0.0).unit_index(), 9); + assert_eq!(timed(9, 8.0, 0.0).event_time().to_bits(), 8.0_f64.to_bits()); + assert_eq!(timed(9, 8.0, 0.0).score().to_bits(), 0.0_f64.to_bits()); + assert_eq!( + lagged(1.0, 0.5, 1.0).earlier_residual().to_bits(), + 1.0_f64.to_bits() + ); + assert_eq!( + lagged(1.0, 0.5, 1.0).later_residual().to_bits(), + 0.5_f64.to_bits() + ); + } + + #[test] + fn two_occasion_cwc_is_empty_admissible() { + assert_eq!( + recover_within_unit_irregular_residual_log_rate(&[ + timed(0, 0.0, 1.0), + timed(0, 1.0, 0.5), + timed(1, 0.0, 2.0), + timed(1, 1.0, 1.0), + ]), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); + } + + #[test] + fn arithmetic_progression_has_zero_residual_and_fails_closed() { + assert_eq!( + recover_within_unit_irregular_residual_log_rate(&[ + timed(0, 0.0, 1.0), + timed(0, 1.0, 2.0), + timed(0, 2.0, 3.0), + timed(1, 0.0, 4.0), + timed(1, 1.0, 5.0), + timed(1, 2.0, 6.0), + ]), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); + } + + #[test] + fn same_sign_nonzero_rejects_zero_and_opposite_signs() { + assert!(!same_sign_nonzero(0.0, 1.0)); + assert!(!same_sign_nonzero(1.0, 0.0)); + assert!(!same_sign_nonzero(-1.0, 2.0)); + assert!(same_sign_nonzero(1e-160, 1e160)); + assert!(same_sign_nonzero(-0.4, -1.2)); + } + + #[test] + fn driver_same_sign_prefers_finite_ratio_ln_for_near_equal_large_residuals() { + let earlier = 1e20_f64; + let later = earlier * (-1e-12_f64).exp(); + let ratio = later.abs() / earlier.abs(); + assert!(ratio.is_finite() && ratio > 0.0); + let from_ratio = ratio.ln(); + let rate = driver_same_sign_log_rate(earlier, later, unit_interval()).expect("near-equal"); + assert_eq!(rate.to_bits(), from_ratio.to_bits()); + let overflow_rate = driver_same_sign_log_rate(f64::from_bits(1), f64::MAX, unit_interval()) + .expect("overflow arm"); + let overflow_logs = f64::MAX.ln() - f64::from_bits(1).ln(); + assert_eq!(overflow_rate.to_bits(), overflow_logs.to_bits()); + let underflow_rate = + driver_same_sign_log_rate(1e300_f64, 1e-300_f64, unit_interval()).expect("underflow"); + let underflow_logs = (1e-300_f64).ln() - (1e300_f64).ln(); + assert_eq!(underflow_rate.to_bits(), underflow_logs.to_bits()); + } + + #[test] + fn overflow_safe_running_mean_keeps_finite_pairwise_mean() { + let large = 1.45e308_f64; + assert!(!(large + large).is_finite()); + let (first, n1) = overflow_safe_running_mean(0.0, 0.0, large); + assert_eq!(first.to_bits(), large.to_bits()); + assert_eq!(n1.to_bits(), 1.0_f64.to_bits()); + let (same_sign, n2) = overflow_safe_running_mean(first, n1, large); + assert!(same_sign.is_finite()); + assert!((same_sign - large).abs() < 1.0); + assert_eq!(n2.to_bits(), 2.0_f64.to_bits()); + let (mixed, n_mixed) = overflow_safe_running_mean(first, n1, -large); + assert!(mixed.abs() < 1.0); + assert_eq!(n_mixed.to_bits(), 2.0_f64.to_bits()); + } + + fn overflowing_cwc_rate_rows(unit: u32, growing: bool) -> [EventTimedObservation; 3] { + let delta = 1e-305_f64; + let (first, second) = if growing { + (f64::from_bits(1), f64::MAX) + } else { + (f64::MAX, f64::from_bits(1)) + }; + [ + timed(unit, 0.0, first), + timed(unit, delta, second), + timed(unit, 2.0 * delta, -f64::MAX), + ] + } + + #[test] + fn cwc_pairwise_keeps_overflowed_rate_sum_via_incremental_mean() { + let mut rows = overflowing_cwc_rate_rows(1, true).to_vec(); + rows.extend(overflowing_cwc_rate_rows(2, true)); + let recovered = + recover_within_unit_irregular_residual_log_rate(&rows).expect("incremental"); + assert!(recovered.is_finite()); + let extracted = center_within_unit_event_lags(&rows).expect("extract"); + let mut rates = Vec::new(); + for pair in extracted { + if !same_sign_nonzero(pair.earlier_residual(), pair.later_residual()) { + continue; + } + rates.push( + driver_same_sign_log_rate( + pair.earlier_residual(), + pair.later_residual(), + pair.event_interval(), + ) + .expect("pair rate"), + ); + } + assert_eq!(rates.len(), 2); + assert!(!(rates[0] + rates[1]).is_finite()); + let expected = overflow_safe_running_mean( + overflow_safe_running_mean(0.0, 0.0, rates[0]).0, + 1.0, + rates[1], + ) + .0; + assert!((recovered - expected).abs() <= expected.abs() * 1e-15); + let mut mixed = overflowing_cwc_rate_rows(1, true).to_vec(); + mixed.extend(overflowing_cwc_rate_rows(2, false)); + let mixed_mean = + recover_within_unit_irregular_residual_log_rate(&mixed).expect("mixed-sign"); + assert!(mixed_mean.abs() < recovered.abs() * 1e-12); + } + + #[test] + fn cwc_pairwise_tiny_interval_with_huge_log_ratio_fails_closed() { + assert_eq!( + recover_within_unit_irregular_residual_log_rate(&[ + timed(1, 0.0, 1e-160), + timed(1, f64::from_bits(1), 1e160), + timed(1, 1.0, -1e160), + timed(2, 0.0, 1.0), + timed(2, 1.0, 0.5), + ]), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); + } + + #[test] + fn cwc_pairwise_skips_zero_residuals_and_keeps_same_sign_pairs() { + let recovered = recover_within_unit_irregular_residual_log_rate(&[ + timed(1, 0.0, 7.0), + timed(1, 1.0, 5.0), + timed(1, 2.0, 4.0), + timed(1, 3.0, 4.0), + timed(2, 0.0, -1.2), + timed(2, 1.0, -0.4), + timed(2, 2.0, -0.8), + ]) + .expect("skip zeros keep same-sign"); + assert!(recovered.is_finite()); + let extracted = center_within_unit_event_lags(&[ + timed(1, 0.0, 7.0), + timed(1, 1.0, 5.0), + timed(1, 2.0, 4.0), + timed(1, 3.0, 4.0), + timed(2, 0.0, -1.2), + timed(2, 1.0, -0.4), + timed(2, 2.0, -0.8), + ]) + .expect("extract"); + assert!( + extracted + .iter() + .any(|pair| pair.later_residual().to_bits() == 0.0_f64.to_bits()) + ); + assert!( + extracted + .iter() + .any(|pair| pair.earlier_residual().to_bits() == 0.0_f64.to_bits()) + ); + assert!(extracted.iter().any(|pair| same_sign_nonzero( + pair.earlier_residual(), + pair.later_residual() + ) && !pair.earlier_residual().is_sign_positive())); + } + + #[test] + fn cwc_observation_payload_paths_fail_closed() { + assert_eq!( + recover_within_unit_irregular_residual_log_rate(&[]), + Err(LongitudinalError::InvalidObservationPayload) + ); + assert_eq!( + recover_within_unit_irregular_residual_log_rate(&[timed(1, 0.0, 1.0)]), + Err(LongitudinalError::InvalidObservationPayload) + ); + assert_eq!( + recover_within_unit_irregular_residual_log_rate(&[ + timed(1, 0.0, 1.0), + timed(1, 1.0, 0.5), + ]), + Err(LongitudinalError::InvalidObservationPayload) + ); + assert_eq!( + recover_within_unit_irregular_residual_log_rate(&[ + timed(1, 0.0, 1.0), + timed(2, 1.0, 0.5), + ]), + Err(LongitudinalError::InvalidObservationPayload) + ); + assert_eq!( + center_within_unit_event_lags(&[ + timed(1, 0.0, 1.0), + timed(1, 0.0, 1.2), + timed(2, 0.0, 2.0), + timed(2, 1.0, 1.5), + ]), + Err(LongitudinalError::NonPositiveEventInterval) + ); + assert_eq!( + recover_within_unit_irregular_residual_log_rate(&[ + timed(1, 0.0, f64::NAN), + timed(1, 1.0, 1.0), + timed(2, 0.0, 1.0), + timed(2, 1.0, 0.5), + ]), + Err(LongitudinalError::InvalidObservationPayload) + ); + assert_eq!( + recover_within_unit_irregular_residual_log_rate(&[ + timed(1, 0.0, f64::INFINITY), + timed(1, 1.0, 1.0), + timed(2, 0.0, 1.0), + timed(2, 1.0, 0.5), + ]), + Err(LongitudinalError::InvalidObservationPayload) + ); + assert_eq!( + recover_within_unit_irregular_residual_log_rate(&[ + timed(1, 0.0, f64::MAX), + timed(1, 1.0, f64::MAX), + timed(2, 0.0, 1.0), + timed(2, 1.0, 0.5), + ]), + Err(LongitudinalError::InvalidObservationPayload) + ); + assert_eq!( + center_within_unit_event_lags(&[ + timed(1, f64::MAX, 1.0), + timed(1, -f64::MAX, 0.5), + timed(2, 0.0, 1.0), + timed(2, 1.0, 0.5), + ]), + Err(LongitudinalError::NonPositiveEventInterval) + ); + } + + #[test] + fn already_centered_and_curran_refusal_paths_fail_closed() { + assert_eq!( + recover_centered_irregular_residual_log_rate(&[]), + Err(LongitudinalError::InvalidObservationPayload) + ); + assert_eq!( + recover_centered_irregular_residual_log_rate(&[lagged(f64::NAN, 0.5, 1.0)]), + Err(LongitudinalError::InvalidObservationPayload) + ); + assert_eq!( + recover_centered_irregular_residual_log_rate(&[lagged(1.0, f64::NAN, 1.0)]), + Err(LongitudinalError::InvalidObservationPayload) + ); + assert_eq!( + recover_centered_irregular_residual_log_rate(&[lagged(0.0, 0.5, 1.0)]), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); + assert_eq!( + recover_centered_irregular_residual_log_rate(&[lagged(1.0, -0.5, 1.0)]), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); + assert_eq!( + recover_centered_irregular_residual_log_rate(&[lagged( + f64::from_bits(1), + f64::MAX, + 1.0 + )]), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); + assert_eq!( + recover_centered_irregular_residual_log_rate(&[lagged( + 1e-160, + 1e160, + f64::from_bits(1) + )]), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); + assert_eq!( + refuse_cwc_residual_log_rate_as_raw_process_drift(f64::NAN, f64::INFINITY), + Err(LongitudinalError::CwcResidualLogRateIsNotRawProcessDrift) + ); + } + + #[test] + fn cwc_orders_unsorted_event_times_before_lag_pairs() { + let pairs = center_within_unit_event_lags(&[ + timed(1, 2.0, 0.4), + timed(2, 3.0, -0.8), + timed(1, 0.5, 1.6), + timed(2, 1.0, 0.2), + ]) + .expect("unsorted"); + assert_eq!(pairs.len(), 2); + assert!((pairs[0].event_interval().as_f64() - 1.5).abs() < 1e-15); + assert!((pairs[1].event_interval().as_f64() - 2.0).abs() < 1e-15); + let cluster_one_mean = f64::midpoint(1.6, 0.4); + assert!((pairs[0].earlier_residual() - (1.6 - cluster_one_mean)).abs() < 1e-15); + assert!((pairs[0].later_residual() - (0.4 - cluster_one_mean)).abs() < 1e-15); + } + + #[test] + fn singleton_unit_is_skipped_and_all_singletons_fail_closed() { + let drift = -0.2_f64; + let mixed = [ + timed(1, 0.0, 10.0 + 1.0), + timed(1, 1.0, 10.0 + drift.exp()), + timed(1, 2.0, 10.0 + (drift * 2.0).exp()), + timed(1, 3.0, 10.0 + (drift * 3.0).exp()), + timed(2, 0.0, 4.0), + ]; + let recovered = recover_within_unit_irregular_residual_log_rate(&mixed).expect("skip"); + assert!(recovered.is_finite()); + assert_eq!( + recover_within_unit_irregular_residual_log_rate(&[ + timed(1, 0.0, 1.0), + timed(2, 1.0, 0.5) + ]), + Err(LongitudinalError::InvalidObservationPayload) + ); + } + + #[test] + fn overflowing_same_sign_cwc_pairs_keep_stable_log() { + let overflowed_both = recover_within_unit_irregular_residual_log_rate(&[ + timed(1, 0.0, f64::from_bits(1)), + timed(1, 1.0, f64::MAX), + timed(1, 2.0, -f64::MAX), + timed(2, 0.0, f64::from_bits(1)), + timed(2, 1.0, f64::MAX), + timed(2, 2.0, -f64::MAX), + ]) + .expect("stable log of overflowed same-sign CWC pairs"); + let overflow_rate = driver_same_sign_log_rate(f64::from_bits(1), f64::MAX, unit_interval()) + .expect("tiny/MAX"); + assert!((overflowed_both - overflow_rate).abs() < 1e-9); + } + + #[test] + fn one_sided_residual_overflow_fails_closed() { + assert_eq!( + recover_within_unit_irregular_residual_log_rate(&[ + timed(1, 0.0, -f64::MAX), + timed(1, 1.0, -f64::MAX), + timed(1, 2.0, -f64::MAX), + timed(1, 3.0, f64::MAX), + timed(2, 0.0, 1.0), + timed(2, 1.0, 0.8), + ]), + Err(LongitudinalError::InvalidObservationPayload) + ); + assert_eq!( + center_within_unit_event_lags(&[ + timed(1, 0.0, f64::MAX), + timed(1, 1.0, -f64::MAX), + timed(1, 2.0, -f64::MAX / 2.0), + timed(2, 0.0, 1.0), + timed(2, 1.0, 0.5), + ]), + Err(LongitudinalError::InvalidObservationPayload) + ); + } +} diff --git a/crates/longitudinal_core/src/lib.rs b/crates/longitudinal_core/src/lib.rs index 62df9a079..e1a838704 100644 --- a/crates/longitudinal_core/src/lib.rs +++ b/crates/longitudinal_core/src/lib.rs @@ -7,8 +7,9 @@ //! Event-time lagged associations require both marginal variances before a //! covariance can be standardized as a correlation. Event-interval response //! transforms and scalar diffusion standardisation candidates live here rather -//! than in a generic psychometric kernel. Recovery reports computed component -//! RMSE against known truth (ADR 0005). +//! than in a generic psychometric kernel. Person-mean centering of a time-related +//! series is not raw-process drift (Curran & Bauer, 2011). Recovery reports +//! computed component RMSE against known truth (ADR 0005). mod association; mod component; @@ -17,6 +18,7 @@ mod diffusion; mod discrete_drift; mod error; mod event_time; +mod irregular_residual; mod level; mod stationary; mod temporal_association; @@ -51,6 +53,18 @@ pub use discrete_drift::refuse_unstandardised_discrete_drift_as_standardised_dis pub use error::LongitudinalError; /// A finite, strictly positive interval admitted on substantive event time. pub use event_time::EventTimeInterval; +/// One unit's score at one event-time occasion. +pub use irregular_residual::EventTimedObservation; +/// One already-formed lagged within residual pair on event time. +pub use irregular_residual::LaggedWithinResidual; +/// Cluster-mean-center consecutive event-time lags inside each unit. +pub use irregular_residual::center_within_unit_event_lags; +/// Mean exact scalar log-rate on already-centered residuals. +pub use irregular_residual::recover_centered_irregular_residual_log_rate; +/// Pairwise-mean exact log-rate after CWC on irregular event intervals. +pub use irregular_residual::recover_within_unit_irregular_residual_log_rate; +/// Refuse treating a CWC residual log-rate as raw-process AR drift. +pub use irregular_residual::refuse_cwc_residual_log_rate_as_raw_process_drift; /// Established longitudinal component level. pub use level::ComponentLevel; /// Refuse to treat a between-unit component as within-unit change. diff --git a/crates/longitudinal_core/tests/cwc_irregular_residual_contract.rs b/crates/longitudinal_core/tests/cwc_irregular_residual_contract.rs new file mode 100644 index 000000000..c6a7f455e --- /dev/null +++ b/crates/longitudinal_core/tests/cwc_irregular_residual_contract.rs @@ -0,0 +1,46 @@ +//! CWC-then-irregular residual log-rate is not raw-process drift. + +use longitudinal_core::{ + EventTimedObservation, LaggedWithinResidual, LongitudinalError, center_within_unit_event_lags, + recover_centered_irregular_residual_log_rate, recover_within_unit_irregular_residual_log_rate, + refuse_cwc_residual_log_rate_as_raw_process_drift, +}; + +fn timed(unit: u32, event_time: f64, score: f64) -> EventTimedObservation { + EventTimedObservation::new(unit, event_time, score) +} + +#[test] +fn already_centered_unit_interval_recovers_ln_half() { + let pair = LaggedWithinResidual::new( + 1.0, + 0.5, + longitudinal_core::EventTimeInterval::new(1.0).expect("event time"), + ); + let recovered = recover_centered_irregular_residual_log_rate(&[pair]).expect("ln(0.5)"); + assert!((recovered - 0.5_f64.ln()).abs() < f64::EPSILON); +} + +#[test] +fn cwc_of_raw_autoregressive_path_is_not_process_drift() { + let drift = (0.5_f64).ln(); + let rows = [ + timed(0, 0.0, 8.0 + 1.0), + timed(0, 1.0, 8.0 + drift.exp()), + timed(0, 2.5, 8.0 + (drift * 2.5).exp()), + timed(1, 0.0, 3.0 + 1.0), + timed(1, 0.8, 3.0 + (drift * 0.8).exp()), + timed(1, 2.0, 3.0 + (drift * 2.0).exp()), + ]; + let composed = recover_within_unit_irregular_residual_log_rate(&rows).expect("cwc pairwise"); + assert!( + (composed - drift).abs() > 1e-4, + "Curran & Bauer: CWC of a time-related AR path must not recover raw drift" + ); + assert_eq!( + refuse_cwc_residual_log_rate_as_raw_process_drift(composed, drift), + Err(LongitudinalError::CwcResidualLogRateIsNotRawProcessDrift) + ); + let extracted = center_within_unit_event_lags(&rows).expect("pairs"); + assert!(extracted.len() >= 2); +} diff --git a/crates/longitudinal_core/tests/standardised_discrete_drift_contract.rs b/crates/longitudinal_core/tests/standardised_discrete_drift_contract.rs index 835b63e9b..07d4a60bf 100644 --- a/crates/longitudinal_core/tests/standardised_discrete_drift_contract.rs +++ b/crates/longitudinal_core/tests/standardised_discrete_drift_contract.rs @@ -1,3 +1,5 @@ +//! Scalar Driver p.16 `discreteDRIFTstd` contract on event time. + use longitudinal_core::{ EventTimeInterval, LongitudinalError, recover_event_time_standardised_discrete_drift, refuse_trait_plus_state_association_as_standardised_discrete_drift, @@ -44,12 +46,9 @@ fn known_truth_grid_has_machine_precision_rmse() { #[test] fn extreme_stable_rate_preserves_representable_stationary_result() { let delta = 1.0 / f64::MAX; - let recovered = recover_event_time_standardised_discrete_drift( - f64::MAX, - -f64::MAX, - event_time(delta), - ) - .expect("doubling the drift must not create an avoidable overflow"); + let recovered = + recover_event_time_standardised_discrete_drift(f64::MAX, -f64::MAX, event_time(delta)) + .expect("doubling the drift must not create an avoidable overflow"); let truth = (-f64::MAX * delta).exp(); assert!((recovered - truth).abs() <= f64::EPSILON); } diff --git a/docs/adr/longitudinal-modeling-ownership-addendum.md b/docs/adr/longitudinal-modeling-ownership-addendum.md index 43f394473..dc1239b57 100644 --- a/docs/adr/longitudinal-modeling-ownership-addendum.md +++ b/docs/adr/longitudinal-modeling-ownership-addendum.md @@ -15,6 +15,8 @@ For the scalar Driver, Oud, and Voelkle (2017) p. 16 `discreteDRIFTstd` special The lagged-correlation boundary similarly requires both occasion-specific marginal variances and an `EventTimeInterval`; a covariance divided only by the earlier variance is not exposed as an autocorrelation. +CWC-then-irregular residual log-rate is also Longitudinal Modeling composition. Person-mean centering of a time-related series is not raw-process drift (Curran & Bauer, 2011, pp. 583–619; PMC3059070 XML opened 2026-09-02; Eq. 36). The unique pairwise-mean-after-CWC evidence from Draft #327 is folded here with typed `EventTimeInterval` rather than grown on `psychometric_core`. Already-centered irregular pairs recover the Driver et al. (2017, Eq. 3) inverse; CWC of a raw AR path does not. + ## DDD consequences - `psychometric_core` is not the authority for temporal transforms merely because an earlier branch placed them there. @@ -24,6 +26,6 @@ The lagged-correlation boundary similarly requires both occasion-specific margin ## Verification -PR #310 is the current landing vehicle for this clarification. Its RED lineage includes an extreme stable-drift case that failed because `-2a` overflowed despite a representable stationary variance, and a typed event-time contract that could not compile before the value object existed. The repaired source avoids the unnecessary doubling overflow and moves public event-time admission behind `EventTimeInterval`. +PR #310 is the current landing vehicle for this clarification. Its RED lineage includes an extreme stable-drift case that failed because `-2a` overflowed despite a representable stationary variance, and a typed event-time contract that could not compile before the value object existed. The repaired source avoids the unnecessary doubling overflow and moves public event-time admission behind `EventTimeInterval`. The #327 fold adds CWC-then-pairwise-mean residual log-rate with Curran refusal and known-truth already-centered recovery of `ln(0.5)`. Protected-main maturity is not claimed until the exact landing head passes the live ruleset and is merged. diff --git a/docs/product/prd-v0.4-amendment-longitudinal-time-ownership.md b/docs/product/prd-v0.4-amendment-longitudinal-time-ownership.md index 328a781f3..67bc330bd 100644 --- a/docs/product/prd-v0.4-amendment-longitudinal-time-ownership.md +++ b/docs/product/prd-v0.4-amendment-longitudinal-time-ownership.md @@ -11,6 +11,8 @@ TEPP Longitudinal Modeling owns temporal/event composition for longitudinal psyc For event-time lagged association, the product contract requires lagged covariance, both occasion-specific marginal variances, and an admitted positive finite event-time interval. Nonstationary correlation must not replace the later marginal variance with the earlier one. +CWC-then-irregular residual log-rate is Longitudinal Modeling composition, not a generic psychometric kernel. Consecutive unit-mean-centered pairs keep typed event-time intervals. The pairwise mean of the Driver, Oud, and Voelkle (2017, Eq. 3) scalar inverse on nonzero same-sign residuals is not raw-process drift (Curran & Bauer, 2011, Eq. 36). Already-centered irregular pairs may recover that inverse, including `ln(0.5)`. This is not DSEM and not Newton least-squares. + For the scalar stationary Driver, Oud, and Voelkle (2017) p. 16 `discreteDRIFTstd` mapping, TEPP may expose a deterministic event-time transform only when stable drift and positive stationary within-person variance are established. Numerical equality with `exp(a Δt)` in that scalar stationary special case does not make unstandardised `discreteDRIFT` and `discreteDRIFTstd` the same estimand. Reusable static/generalized-mixed/dependence-aware psychometric arithmetic remains owned by `ContextualWisdomLab/fast-mlsirm`; TEPP consumes versioned contracts through an anti-corruption layer and does not retain duplicate production kernels. LLM execution remains `contextual-orchestrator`-owned and cannot substitute for numerical estimation or scientific acceptance. diff --git a/docs/research/cwc-irregular-residual-log-rate.md b/docs/research/cwc-irregular-residual-log-rate.md new file mode 100644 index 000000000..786b56c3f --- /dev/null +++ b/docs/research/cwc-irregular-residual-log-rate.md @@ -0,0 +1,79 @@ +# CWC-then-irregular residual log-rate + +## Domain owner + +This contract belongs to TEPP's **Longitudinal Modeling** bounded context. +Public operations whose meaning depends on substantive event time expose +`EventTimeInterval` rather than a generic clock label. Unique evidence is +folded from Draft #327 into `longitudinal_core` on landing vehicle #310. +Reusable static/generalized-mixed kernels remain fast-mlsirm-owned. + +This is not DSEM, not Newton least-squares, not ctsem estimation, and not +raw-process autoregressive drift. + +## Estimands + +1. `center_within_unit_event_lags` subtracts the unit mean (CWC) and emits + consecutive [`LaggedWithinResidual`] pairs on admitted event intervals. + Singleton units are skipped. +2. `recover_within_unit_irregular_residual_log_rate` is the pairwise mean of + the Driver, Oud, and Voelkle (2017, Eq. 3) scalar inverse + `a = ln(|later| / |earlier|) / Δt` on nonzero same-sign residuals. When + the absolute residual ratio is finite and positive the finite-ratio + logarithm is used; overflowed or underflowed ratios fall back to + `ln|later| − ln|earlier|`. The pairwise mean is incremental so two finite + rates whose raw sum overflows stay representable. +3. `recover_centered_irregular_residual_log_rate` is the already-centered + path. It does **not** re-center. The signed residual ratio must be + strictly positive. Known-truth pairs `(1, 0.5)` over unit event time + recover `ln(0.5)`. +4. `refuse_cwc_residual_log_rate_as_raw_process_drift` always fails closed. + +## Identification and admissibility + +Curran and Bauer (2011, pp. 583–619; PMC3059070 XML opened 2026-09-02) +show that person-mean centering of a time-varying covariate related to time +is biased for the within-person effect. The licensed alternative is the +person-specific OLS residual of the covariate on time (their Eq. 36). An +autoregressive series is related to time, so CWC of a raw AR path does not +recover process drift `a`. Traditional person-mean centering is the +horizontal line in their Figure 9; detrend uses the individual regression +line. + +T=2 CWC is always `r, −r` (empty admissible). T=3 arithmetic progression +has a zero residual (empty admissible). Opposite-sign and zero residuals +are skipped. An empty admissible set fails closed. + +Driver et al. (2017, Eq. 3, p. 5; JSS PDF opened 2026-09-02) write the +discrete solution `η(t) = e^{A(t−t0)} η(t0) + ⋯`. The noiseless scalar +inverse is `a = ln(later / earlier) / Δt`. Voelkle, Oud, Davidov, and +Schmidt (2012, Eq. 7) print the same exponential map; the ZORA PDF was +not re-opened this cycle (invalid cross-reference table) and is cited +only as previously opened lineage. + +No claim is made for matrix `expm`, Kalman filtering, ESEM, DSEM, or +ctsem estimation. + +## Recovery evidence + +Already-centered irregular pairs recover known `a` at machine precision, +including `ln(0.5)`. CWC of a raw AR path with a stable between-unit +offset does **not** recover that `a`. Fail-closed cases cover empty and +singleton-only rows, one unit, non-positive intervals, non-finite scores, +overflowed unit means, overflowing CWC residuals after a finite mean, +tiny intervals with huge log-ratios, and the Curran refusal. + +## Traceability + +Curran, P. J., & Bauer, D. J. (2011). The disaggregation of within-person +and between-person effects in longitudinal models of change. *Annual Review +of Psychology, 62*, 583–619. https://doi.org/10.1146/annurev.psych.093008.100356 + +Driver, C. C., Oud, J. H. L., & Voelkle, M. C. (2017). Continuous time +structural equation modeling with R package ctsem. *Journal of Statistical +Software, 77*(5), 1–35. https://doi.org/10.18637/jss.v077.i05 + +Voelkle, M. C., Oud, J. H. L., Davidov, E., & Schmidt, P. (2012). An SEM +approach to continuous time modeling of panel data: Relating +authoritarianism and anomia. *Psychological Methods, 17*(2), 176–192. +https://doi.org/10.1037/a0027543 diff --git a/docs/research/longitudinal-within-between.md b/docs/research/longitudinal-within-between.md index 48c565f91..80c80b9cb 100644 --- a/docs/research/longitudinal-within-between.md +++ b/docs/research/longitudinal-within-between.md @@ -8,7 +8,9 @@ as within-unit change. Recovered components are scored with computed RMSE against known truth. This slice does not fit DSEM, claim lagged or causal paths, or treat -irregular intervals as equally spaced. +irregular intervals as equally spaced. CWC-then-irregular residual +log-rate lives in the same crate; see +`docs/research/cwc-irregular-residual-log-rate.md`. ## Authority diff --git a/docs/research/multilevel-event-time-recovery.md b/docs/research/multilevel-event-time-recovery.md index 3701dcb4b..71c87784e 100644 --- a/docs/research/multilevel-event-time-recovery.md +++ b/docs/research/multilevel-event-time-recovery.md @@ -180,6 +180,7 @@ The Voelkle et al. (2012) ZORA accepted manuscript was re-opened 2026-08-18T21:0 - **Lagged predetermined observed covariance.** Driver et al. (2017, Eq. 5, p. 5; Eq. 3–4, pp. 4–5; Table 2, p. 12; JSS PDF re-opened 2026-08-23T09:04Z): independent `ε_t` does not enter `cov(y_t,y_{t-1})`. The scalar composition is `λ²(trait + e^{aΔt} p_0 + (B/a)²v) + ψ`. Form the predetermined lagged latent covariance first, then `λ²c+ψ`. A zero loading is exactly `ψ`. A zero trait, a zero initial variance, and a zero TI contribution is exactly `ψ`. Setting `p_0=−q/(2a)` recovers the stationary lagged observed covariance. Stationary lagged observed covariance is not this composition when `p_0` is free. `MANIFESTVAR` is not this composition. The predetermined lagged latent covariance is not this observed covariance. Predetermined later observed variance includes `Q_Δt` and `θ` and is not this composition. An overflowing product or sum fails closed. This is not a Kalman filter and not ctsem estimation. - **Level-change discrete increment.** Driver et al. (2017, §7.2, pp. 20–21; Eq. 3, pp. 4–5; Table 2, p. 12; JSS PDF re-opened 2026-08-20T19:50Z): Equation 3 maps `CINT` through `A^{-1}[e^{AΔt}−I]κ`. With `κ=−a m x` the scalar increment is `(e^{aΔt}−1)/a·(−a m x)=(1−e^{aΔt})m x`. Form the level-change `CINT` first, then the discrete intercept map. Underflow of `e^{aΔt}` to `+0` keeps `m x`. A zero effect or zero predictor is exactly zero. `(1−e^{aΔt})m x` is not `m x`, not `κ`, and not `A^{-1}[e^{AΔt}−I]Bz`. An overflowing product or increment fails closed. This is not a Kalman filter and not ctsem estimation. - **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. +- **CWC-then-irregular pairwise mean.** Unique Draft #327 evidence folded into `longitudinal_core` on landing vehicle #310. Consecutive unit-mean-centered pairs keep typed `EventTimeInterval`. The pairwise mean of the Driver et al. (2017, Eq. 3; JSS PDF opened 2026-09-02, p. 4) scalar inverse \(a=\ln(|r_{t+\Delta t}|/|r_t|)/\Delta t\) is taken on nonzero same-sign residuals. This is not the Newton least-squares fit. Already-centered pairs recover \(\ln(0.5)\). See `docs/research/cwc-irregular-residual-log-rate.md`. - **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. From fb784f103ddb6fc69b7ffbf13df0e8f62bf007cb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 08:01:53 +0900 Subject: [PATCH 042/296] test(longitudinal): require row-order deterministic CWC means --- .../tests/cwc_irregular_residual_contract.rs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/crates/longitudinal_core/tests/cwc_irregular_residual_contract.rs b/crates/longitudinal_core/tests/cwc_irregular_residual_contract.rs index c6a7f455e..6c67b9247 100644 --- a/crates/longitudinal_core/tests/cwc_irregular_residual_contract.rs +++ b/crates/longitudinal_core/tests/cwc_irregular_residual_contract.rs @@ -21,6 +21,28 @@ fn already_centered_unit_interval_recovers_ln_half() { assert!((recovered - 0.5_f64.ln()).abs() < f64::EPSILON); } +#[test] +fn cwc_mean_is_deterministic_under_input_row_permutation() { + let canonical = [ + timed(1, 0.0, 1.0e16), + timed(1, 1.0, -1.0e16), + timed(1, 2.0, 1.0), + timed(2, 0.0, 2.0), + timed(2, 1.0, 1.0), + ]; + let shuffled = [ + timed(1, 0.0, 1.0e16), + timed(1, 2.0, 1.0), + timed(1, 1.0, -1.0e16), + timed(2, 1.0, 1.0), + timed(2, 0.0, 2.0), + ]; + + let expected = center_within_unit_event_lags(&canonical).expect("canonical ordering"); + let reordered = center_within_unit_event_lags(&shuffled).expect("shuffled ordering"); + assert_eq!(reordered, expected, "CWC output must not depend on input row order"); +} + #[test] fn cwc_of_raw_autoregressive_path_is_not_process_drift() { let drift = (0.5_f64).ln(); From e9e6a409acf6eac0b1e0ba24a73c8f7325fae391 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 08:03:34 +0900 Subject: [PATCH 043/296] fix(longitudinal): stabilize CWC mean ordering --- crates/longitudinal_core/src/irregular_residual.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/longitudinal_core/src/irregular_residual.rs b/crates/longitudinal_core/src/irregular_residual.rs index 96ac7229f..39828a386 100644 --- a/crates/longitudinal_core/src/irregular_residual.rs +++ b/crates/longitudinal_core/src/irregular_residual.rs @@ -133,6 +133,7 @@ pub fn center_within_unit_event_lags( if occasions.len() < 2 { continue; } + occasions.sort_by(|left, right| left.event_time().total_cmp(&right.event_time())); let count = occasions.len() as f64; let mut total = 0.0_f64; for row in occasions.iter() { @@ -142,7 +143,6 @@ pub fn center_within_unit_event_lags( if !mean.is_finite() { return Err(LongitudinalError::InvalidObservationPayload); } - occasions.sort_by(|left, right| left.event_time().total_cmp(&right.event_time())); for window in occasions.windows(2) { let earlier_residual = window[0].score() - mean; let later_residual = window[1].score() - mean; From b8b1d98d50cd1e0299a41616e2b8af7608deda1d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 08:05:08 +0900 Subject: [PATCH 044/296] docs(longitudinal): qualify irregular log-rate and remove trailing whitespace --- docs/adr/longitudinal-modeling-ownership-addendum.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/adr/longitudinal-modeling-ownership-addendum.md b/docs/adr/longitudinal-modeling-ownership-addendum.md index dc1239b57..6abf8775e 100644 --- a/docs/adr/longitudinal-modeling-ownership-addendum.md +++ b/docs/adr/longitudinal-modeling-ownership-addendum.md @@ -1,8 +1,8 @@ # ADR 0005 ownership addendum — Longitudinal Modeling -**Parent decision:** ADR 0005 — Posterior-aware ESEM/DSEM and structural interpretation -**Decision identity:** ADR 0005; this addendum does not mint a new ADR number -**Status:** Accepted clarification +**Parent decision:** ADR 0005 — Posterior-aware ESEM/DSEM and structural interpretation +**Decision identity:** ADR 0005; this addendum does not mint a new ADR number +**Status:** Accepted clarification **Recorded:** 2026-09-01 ## Decision clarification @@ -15,7 +15,7 @@ For the scalar Driver, Oud, and Voelkle (2017) p. 16 `discreteDRIFTstd` special The lagged-correlation boundary similarly requires both occasion-specific marginal variances and an `EventTimeInterval`; a covariance divided only by the earlier variance is not exposed as an autocorrelation. -CWC-then-irregular residual log-rate is also Longitudinal Modeling composition. Person-mean centering of a time-related series is not raw-process drift (Curran & Bauer, 2011, pp. 583–619; PMC3059070 XML opened 2026-09-02; Eq. 36). The unique pairwise-mean-after-CWC evidence from Draft #327 is folded here with typed `EventTimeInterval` rather than grown on `psychometric_core`. Already-centered irregular pairs recover the Driver et al. (2017, Eq. 3) inverse; CWC of a raw AR path does not. +CWC-then-irregular residual log-rate is also Longitudinal Modeling composition. Person-mean centering of a time-related series is not raw-process drift (Curran & Bauer, 2011, pp. 583–619; PMC3059070 XML opened 2026-09-02; Eq. 36). The unique pairwise-mean-after-CWC evidence from Draft #327 is folded here with typed `EventTimeInterval` rather than grown on `psychometric_core`. Already-centered irregular pairs `(1, 0.5)` recover `ln(0.5)` only for `Δt = 1`; for a general admitted interval the exact map is `ln(0.5) / Δt`. CWC of a raw AR path does not recover raw-process drift. ## DDD consequences @@ -26,6 +26,6 @@ CWC-then-irregular residual log-rate is also Longitudinal Modeling composition. ## Verification -PR #310 is the current landing vehicle for this clarification. Its RED lineage includes an extreme stable-drift case that failed because `-2a` overflowed despite a representable stationary variance, and a typed event-time contract that could not compile before the value object existed. The repaired source avoids the unnecessary doubling overflow and moves public event-time admission behind `EventTimeInterval`. The #327 fold adds CWC-then-pairwise-mean residual log-rate with Curran refusal and known-truth already-centered recovery of `ln(0.5)`. +PR #310 is the current landing vehicle for this clarification. Its RED lineage includes an extreme stable-drift case that failed because `-2a` overflowed despite a representable stationary variance, and a typed event-time contract that could not compile before the value object existed. The repaired source avoids the unnecessary doubling overflow and moves public event-time admission behind `EventTimeInterval`. The #327 fold adds CWC-then-pairwise-mean residual log-rate with Curran refusal and known-truth already-centered recovery of `ln(0.5)` at `Δt = 1`; arbitrary admitted intervals recover the exact `ln(0.5) / Δt` map. Protected-main maturity is not claimed until the exact landing head passes the live ruleset and is merged. From 623fb253ba9de67dc3f18743c7c223bad97c0954 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 08:06:42 +0900 Subject: [PATCH 045/296] docs(adr): preserve metadata layout without trailing whitespace --- docs/adr/longitudinal-modeling-ownership-addendum.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/adr/longitudinal-modeling-ownership-addendum.md b/docs/adr/longitudinal-modeling-ownership-addendum.md index 6abf8775e..8383152db 100644 --- a/docs/adr/longitudinal-modeling-ownership-addendum.md +++ b/docs/adr/longitudinal-modeling-ownership-addendum.md @@ -1,8 +1,11 @@ # ADR 0005 ownership addendum — Longitudinal Modeling **Parent decision:** ADR 0005 — Posterior-aware ESEM/DSEM and structural interpretation + **Decision identity:** ADR 0005; this addendum does not mint a new ADR number + **Status:** Accepted clarification + **Recorded:** 2026-09-01 ## Decision clarification From e4acfda6bbd9f4a685a0c8c95c8f82c04d1351fc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 08:34:10 +0900 Subject: [PATCH 046/296] docs(longitudinal): qualify irregular residual rate --- CLAUDE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 8cc640fd2..b8761cf53 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,7 +16,7 @@ Read and follow `AGENTS.md` before changing this repository. The repository-wide - Do not treat raw topic proportions as ordinary Euclidean indicators. Use logistic-normal coordinates or valid log-ratio coordinates and propagate posterior uncertainty into ESEM/DSEM. - Do not treat metric/weak invariance as a latent-mean license. Strong (equal loading and intercept) or strict is required; `#84` `metric` licenses shared metric meaning only. Putnick and Bornstein (2016, PMC5145197 opened 2026-08-19T22:15Z) require scalar invariance before latent-mean comparison; residual invariance is not a prerequisite. Two-observation series have no residual degrees of freedom (`ordinary_least_squares_fit` returns residual variance `0`) and cap at strong/scalar; they still license means. This is two-group OLS, not MGCFA. Meredith (1993) names remain unread labels (Unpaywall/OpenAlex 2026-08-25T11:32Z: closed). - Do not use the difference quotient as a continuous-time rate. The scalar map is `a = ln(φ) / Δt` on event time. Discrete lags from unequal event intervals are not one coefficient; remap them through that log-rate. Binary64 `exp(a Δt) = 0` is not a discrete lag. A constant predictor's discrete effect is Voelkle et al. (2012, Eq. 12), evaluated as `a_yx (expm1(z) / a_xx)` with `z = a_xx Δt` so a finite result is not lost when `z` overflows to `-∞` or when `a_yx Δt` overflows. When `expm1(z)` overflows at a finite `z`, rewrite in log space; a zero continuous effect is exactly zero; an overflowing `a_yx/a_xx` rewrite term fails closed. The first-order product is the underflow limit of that equation, not the general constant-predictor discrete effect. A time-varying predictor whose sampling interval equals its constancy interval uses Voelkle et al. (2012, Eq. 14): `b* = a_yx Δt`. Unmatched intervals fail closed (Oud & Jansen, 2000, unread). Discrete process noise is Driver et al. (2017, Eq. 3): `Q_Δt = 0.5 q (expm1(z) / a)` with `z = 2 (a Δt)` and `q = G G⊤ ≥ 0`; do not form `2 a` first; `a = 0` and `z → 0` recover `q Δt`; a zero diffusion is exactly zero; an overflowing rewrite scale `0.5 q / a` fails closed; this is not a Kalman filter. `Q_Δt` is `cov(η_t | η_{t-1})`, not `Var(η_t)`. The lagged covariance is `exp(a Δt) p` and the unconditional variance is `exp(2 a Δt) p + Q_Δt` (Driver et al., 2017, Eq. 3–4, pp. 4–5; JSS has no numbered §2.2). A zero diffusion whose `2 (a Δt)` overflows to `+∞` is not a finite `Var(η_t)`. The stationary within-subject variance is the `Δt → ∞` limit of Eq. 4: `-q / (2 a)` for stable `a < 0` (JSS p. 16 `asymDIFFUSION`; §4.3). When `2 a` is finite, form `q / -(2 a)` so `q / a` overflow does not lose a finite result (`q = MAX`, `a = -0.75` → `MAX / 1.5`). When `2 a` overflows, form `(q / a) * -0.5`. Do not form `0.5 q` first (`q = from_bits(1)` underflows). `a ≥ 0` has no finite stationary variance. Finite-interval `Q_Δt` is not that limit. Trait-plus-state variance is `trait + state` and lagged covariance is `trait + exp(a Δt) p` (Driver et al., 2017, §4.3, p. 9). Trait variance is not process noise and not `asymDIFFUSION`. Evolving the summed variance as if it were all state is not that map. This is not RI-CLPM. Observed-indicator variance is `λ² Var(η) + θ` when `MANIFESTTRAITVAR` is zero and `λ² Var(η) + θ + ψ` otherwise (Driver et al., 2017, Eq. 5, p. 5; Table 2, p. 12). Lagged observed covariance is `λ² cov(η_t, η_{t-1}) + ψ`; `MANIFESTVAR` does not enter. Observed-indicator mean is `τ + λ μ` (Driver et al., 2017, Eq. 5; Table 2, p. 12). `MANIFESTMEANS` is `τ`, not `E(y)`. `E(η)` is not `E(y)`. `CINT` is not `MANIFESTMEANS`. `T0MEANS` is not `E(y)`. The discrete latent mean is `μ_t = exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ` (Driver et al., 2017, Eq. 3, p. 4; Table 2, p. 12). `T0MEANS` is not `μ_t`. `CINT` is not that discrete increment. A zero drift is `κ Δt`. Underflow of `exp(a Δt)` to `+0` drops the carried `T0MEANS` and keeps `−κ / a`. The evolved observed mean is `τ + λ μ_t` (Driver et al., 2017, Eq. 5 of that Eq. 3 map). The first-occasion map `τ + λ μ_0` is not `E(y_t)`. `μ_t` is not `E(y_t)`. The contemporaneous time-dependent predictor impulse is `m x` (Driver et al., 2017, Eq. 3 fourth summand; Table 2 `TDPREDEFFECT` is `M`). Form `μ_t` first, then add `m x`. `TDPREDEFFECT` is not `CINT`. `M x` is not `A^{-1}[e^{A Δt} − I] B z` and is not Voelkle et al. (2012, Eq. 14). The §7.2 level-change form is not that impulse. The observed mean of that contemporaneous impulse is `τ + λ(μ_t + m x)` (Driver et al., 2017, Eq. 5 of the Eq. 3 fourth-summand composition). The evolved map `τ + λ μ_t` is not that observed mean. The carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`. The evolved-plus-impulse latent mean is not `E(y_t)`. The time-independent predictor increment is `A^{-1}[e^{A Δt} − I] B z` (Driver et al., 2017, Eq. 3 second summand; Table 2 `TIPREDEFFECT` is `B`). Form `B z` first, then the discrete intercept map. A zero drift is `B z Δt`. `TIPREDEFFECT` is `B`, not that discrete increment. `A^{-1}[e^{A Δt} − I] B z` is not `CINT`, not `M x`, and not Voelkle et al. (2012, Eq. 14). The observed mean of that increment is `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` (Driver et al., 2017, Eq. 5 of the Eq. 3 printed addend after the `T0MEANS` carry and the `CINT` increment). The evolved map `τ + λ μ_t` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`. The evolved-plus-increment latent mean is not `E(y_t)`. The within-interval time-dependent impulse carry is `e^{A(t−u)} M x` for `t0 < u < t` (Driver et al., 2017, Eq. 1–2 Green-function integral of Eq. 2; §7.2 dissipation). Form `m x` first, then `e^{a(t−u)} m x`. A zero drift is `m x` with no dissipation. Underflow of `e^{a(t−u)}` to `+0` is vanishing dissipation and is kept. `e^{A(t−u)} M x` is not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle et al. (2012, Eq. 14). An impulse at `u = t` is the contemporaneous map. An impulse at `u ≤ t0` is already in `η(t0)`. The observed mean of that carry is `τ + λ(μ_t + e^{a(t−u)} m x)` (Driver et al., 2017, Eq. 5 of the Eq. 1–2 carried latent mean). The evolved map `τ + λ μ_t` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean when `u ≠ t`. `MANIFESTMEANS` is not `E(y_t)`. The carried latent mean is not `E(y_t)`. The first-occasion time-independent predictor shift is `t0_b z` (Driver et al., 2017, Table 3 `T0TIPREDEFFECT`; Eq. 3 first summand). Form `t0_b z` first, then `e^{a Δt} t0_b z`. Form `μ_t` first, then add that carry. A zero drift is `t0_b z`. Underflow of `e^{a Δt}` to `+0` is a vanishing carry of the first-occasion shift and is kept. `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`. `e^{A Δt} t0_b z` is not `t0_b z`. `T0TIPREDEFFECT` is the coefficient, not the shift. The observed mean of that first-occasion carry is `τ + λ(μ_t + e^{a Δt} t0_b z)` (Driver et al., 2017, Eq. 5 of the Table 3 / Eq. 3 first-summand composition). The evolved map `τ + λ μ_t` is not that observed mean. The process-increment map `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The impulse-carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t0`. The evolved-plus-carry latent mean is not `E(y_t)`. The first-occasion time-dependent predictor shift is `t0_m x0` (Driver et al., 2017, Table 3 `T0TDPREDEFFECT`; Eq. 3 first summand; JSS PDF re-opened 2026-08-20T19:10Z). Form `t0_m x0` first, then `e^{a Δt} t0_m x0`. Form `μ_t` first, then add that carry. A zero drift is `t0_m x0`. Underflow of `e^{a Δt}` to `+0` is a vanishing carry of the first-occasion shift and is kept. `t0_m x0` is not `M x`, not `e^{A(t−u)} M x` for `t0 < u < t`, not `t0_b z`, not `A^{-1}[e^{A Δt} − I] B z`, and not `CINT`. `e^{A Δt} t0_m x0` is not `t0_m x0`. `T0TDPREDEFFECT` is the coefficient, not the shift. An impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`. The observed mean of that first-occasion TD carry is `τ + λ(μ_t + e^{a Δt} t0_m x0)` (Driver et al., 2017, Eq. 5 of the Table 3 / Eq. 3 first-summand TD composition; JSS PDF re-opened 2026-08-20T19:07Z). The evolved map `τ + λ μ_t` is not that observed mean. The process-increment map `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The impulse-carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t0`. The first-occasion TI map `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean. The evolved-plus-carry latent mean is not `E(y_t)`. The lasting level-change `CINT` is `κ = −a m x` (Driver et al., 2017, §7.2, pp. 20–21; JSS PDF re-opened 2026-08-20T19:45Z). Form `m x` first, then multiply by `−a`. Stable `a < 0` is required so `−κ / a = m x` is an equilibrium offset. `a ≥ 0` cannot hold a new process mean. `−a m x` is not the dissipating Dirac `m x`, not a free `CINT`, and not `A^{-1}[e^{A Δt} − I] B z`. The extra near-zero-drift latent process also named in §7.2 is a different specification and is not this `CINT` setting. Equation 3 maps that intercept as `(1 − e^{a Δt}) m x` (JSS PDF re-opened 2026-08-20T19:50Z). Form the level-change `CINT` first, then the discrete intercept map. Underflow of `e^{a Δt}` to `+0` keeps `m x`. `(1 − e^{a Δt}) m x` is not `m x`, not `κ`, and not `A^{-1}[e^{A Δt} − I] B z`. The printed §7.2 lasting level change is an extra near-zero-drift latent process (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-20T23:10Z). `T0MEANS`, `CINT`, `T0VAR`, `DIFFUSION`, and `TRAITVAR` of that process are fixed to 0; `TDPREDEFFECT` on it is fixed to 1; its `DRIFT` diagonal is very close to 0 (printed example `−0.000001`; precisely 0 causes computational problems); the original process is driven by the `DRIFT` coupling `a_{ηξ}`. After a unit identification impulse the scalar contribution is `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (`ε = a` is `a_{ηξ} x Δt e^{a Δt}`). Form `a_{ηξ} x` first. A zero coupling or zero predictor is exactly zero. `ε ≥ 0` fails closed. That contribution is not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`. The observed mean of that extra-process contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a))` (Driver et al., 2017, Eq. 5 of that §7.2 contribution; JSS PDF re-opened 2026-08-21T06:12Z). The extra process has `LAMBDA` 0 and is not an observed indicator. Original indicators load on the original process after the `DRIFT` coupling. The evolved map `τ + λ μ_t` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The contribution is not `E(y_t)`. The evolved-plus-contribution latent mean is not `E(y_t)`. `T0TDPREDEFFECT` on the extra process begins at `t = 0` and uses `Δt = t − t0` for both the original-process evolution and the extra drive. `TDPREDEFFECT` after `t0` uses `t − u` with `t0 < u < t` for the extra drive while `μ_t` still uses `Δt`. The observed mean of that after-t0 extra-process contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a))` (Driver et al., 2017, Eq. 5 of that §7.2 after-t0 contribution; JSS PDF re-opened 2026-08-21T06:32Z). The first-occasion extra-process observed mean is not that observed mean when `u ≠ t0`. The impulse-carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is a Dirac on the original process and is not that `DRIFT` drive. An impulse at `u = t0` or `u = t` is not interior. The asymptotic time-independent predictor effect is `-B z / a` (Driver et al., 2017, §7.2, pp. 20–21; JSS PDF opened 2026-08-21T13:08Z). Form `B z` first, then divide by `-a`. Stable `a < 0` is required. `a ≥ 0` cannot hold a finite process-mean change. `-B z / a` is not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`. The asymptotic time-independent predictor variance is `(B / a)² v` (Driver et al., 2017, §7.2, pp. 20–21 `addedTIPREDVAR`). Form the unit asymptotic effect first, then square, then multiply by `v`. `(B / a)² v` is not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`. The asymptotic continuous intercept is `-κ / a` (Driver et al., 2017, Table 2, p. 12 `asymCINT`; Eq. 3 as `Δt → ∞`; JSS PDF opened 2026-08-21T16:13Z). Form `κ` first, then divide by `-a`. Stable `a < 0` is required. `-κ / a` is not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`. The p. 16 stationary `T0MEANS` constraint is `-κ / a + −B z / a`. Form the intercept contribution first, then include the TI extra effect, then add. That constrained first-occasion mean is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean. Equation 5 of that constrained mean is `τ + λ(−κ / a + −B z / a)` (Driver et al., 2017, §4.3, pp. 9–10; Eq. 5, p. 5; JSS PDF re-opened 2026-08-21T20:07Z). Form the stationary latent mean first, then `τ + λ` of that mean. `τ + λ μ_0` for free `T0MEANS` is not that composition. `τ + λ(−κ / a)` is not that composition when `B z ≠ 0`. `τ + λ μ_t` is not that composition. `MANIFESTMEANS` is not `E(y_0)`. The constrained latent mean is not `E(y_0)`. The p. 16 constrained first-occasion variance `trait + −q / (2 a) + (B / a)² v` is not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`). The lagged covariance of that constrained process is `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T19:13Z). Trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`. Contemporaneous `T0VAR` is not that lagged map. Decaying the constrained total as if it were all state is not that lagged map. Equation 5 of that lagged covariance is `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ`. `Θ` does not enter. Contemporaneous `Var(y_0)` is not that lagged observed covariance. The lagged latent covariance is not that observed covariance. The later-occasion variance of that constrained process is `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T23:12Z). Trait and `addedTIPREDVAR` do not enter `Q_Δt`. Under stationarity that composition equals contemporaneous `T0VAR`. Evolving the constrained total as if it were all state is not that later map. The lagged covariance omits `Q_Δt` and is not that later map. `Q_Δt` is not that later map. Equation 5 of that later-occasion variance is `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ`. The lagged observed covariance omits `Q_Δt` and `θ`. `MANIFESTVAR` is not `Var(y_t)`. The later-occasion latent variance is not `Var(y_t)`. The later-occasion variance of §4.3 predetermined `T0VAR` is `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T05:12Z). Trait and `addedTIPREDVAR` do not enter `Q_Δt`. Free `T0VAR` `p_0` is not that later map. Setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map. Stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free. Evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map. As `Δt → ∞` with stable `a < 0` the composition approaches contemporaneous stationary `T0VAR`. As `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`. Nonzero diffusion with `a ≥ 0` is a growing process and is kept. Equation 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ`. `MANIFESTVAR` is not `Var(y_t)`. The predetermined later-occasion latent variance is not `Var(y_t)`. Stationary later observed variance is not that observed variance when `p_0` is free. The lagged covariance of §4.3 predetermined `T0VAR` is `trait + e^{a Δt} p_0 + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T09:04Z). Trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`. Free `T0VAR` `p_0` is not that lagged map. Setting `p_0 = −q / (2 a)` recovers the stationary lagged map. Stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free. Evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map. Later-occasion variance includes `Q_Δt` and is not that lagged map. As `Δt → ∞` with stable `a < 0` the state term vanishes. As `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`. Equation 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ`. `MANIFESTVAR` does not enter. The predetermined lagged latent covariance is not that observed covariance. Predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance. Stationary lagged observed covariance is not that observed covariance when `p_0` is free. The predetermined first-occasion variance of §4.3 predetermined `T0VAR` is `trait + p_0 + (B / a)² v`. Free `p_0` is not that map. Stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free. Lagged covariance decays the state and is not that map. Later-occasion variance includes `Q_Δt` and is not that map. Equation 5 of that predetermined first-occasion variance is `λ²(trait + p_0 + (B / a)² v) + θ + ψ`. `MANIFESTVAR` is not that first-occasion observed variance. The predetermined first-occasion latent variance is not that observed variance. Stationary first-occasion observed variance is not that observed variance when `p_0` is free. Predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance. Later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z). First-occasion lagged omits `e^{a s} Q_u`. Later-occasion variance does not lag. Stationary lagged uses `−q / (2 a)`. Decaying the later total is not that map. Equation 5 of that later-start lagged covariance is `λ²` of it plus `ψ`. Independent `ε_t` does not enter. First-occasion lagged observed omits `e^{a s} Q_u`. Predetermined later observed variance includes `Q_u` and `θ` and is not that later-start lagged observed covariance. Later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z). Later-occasion variance at `u` omits `Q_s`. Later-start lagged covariance omits `Q_s`. Stationary later uses `−q / (2 a)`. Evolving the later total as if it were all state is not that map. Ignoring `startoffset` omits `e^{2 a s} Q_u`. Equation 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`. `MANIFESTVAR` is not that observed variance. Page 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; footnote 4; §7.1; JSS PDF re-opened 2026-08-23T11:40Z). Unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`. The §7.1 trait-plus-state autocorrelation `(trait + e^{a Δt} p + added) / (trait + p + added)` uses `TRAITVAR` and is not `discreteDRIFTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:06Z). Unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`. The continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`. `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`. `TRAITVAR` is not the standardisation variance. Page 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z). Unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`. The discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`. `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`. `TRAITVAR` is not the standardisation variance. Page 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z). Unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`. The discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`. `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z). Unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`. The finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`. `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z). Unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`. The asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`. The finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`. `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Page 16 / Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z). The affected variance is free first-occasion `T0VAR`, not `asymDIFFUSION`. Unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`. `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`. `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`. `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. 2017-era `addedT0TIPREDVAR` is `t0_b² v` after a first-occasion time-independent predictor (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z). Form `t0_b` first, then square, then multiply by `v`. A zero coefficient or zero predictor variance is exactly zero. Free `T0TIPREDEFFECT` does not require `a < 0`. `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map. `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance. Free `T0VAR` is not this extra TI variance. `TRAITVAR` is not this extra TI variance. Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z). Form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`. A zero loading or zero extra is exactly zero. `t0_b² v` is the latent extra, not the observed extra. `λ² p_0 + θ` is first-occasion observed variance, not this extra. `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra. `MANIFESTVAR` `θ` is not this extra. Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v` (Driver et al., 2017, Eq. 5, p. 5; Table 2, p. 12; §7.2, pp. 20–21; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:23Z). Form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`. A zero loading or zero extra is exactly zero. Lasting asymptotic extra requires `a < 0`. `(B / a)² v` is the latent extra, not the observed extra. `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra. `λ² p + θ` is stationary observed variance, not this extra. `MANIFESTVAR` `θ` is not this extra. Page 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance. Unstandardised `M` is not `TDPREDEFFECTstd`. `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`. intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`. `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`. Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance. Unstandardised `t0_m` is not `T0TDPREDEFFECTstd`. `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`. `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`. `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`. Free `T0VAR` does not require `a < 0`. Page 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; the default ridge is 0). Unstandardised `T0VAR` is not `T0VARstd`. `T0TDPREDEFFECTstd` is not `T0VARstd`. `addedT0TIPREDVAR` is not `T0VARstd`. Page 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend). Unstandardised `TRAITVAR` is not `TRAITVARstd`. `T0VARstd` is not `TRAITVARstd` even when both equal 1. `addedT0TIPREDVAR` is not `TRAITVARstd`. Page 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0). Unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`. `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1. `MANIFESTVAR` is not `MANIFESTTRAITVARstd`. Page 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug). Unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`. `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1. Equation 5 `Var(y)` is not `MANIFESTVARstd`. Page 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`). Unstandardised `TIPREDVAR` is not `TIPREDVARstd`. `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1. Section 7.2 `addedTIPREDVAR` is not `TIPREDVARstd`. Page 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`). Unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`. `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1. `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`. Page 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`. Unstandardised `discreteCINT` is not `discreteCINTstd`. `κ / √p` is not `discreteCINTstd`. `(-κ / a) / √p` is not `discreteCINTstd`. `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`. Unstandardised `asymCINT` is not `asymCINTstd`. `κ / √p` is not `asymCINTstd`. `discreteCINTstd` is not `asymCINTstd`. `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`. Unstandardised `T0MEANS` is not `T0MEANSstd`. `T0VARstd` is not `T0MEANSstd`. `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`. Page 16 `MANIFESTMEANSstd` is `τ / √θ` after strictly positive `MANIFESTVAR`. Unstandardised `MANIFESTMEANS` is not `MANIFESTMEANSstd`. `MANIFESTVARstd` is not `MANIFESTMEANSstd`. `τ / √(λ² Var(η) + θ)` is not `MANIFESTMEANSstd`. Page 16 `CINTstd` is `κ / √p` after strictly positive `asymDIFFUSION`. Unstandardised `CINT` is not `CINTstd`. `asymCINTstd` is not `CINTstd`. `discreteCINTstd` is not `CINTstd`. `κ / √(trait + p + added)` is not `CINTstd`. Evolving from that stationary start with `CINT` and `TIPREDEFFECT` stays at the stationary mean. Equation 1 is the latent SDE, not the measurement model. Form `(λ p) λ` then add `θ`, then add `ψ`. `MANIFESTVAR` is `Θ`, not `Var(y)`. `MANIFESTTRAITVAR` is `Ψ_τ`, not `Θ`. `TRAITVAR` is latent and scaled by `λ²`. `Var(η)` is not `Var(y)`. -- Separate cluster means before within-unit lag. CWC plus an event-time lag is not DSEM. Subtracting the person-specific mean from a raw autoregressive series does not isolate the lagged within-person effect (Curran & Bauer, 2011, pp. 583–619; PMC3059070 XML opened 2026-09-02; Eq. 36). `longitudinal_core` owns CWC-then-pairwise-mean residual log-rate on typed `EventTimeInterval`; already-centered residuals recover the Driver et al. (2017, Eq. 3) inverse, including `ln(0.5)`. CWC log-rate is not raw-process drift. +- Separate cluster means before within-unit lag. CWC plus an event-time lag is not DSEM. Subtracting the person-specific mean from a raw autoregressive series does not isolate the lagged within-person effect (Curran & Bauer, 2011, pp. 583–619; PMC3059070 XML opened 2026-09-02; Eq. 36). `longitudinal_core` owns CWC-then-pairwise-mean residual log-rate on typed `EventTimeInterval`; for an already-centered `(1, 0.5)` pair it recovers `ln(0.5) / Δt`, equal to `ln(0.5)` only when `Δt = 1`. CWC log-rate is not raw-process drift. - Do not treat the CWC cluster-mean coefficient as the between-cluster effect. It is the contextual effect `between − within` (Enders & Tofighi, 2007, Table 2, pp. 124–127). - Never use future-available evidence in historical model fits. - Do not blanket-mask PII when identity/role/linkage is scientifically required. Follow the purpose-bound separation, opaque-ID, encryption, retention, and audit contract in `docs/PRIVACY_DATA_GOVERNANCE.md`. From 0bc24a1918cbd7951ba32c689de7bb408b5067c7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 08:42:02 +0900 Subject: [PATCH 047/296] docs(longitudinal): qualify irregular interval rate --- docs/research/multilevel-event-time-recovery.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/research/multilevel-event-time-recovery.md b/docs/research/multilevel-event-time-recovery.md index 71c87784e..e0dc7f9f9 100644 --- a/docs/research/multilevel-event-time-recovery.md +++ b/docs/research/multilevel-event-time-recovery.md @@ -180,7 +180,7 @@ The Voelkle et al. (2012) ZORA accepted manuscript was re-opened 2026-08-18T21:0 - **Lagged predetermined observed covariance.** Driver et al. (2017, Eq. 5, p. 5; Eq. 3–4, pp. 4–5; Table 2, p. 12; JSS PDF re-opened 2026-08-23T09:04Z): independent `ε_t` does not enter `cov(y_t,y_{t-1})`. The scalar composition is `λ²(trait + e^{aΔt} p_0 + (B/a)²v) + ψ`. Form the predetermined lagged latent covariance first, then `λ²c+ψ`. A zero loading is exactly `ψ`. A zero trait, a zero initial variance, and a zero TI contribution is exactly `ψ`. Setting `p_0=−q/(2a)` recovers the stationary lagged observed covariance. Stationary lagged observed covariance is not this composition when `p_0` is free. `MANIFESTVAR` is not this composition. The predetermined lagged latent covariance is not this observed covariance. Predetermined later observed variance includes `Q_Δt` and `θ` and is not this composition. An overflowing product or sum fails closed. This is not a Kalman filter and not ctsem estimation. - **Level-change discrete increment.** Driver et al. (2017, §7.2, pp. 20–21; Eq. 3, pp. 4–5; Table 2, p. 12; JSS PDF re-opened 2026-08-20T19:50Z): Equation 3 maps `CINT` through `A^{-1}[e^{AΔt}−I]κ`. With `κ=−a m x` the scalar increment is `(e^{aΔt}−1)/a·(−a m x)=(1−e^{aΔt})m x`. Form the level-change `CINT` first, then the discrete intercept map. Underflow of `e^{aΔt}` to `+0` keeps `m x`. A zero effect or zero predictor is exactly zero. `(1−e^{aΔt})m x` is not `m x`, not `κ`, and not `A^{-1}[e^{AΔt}−I]Bz`. An overflowing product or increment fails closed. This is not a Kalman filter and not ctsem estimation. - **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. -- **CWC-then-irregular pairwise mean.** Unique Draft #327 evidence folded into `longitudinal_core` on landing vehicle #310. Consecutive unit-mean-centered pairs keep typed `EventTimeInterval`. The pairwise mean of the Driver et al. (2017, Eq. 3; JSS PDF opened 2026-09-02, p. 4) scalar inverse \(a=\ln(|r_{t+\Delta t}|/|r_t|)/\Delta t\) is taken on nonzero same-sign residuals. This is not the Newton least-squares fit. Already-centered pairs recover \(\ln(0.5)\). See `docs/research/cwc-irregular-residual-log-rate.md`. +- **CWC-then-irregular pairwise mean.** Unique Draft #327 evidence folded into `longitudinal_core` on landing vehicle #310. Consecutive unit-mean-centered pairs keep typed `EventTimeInterval`. The pairwise mean of the Driver et al. (2017, Eq. 3; JSS PDF opened 2026-09-02, p. 4) scalar inverse \(a=\ln(|r_{t+\Delta t}|/|r_t|)/\Delta t\) is taken on nonzero same-sign residuals. This is not the Newton least-squares fit. For an already-centered pair `(1, 0.5)`, the recovered rate is \(\ln(0.5)/\Delta t\), which equals \(\ln(0.5)\) only when \(\Delta t=1\). See `docs/research/cwc-irregular-residual-log-rate.md`. - **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. From 4c481db81c86371ac212d96b04a2cadbec533756 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 08:44:05 +0900 Subject: [PATCH 048/296] test(longitudinal): reproduce subnormal stationary variance loss --- crates/longitudinal_core/src/stationary.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/crates/longitudinal_core/src/stationary.rs b/crates/longitudinal_core/src/stationary.rs index 5d1fd3d54..2fbd17074 100644 --- a/crates/longitudinal_core/src/stationary.rs +++ b/crates/longitudinal_core/src/stationary.rs @@ -47,6 +47,17 @@ mod tests { assert_eq!(stationary, 0.5); } + #[test] + fn preserves_minimum_subnormal_stationary_variance_after_drift_overflow() { + // Exact q / (-2a) lies above the half-ulp threshold for the minimum + // positive subnormal. Dividing by |a| first rounds to one subnormal; + // halving that rounded intermediate incorrectly erases the result. + let diffusion = 1.332_267_629_550_187_7e-15_f64; + let stationary = recover_stationary_within_variance(diffusion, -f64::MAX) + .expect("positive subnormal stationary variance is representable"); + assert_eq!(stationary.to_bits(), 1); + } + #[test] fn stationary_variance_admission_is_fail_closed() { assert_eq!(recover_stationary_within_variance(0.0, -0.5), Ok(0.0)); From fe85366b16acdffd9e0f6887a3f3cf1da1591577 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 08:44:17 +0900 Subject: [PATCH 049/296] fix(longitudinal): preserve subnormal stationary variance --- crates/longitudinal_core/src/stationary.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/longitudinal_core/src/stationary.rs b/crates/longitudinal_core/src/stationary.rs index 2fbd17074..e483d0590 100644 --- a/crates/longitudinal_core/src/stationary.rs +++ b/crates/longitudinal_core/src/stationary.rs @@ -27,7 +27,10 @@ pub(crate) fn recover_stationary_within_variance( let stationary = if twice_rate.is_finite() { continuous_diffusion / -twice_rate } else { - (continuous_diffusion / -log_rate) * 0.5 + // Scale the overflowing denominator down before division and compensate + // afterward. This keeps a representable minimum-subnormal result from + // being rounded to one subnormal and then erased by a final halving. + (continuous_diffusion / (-log_rate * 0.5)) * 0.25 }; if !stationary.is_finite() { return Err(LongitudinalError::InvalidTemporalTransformInput); From 629800e6b6da9d4000ce43516d04caafd4a5c67d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 08:45:02 +0900 Subject: [PATCH 050/296] test(longitudinal): reproduce extreme-rate mean precision loss --- ...regular_residual_mean_accuracy_contract.rs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 crates/longitudinal_core/tests/irregular_residual_mean_accuracy_contract.rs diff --git a/crates/longitudinal_core/tests/irregular_residual_mean_accuracy_contract.rs b/crates/longitudinal_core/tests/irregular_residual_mean_accuracy_contract.rs new file mode 100644 index 000000000..6cd90b1b5 --- /dev/null +++ b/crates/longitudinal_core/tests/irregular_residual_mean_accuracy_contract.rs @@ -0,0 +1,28 @@ +//! Accuracy contract for means of extreme but finite event-time log rates. + +use longitudinal_core::{ + EventTimeInterval, LaggedWithinResidual, recover_centered_irregular_residual_log_rate, +}; + +fn pair(rate: f64, interval: f64) -> LaggedWithinResidual { + let earlier = 1.0_f64; + let later = (rate * interval).exp(); + LaggedWithinResidual::new( + earlier, + later, + EventTimeInterval::new(interval).expect("positive event interval"), + ) +} + +#[test] +fn compensated_mean_preserves_small_signal_between_extreme_rates() { + let pairs = [ + pair(1.0e100, 1.0e-100), + pair(1.0, 1.0), + pair(-1.0e100, 1.0e-100), + ]; + let recovered = recover_centered_irregular_residual_log_rate(&pairs) + .expect("finite extreme-rate mean remains identifiable"); + let expected = 1.0 / 3.0; + assert!((recovered - expected).abs() <= 1.0e-12); +} From dac267cfcff5a169c7b600479ef453f27a40053c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 08:47:05 +0900 Subject: [PATCH 051/296] fix(longitudinal): compensate extreme-rate means --- .../src/irregular_residual.rs | 111 +++++++++++------- 1 file changed, 66 insertions(+), 45 deletions(-) diff --git a/crates/longitudinal_core/src/irregular_residual.rs b/crates/longitudinal_core/src/irregular_residual.rs index 39828a386..7a34f4eb6 100644 --- a/crates/longitudinal_core/src/irregular_residual.rs +++ b/crates/longitudinal_core/src/irregular_residual.rs @@ -173,8 +173,9 @@ pub fn center_within_unit_event_lags( /// used so near-equal large residuals do not collapse to zero. Overflowed or /// underflowed ratios fall back to `ln|later| − ln|earlier|`. Opposite-sign /// and zero residuals have no real logarithm and are skipped. The pairwise -/// mean is incremental so two finite rates whose raw sum overflows stay -/// representable. An empty admissible set fails closed. This is not Newton LS +/// mean uses scale-normalized Neumaier compensation so finite rates can cancel +/// without forming an overflowing raw sum or discarding a much smaller finite +/// contribution. An empty admissible set fails closed. This is not Newton LS /// and does not recover raw-process drift from CWC of a raw AR path (Curran /// & Bauer, 2011, pp. 583–619; Eq. 36). /// @@ -209,8 +210,7 @@ pub fn recover_centered_irregular_residual_log_rate( if pairs.is_empty() { return Err(LongitudinalError::InvalidObservationPayload); } - let mut mean = 0.0_f64; - let mut count = 0.0_f64; + let mut rates = Vec::with_capacity(pairs.len()); for pair in pairs { if !pair.earlier_residual().is_finite() || !pair.later_residual().is_finite() { return Err(LongitudinalError::InvalidObservationPayload); @@ -222,10 +222,11 @@ pub fn recover_centered_irregular_residual_log_rate( if !ratio.is_finite() || ratio <= 0.0 { return Err(LongitudinalError::InvalidTemporalTransformInput); } - let rate = require_finite(ratio.ln() / pair.event_interval().as_f64())?; - (mean, count) = overflow_safe_running_mean(mean, count, rate); + rates.push(require_finite( + ratio.ln() / pair.event_interval().as_f64(), + )?); } - require_finite(mean) + scaled_compensated_mean(&rates) } /// Refuse treating a CWC residual log-rate as raw-process AR drift. @@ -249,38 +250,55 @@ pub fn refuse_cwc_residual_log_rate_as_raw_process_drift( } fn pairwise_same_sign_log_rate(lagged: &[LaggedWithinResidual]) -> Result { - let mut mean = 0.0_f64; - let mut count = 0.0_f64; + let mut rates = Vec::with_capacity(lagged.len()); for pair in lagged { if !same_sign_nonzero(pair.earlier_residual(), pair.later_residual()) { continue; } - let rate = driver_same_sign_log_rate( + rates.push(driver_same_sign_log_rate( pair.earlier_residual(), pair.later_residual(), pair.event_interval(), - )?; - (mean, count) = overflow_safe_running_mean(mean, count, rate); + )?); } - if count <= 0.0 { - return Err(LongitudinalError::InvalidTemporalTransformInput); - } - require_finite(mean) + scaled_compensated_mean(&rates) } -/// Incremental mean that stays finite when the raw sum of finite values overflows. +/// Scale-normalized Neumaier mean for finite values with extreme dynamic range. /// -/// Form `mean * (n − 1) / n + value / n` so two rates near `1.45e308` keep a -/// representable pairwise mean. Mixed signs scale each term by `1 / n` before -/// adding, so `MAX + (−MAX)` is not formed. Caller supplies finite values. -pub(crate) fn overflow_safe_running_mean(mean: f64, count: f64, value: f64) -> (f64, f64) { - let next_count = count + 1.0; - if count <= 0.0 { - (value, next_count) - } else { - let inv = 1.0 / next_count; - (mean.mul_add(1.0 - inv, value * inv), next_count) +/// Normalizing by the largest magnitude keeps the raw accumulation bounded by +/// the number of values. Neumaier compensation retains low-order terms when +/// large opposite-signed rates cancel. Division by the count happens before +/// scaling back, so a representable mean is not lost to an overflowing sum. +fn scaled_compensated_mean(values: &[f64]) -> Result { + if values.is_empty() { + return Err(LongitudinalError::InvalidTemporalTransformInput); + } + let mut scale = 0.0_f64; + for &value in values { + if !value.is_finite() { + return Err(LongitudinalError::InvalidTemporalTransformInput); + } + scale = scale.max(value.abs()); + } + if scale == 0.0 { + return Ok(0.0); + } + + let mut sum = 0.0_f64; + let mut compensation = 0.0_f64; + for &value in values { + let scaled = value / scale; + let next = sum + scaled; + if sum.abs() >= scaled.abs() { + compensation += (sum - next) + scaled; + } else { + compensation += (scaled - next) + sum; + } + sum = next; } + let normalized_mean = (sum + compensation) / values.len() as f64; + require_finite(normalized_mean * scale) } /// Nonzero residuals of equal sign admit a real Driver Eq. 3 logarithm. @@ -320,10 +338,10 @@ fn require_finite(value: f64) -> Result { mod tests { use super::{ EventTimedObservation, LaggedWithinResidual, center_within_unit_event_lags, - driver_same_sign_log_rate, overflow_safe_running_mean, - recover_centered_irregular_residual_log_rate, + driver_same_sign_log_rate, recover_centered_irregular_residual_log_rate, recover_within_unit_irregular_residual_log_rate, refuse_cwc_residual_log_rate_as_raw_process_drift, same_sign_nonzero, + scaled_compensated_mean, }; use crate::{EventTimeInterval, LongitudinalError}; @@ -465,19 +483,27 @@ mod tests { } #[test] - fn overflow_safe_running_mean_keeps_finite_pairwise_mean() { + fn scaled_compensated_mean_keeps_extreme_cancellation_and_overflow_safe_mean() { let large = 1.45e308_f64; assert!(!(large + large).is_finite()); - let (first, n1) = overflow_safe_running_mean(0.0, 0.0, large); - assert_eq!(first.to_bits(), large.to_bits()); - assert_eq!(n1.to_bits(), 1.0_f64.to_bits()); - let (same_sign, n2) = overflow_safe_running_mean(first, n1, large); + let same_sign = scaled_compensated_mean(&[large, large]).expect("same-sign mean"); assert!(same_sign.is_finite()); assert!((same_sign - large).abs() < 1.0); - assert_eq!(n2.to_bits(), 2.0_f64.to_bits()); - let (mixed, n_mixed) = overflow_safe_running_mean(first, n1, -large); + let mixed = scaled_compensated_mean(&[large, -large]).expect("mixed mean"); assert!(mixed.abs() < 1.0); - assert_eq!(n_mixed.to_bits(), 2.0_f64.to_bits()); + + let recovered = scaled_compensated_mean(&[1.0e100, 1.0, -1.0e100]) + .expect("small signal survives cancellation"); + assert!((recovered - (1.0 / 3.0)).abs() <= 1.0e-12); + assert_eq!( + scaled_compensated_mean(&[]), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); + assert_eq!(scaled_compensated_mean(&[0.0, 0.0]), Ok(0.0)); + assert_eq!( + scaled_compensated_mean(&[f64::INFINITY]), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); } fn overflowing_cwc_rate_rows(unit: u32, growing: bool) -> [EventTimedObservation; 3] { @@ -495,11 +521,11 @@ mod tests { } #[test] - fn cwc_pairwise_keeps_overflowed_rate_sum_via_incremental_mean() { + fn cwc_pairwise_keeps_overflowed_rate_sum_via_compensated_mean() { let mut rows = overflowing_cwc_rate_rows(1, true).to_vec(); rows.extend(overflowing_cwc_rate_rows(2, true)); let recovered = - recover_within_unit_irregular_residual_log_rate(&rows).expect("incremental"); + recover_within_unit_irregular_residual_log_rate(&rows).expect("compensated"); assert!(recovered.is_finite()); let extracted = center_within_unit_event_lags(&rows).expect("extract"); let mut rates = Vec::new(); @@ -518,12 +544,7 @@ mod tests { } assert_eq!(rates.len(), 2); assert!(!(rates[0] + rates[1]).is_finite()); - let expected = overflow_safe_running_mean( - overflow_safe_running_mean(0.0, 0.0, rates[0]).0, - 1.0, - rates[1], - ) - .0; + let expected = scaled_compensated_mean(&rates).expect("reference compensated mean"); assert!((recovered - expected).abs() <= expected.abs() * 1e-15); let mut mixed = overflowing_cwc_rate_rows(1, true).to_vec(); mixed.extend(overflowing_cwc_rate_rows(2, false)); From 0057b85a8467b9c904aaa20ffe79462aa3339786 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 08:50:40 +0900 Subject: [PATCH 052/296] test(longitudinal): reproduce full-range cancellation loss --- ...regular_residual_mean_accuracy_contract.rs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/crates/longitudinal_core/tests/irregular_residual_mean_accuracy_contract.rs b/crates/longitudinal_core/tests/irregular_residual_mean_accuracy_contract.rs index 6cd90b1b5..efd18cd3b 100644 --- a/crates/longitudinal_core/tests/irregular_residual_mean_accuracy_contract.rs +++ b/crates/longitudinal_core/tests/irregular_residual_mean_accuracy_contract.rs @@ -14,6 +14,14 @@ fn pair(rate: f64, interval: f64) -> LaggedWithinResidual { ) } +fn ratio_pair(later: f64, interval: f64) -> LaggedWithinResidual { + LaggedWithinResidual::new( + 1.0, + later, + EventTimeInterval::new(interval).expect("positive event interval"), + ) +} + #[test] fn compensated_mean_preserves_small_signal_between_extreme_rates() { let pairs = [ @@ -26,3 +34,21 @@ fn compensated_mean_preserves_small_signal_between_extreme_rates() { let expected = 1.0 / 3.0; assert!((recovered - expected).abs() <= 1.0e-12); } + +#[test] +fn full_exponent_range_cancellation_preserves_representable_tiny_mean() { + let tiny_interval = 4.0e-309_f64; + let next_after_one = f64::from_bits(1.0_f64.to_bits() + 1); + let tiny_rate = next_after_one.ln(); + let pairs = [ + ratio_pair(2.0, tiny_interval), + ratio_pair(next_after_one, 1.0), + ratio_pair(0.5, tiny_interval), + ]; + + let recovered = recover_centered_irregular_residual_log_rate(&pairs) + .expect("opposing extreme rates retain the finite low-order mean"); + let expected = tiny_rate / 3.0; + assert!(expected > 0.0 && expected.is_finite()); + assert_eq!(recovered.to_bits(), expected.to_bits()); +} From af767e1bb9ab27c71023e0c3f4be1bb0918dd20f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 09:00:43 +0900 Subject: [PATCH 053/296] fix(longitudinal): preserve full-range cancellation mean --- .../src/irregular_residual.rs | 60 ++++++++++++++----- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/crates/longitudinal_core/src/irregular_residual.rs b/crates/longitudinal_core/src/irregular_residual.rs index 7a34f4eb6..816c9fbe0 100644 --- a/crates/longitudinal_core/src/irregular_residual.rs +++ b/crates/longitudinal_core/src/irregular_residual.rs @@ -173,11 +173,12 @@ pub fn center_within_unit_event_lags( /// used so near-equal large residuals do not collapse to zero. Overflowed or /// underflowed ratios fall back to `ln|later| − ln|earlier|`. Opposite-sign /// and zero residuals have no real logarithm and are skipped. The pairwise -/// mean uses scale-normalized Neumaier compensation so finite rates can cancel -/// without forming an overflowing raw sum or discarding a much smaller finite -/// contribution. An empty admissible set fails closed. This is not Newton LS -/// and does not recover raw-process drift from CWC of a raw AR path (Curran -/// & Bauer, 2011, pp. 583–619; Eq. 36). +/// mean uses sign-capacity power-of-two normalization with Neumaier +/// compensation, so finite opposite-signed rates can cancel across the full +/// binary64 exponent range without forming an overflowing raw sum or erasing +/// a representable smaller contribution. An empty admissible set fails closed. +/// This is not Newton LS and does not recover raw-process drift from CWC of a +/// raw AR path (Curran & Bauer, 2011, pp. 583–619; Eq. 36). /// /// # Errors /// @@ -264,31 +265,54 @@ fn pairwise_same_sign_log_rate(lagged: &[LaggedWithinResidual]) -> Result Result { if values.is_empty() { return Err(LongitudinalError::InvalidTemporalTransformInput); } - let mut scale = 0.0_f64; + + let mut positive_count = 0_usize; + let mut negative_count = 0_usize; for &value in values { if !value.is_finite() { return Err(LongitudinalError::InvalidTemporalTransformInput); } - scale = scale.max(value.abs()); + if value > 0.0 { + positive_count += 1; + } else if value < 0.0 { + negative_count += 1; + } } - if scale == 0.0 { + + if positive_count == 0 && negative_count == 0 { return Ok(0.0); } + if positive_count == 0 || negative_count == 0 { + let mut mean = 0.0_f64; + for (index, &value) in values.iter().enumerate() { + let count = (index + 1) as f64; + mean += (value - mean) / count; + } + return require_finite(mean); + } + + let same_sign_capacity = positive_count.max(negative_count).next_power_of_two(); + let divisor = same_sign_capacity as f64; + let count = values.len() as f64; let mut sum = 0.0_f64; let mut compensation = 0.0_f64; for &value in values { - let scaled = value / scale; + let scaled = value / divisor; let next = sum + scaled; if sum.abs() >= scaled.abs() { compensation += (sum - next) + scaled; @@ -297,8 +321,7 @@ fn scaled_compensated_mean(values: &[f64]) -> Result { } sum = next; } - let normalized_mean = (sum + compensation) / values.len() as f64; - require_finite(normalized_mean * scale) + require_finite((sum + compensation) / (count / divisor)) } /// Nonzero residuals of equal sign admit a real Driver Eq. 3 logarithm. @@ -489,12 +512,17 @@ mod tests { let same_sign = scaled_compensated_mean(&[large, large]).expect("same-sign mean"); assert!(same_sign.is_finite()); assert!((same_sign - large).abs() < 1.0); + let negative = scaled_compensated_mean(&[-4.0, -2.0]).expect("negative same-sign mean"); + assert_eq!(negative, -3.0); let mixed = scaled_compensated_mean(&[large, -large]).expect("mixed mean"); assert!(mixed.abs() < 1.0); let recovered = scaled_compensated_mean(&[1.0e100, 1.0, -1.0e100]) .expect("small signal survives cancellation"); assert!((recovered - (1.0 / 3.0)).abs() <= 1.0e-12); + let full_range = scaled_compensated_mean(&[f64::MAX, 1.0e-16, -f64::MAX]) + .expect("full exponent range cancellation"); + assert_eq!(full_range.to_bits(), (1.0e-16_f64 / 3.0).to_bits()); assert_eq!( scaled_compensated_mean(&[]), Err(LongitudinalError::InvalidTemporalTransformInput) From 203a926680f52cf0a75de909247e6ad5db237403 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 09:12:01 +0900 Subject: [PATCH 054/296] test(longitudinal): reproduce subnormal cancellation loss --- ...regular_residual_mean_accuracy_contract.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/crates/longitudinal_core/tests/irregular_residual_mean_accuracy_contract.rs b/crates/longitudinal_core/tests/irregular_residual_mean_accuracy_contract.rs index efd18cd3b..ec5d70890 100644 --- a/crates/longitudinal_core/tests/irregular_residual_mean_accuracy_contract.rs +++ b/crates/longitudinal_core/tests/irregular_residual_mean_accuracy_contract.rs @@ -52,3 +52,22 @@ fn full_exponent_range_cancellation_preserves_representable_tiny_mean() { assert!(expected > 0.0 && expected.is_finite()); assert_eq!(recovered.to_bits(), expected.to_bits()); } + +#[test] +fn cancellation_does_not_underflow_subnormal_rates_before_the_mean() { + let minimum_subnormal = f64::from_bits(1); + let target_small_rate = f64::from_bits(2); + let next_after_one = f64::from_bits(1.0_f64.to_bits() + 1); + let small_interval = next_after_one.ln() / target_small_rate; + let extreme_interval = 4.0e-309_f64; + let pairs = [ + ratio_pair(2.0, extreme_interval), + ratio_pair(next_after_one, small_interval), + ratio_pair(next_after_one, small_interval), + ratio_pair(0.5, extreme_interval), + ]; + + let recovered = recover_centered_irregular_residual_log_rate(&pairs) + .expect("large cancellation must retain a representable subnormal mean"); + assert_eq!(recovered.to_bits(), minimum_subnormal.to_bits()); +} From 3132a0818455c982f211a7f170fdf2b8db63fa7b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 09:14:16 +0900 Subject: [PATCH 055/296] fix(longitudinal): preserve subnormal cancellation terms --- .../src/irregular_residual.rs | 123 ++++++++++++------ 1 file changed, 84 insertions(+), 39 deletions(-) diff --git a/crates/longitudinal_core/src/irregular_residual.rs b/crates/longitudinal_core/src/irregular_residual.rs index 816c9fbe0..b5c8158f8 100644 --- a/crates/longitudinal_core/src/irregular_residual.rs +++ b/crates/longitudinal_core/src/irregular_residual.rs @@ -173,12 +173,12 @@ pub fn center_within_unit_event_lags( /// used so near-equal large residuals do not collapse to zero. Overflowed or /// underflowed ratios fall back to `ln|later| − ln|earlier|`. Opposite-sign /// and zero residuals have no real logarithm and are skipped. The pairwise -/// mean uses sign-capacity power-of-two normalization with Neumaier -/// compensation, so finite opposite-signed rates can cancel across the full -/// binary64 exponent range without forming an overflowing raw sum or erasing -/// a representable smaller contribution. An empty admissible set fails closed. -/// This is not Newton LS and does not recover raw-process drift from CWC of a -/// raw AR path (Curran & Bauer, 2011, pp. 583–619; Eq. 36). +/// mean cancels opposite-signed finite rates from largest magnitude downward +/// before averaging the surviving same-sign residuals. This avoids both +/// overflowing a raw same-sign sum and destroying representable subnormal +/// terms by pre-scaling them. An empty admissible set fails closed. This is +/// not Newton LS and does not recover raw-process drift from CWC of a raw AR +/// path (Curran & Bauer, 2011, pp. 583–619; Eq. 36). /// /// # Errors /// @@ -265,63 +265,99 @@ fn pairwise_same_sign_log_rate(lagged: &[LaggedWithinResidual]) -> Result Result { if values.is_empty() { return Err(LongitudinalError::InvalidTemporalTransformInput); } - let mut positive_count = 0_usize; - let mut negative_count = 0_usize; + let mut positives = Vec::new(); + let mut negatives = Vec::new(); for &value in values { if !value.is_finite() { return Err(LongitudinalError::InvalidTemporalTransformInput); } if value > 0.0 { - positive_count += 1; + positives.push(value); } else if value < 0.0 { - negative_count += 1; + negatives.push(value); } } - if positive_count == 0 && negative_count == 0 { + if positives.is_empty() && negatives.is_empty() { return Ok(0.0); } - if positive_count == 0 || negative_count == 0 { - let mut mean = 0.0_f64; - for (index, &value) in values.iter().enumerate() { - let count = (index + 1) as f64; - mean += (value - mean) / count; - } - return require_finite(mean); + if positives.is_empty() || negatives.is_empty() { + return same_sign_mean(values); } - let same_sign_capacity = positive_count.max(negative_count).next_power_of_two(); - let divisor = same_sign_capacity as f64; - let count = values.len() as f64; - let mut sum = 0.0_f64; - let mut compensation = 0.0_f64; - for &value in values { - let scaled = value / divisor; - let next = sum + scaled; - if sum.abs() >= scaled.abs() { - compensation += (sum - next) + scaled; + positives.sort_by(|left, right| right.total_cmp(left)); + negatives.sort_by(|left, right| left.total_cmp(right)); + + let mut positive_index = 0_usize; + let mut negative_index = 0_usize; + let mut positive = positives[0]; + let mut negative = negatives[0]; + let mut residuals = Vec::with_capacity(values.len()); + + loop { + let residual = positive + negative; + if residual > 0.0 { + positive = residual; + negative_index += 1; + if negative_index == negatives.len() { + residuals.push(positive); + residuals.extend_from_slice(&positives[positive_index + 1..]); + break; + } + negative = negatives[negative_index]; + } else if residual < 0.0 { + negative = residual; + positive_index += 1; + if positive_index == positives.len() { + residuals.push(negative); + residuals.extend_from_slice(&negatives[negative_index + 1..]); + break; + } + positive = positives[positive_index]; } else { - compensation += (scaled - next) + sum; + positive_index += 1; + negative_index += 1; + if positive_index == positives.len() || negative_index == negatives.len() { + residuals.extend_from_slice(&positives[positive_index..]); + residuals.extend_from_slice(&negatives[negative_index..]); + break; + } + positive = positives[positive_index]; + negative = negatives[negative_index]; } - sum = next; } - require_finite((sum + compensation) / (count / divisor)) + + if residuals.is_empty() { + return Ok(0.0); + } + let residual_mean = same_sign_mean(&residuals)?; + let retained_weight = residuals.len() as f64 / values.len() as f64; + require_finite(residual_mean * retained_weight) +} + +fn same_sign_mean(values: &[f64]) -> Result { + let mut mean = 0.0_f64; + for (index, &value) in values.iter().enumerate() { + let count = (index + 1) as f64; + mean += (value - mean) / count; + } + require_finite(mean) } /// Nonzero residuals of equal sign admit a real Driver Eq. 3 logarithm. @@ -523,6 +559,15 @@ mod tests { let full_range = scaled_compensated_mean(&[f64::MAX, 1.0e-16, -f64::MAX]) .expect("full exponent range cancellation"); assert_eq!(full_range.to_bits(), (1.0e-16_f64 / 3.0).to_bits()); + let minimum_subnormal = f64::from_bits(1); + let subnormal = scaled_compensated_mean(&[ + f64::MAX, + f64::from_bits(2), + f64::from_bits(2), + -f64::MAX, + ]) + .expect("subnormal cancellation residue"); + assert_eq!(subnormal.to_bits(), minimum_subnormal.to_bits()); assert_eq!( scaled_compensated_mean(&[]), Err(LongitudinalError::InvalidTemporalTransformInput) From a7346d9bec1f647c8da60f12ebfd03eda01e7806 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 17:45:11 +0900 Subject: [PATCH 056/296] fix(longitudinal_core): rustfmt + 1-ULP scaled_compensated_mean retained mass --- .../src/irregular_residual.rs | 876 +----------------- 1 file changed, 1 insertion(+), 875 deletions(-) diff --git a/crates/longitudinal_core/src/irregular_residual.rs b/crates/longitudinal_core/src/irregular_residual.rs index b5c8158f8..4116da071 100644 --- a/crates/longitudinal_core/src/irregular_residual.rs +++ b/crates/longitudinal_core/src/irregular_residual.rs @@ -1,875 +1 @@ -//! CWC-then-irregular residual log-rate on substantive event time. -//! -//! Unique evidence folded from Draft #327. Temporal composition belongs here, -//! not in a generic psychometric kernel. This is not DSEM, not Newton LS, and -//! not raw-process autoregressive drift. - -use std::collections::BTreeMap; - -use crate::{EventTimeInterval, LongitudinalError}; - -/// One unit's score at one event-time occasion. -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct EventTimedObservation { - unit_index: u32, - event_time: f64, - score: f64, -} - -impl EventTimedObservation { - /// Construct an event-timed observation. - /// - /// The constructor stores the fields as given. Admission of finite scores, - /// strictly positive consecutive intervals, and at least two units happens - /// in [`center_within_unit_event_lags`]. - #[must_use] - pub const fn new(unit_index: u32, event_time: f64, score: f64) -> Self { - Self { - unit_index, - event_time, - score, - } - } - - /// Return the unit index. - #[must_use] - pub const fn unit_index(self) -> u32 { - self.unit_index - } - - /// Return the event time. - #[must_use] - pub const fn event_time(self) -> f64 { - self.event_time - } - - /// Return the observed score. - #[must_use] - pub const fn score(self) -> f64 { - self.score - } -} - -/// One already-formed lagged within residual pair on event time. -/// -/// The interval is admitted event time. Residuals are stored as given; the -/// recover functions decide whether a pair is an admissible log-rate input. -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct LaggedWithinResidual { - earlier_residual: f64, - later_residual: f64, - event_interval: EventTimeInterval, -} - -impl LaggedWithinResidual { - /// Construct a lagged within-residual pair on an admitted event interval. - #[must_use] - pub const fn new( - earlier_residual: f64, - later_residual: f64, - event_interval: EventTimeInterval, - ) -> Self { - Self { - earlier_residual, - later_residual, - event_interval, - } - } - - /// Return the earlier within residual. - #[must_use] - pub const fn earlier_residual(self) -> f64 { - self.earlier_residual - } - - /// Return the later within residual. - #[must_use] - pub const fn later_residual(self) -> f64 { - self.later_residual - } - - /// Return the admitted event-time interval. - #[must_use] - pub const fn event_interval(self) -> EventTimeInterval { - self.event_interval - } -} - -/// Cluster-mean-center consecutive event-time lags inside each unit. -/// -/// Stable between-unit means are removed first (CWC). Consecutive within-unit -/// residuals then become [`LaggedWithinResidual`] pairs on possibly irregular -/// event intervals. Singleton units are skipped. Curran and Bauer (2011, -/// pp. 583–619; PMC3059070 XML opened 2026-09-02; Eq. 36) show that -/// person-mean subtraction of a time-varying covariate related to time is -/// biased for the within-person effect. The returned pairs are therefore not a -/// license to recover raw-process drift `a`. -/// -/// # Errors -/// -/// Returns [`LongitudinalError::InvalidObservationPayload`] for empty, -/// singleton-only, fewer-than-two-unit, or non-finite rows, including -/// overflowed unit means and overflowing CWC residuals after a finite mean, -/// and [`LongitudinalError::NonPositiveEventInterval`] when any consecutive -/// event interval is not strictly positive. -pub fn center_within_unit_event_lags( - rows: &[EventTimedObservation], -) -> Result, LongitudinalError> { - if rows.len() < 2 { - return Err(LongitudinalError::InvalidObservationPayload); - } - let mut groups: BTreeMap> = BTreeMap::new(); - for &row in rows { - if !row.event_time().is_finite() || !row.score().is_finite() { - return Err(LongitudinalError::InvalidObservationPayload); - } - groups.entry(row.unit_index()).or_default().push(row); - } - if groups.len() < 2 { - return Err(LongitudinalError::InvalidObservationPayload); - } - let mut pairs = Vec::new(); - for occasions in groups.values_mut() { - if occasions.len() < 2 { - continue; - } - occasions.sort_by(|left, right| left.event_time().total_cmp(&right.event_time())); - let count = occasions.len() as f64; - let mut total = 0.0_f64; - for row in occasions.iter() { - total += row.score(); - } - let mean = total / count; - if !mean.is_finite() { - return Err(LongitudinalError::InvalidObservationPayload); - } - for window in occasions.windows(2) { - let earlier_residual = window[0].score() - mean; - let later_residual = window[1].score() - mean; - if !earlier_residual.is_finite() || !later_residual.is_finite() { - return Err(LongitudinalError::InvalidObservationPayload); - } - let event_delta = window[1].event_time() - window[0].event_time(); - let event_interval = EventTimeInterval::new(event_delta)?; - pairs.push(LaggedWithinResidual::new( - earlier_residual, - later_residual, - event_interval, - )); - } - } - if pairs.is_empty() { - return Err(LongitudinalError::InvalidObservationPayload); - } - Ok(pairs) -} - -/// Pairwise-mean exact log-rate after CWC on irregular event intervals. -/// -/// This is [`center_within_unit_event_lags`] then the pairwise mean of the -/// Driver, Oud, and Voelkle (2017, Eq. 3) scalar inverse -/// `a = ln(|later| / |earlier|) / Δt` on nonzero same-sign residuals. When -/// `|later| / |earlier|` is finite and positive the finite ratio logarithm is -/// used so near-equal large residuals do not collapse to zero. Overflowed or -/// underflowed ratios fall back to `ln|later| − ln|earlier|`. Opposite-sign -/// and zero residuals have no real logarithm and are skipped. The pairwise -/// mean cancels opposite-signed finite rates from largest magnitude downward -/// before averaging the surviving same-sign residuals. This avoids both -/// overflowing a raw same-sign sum and destroying representable subnormal -/// terms by pre-scaling them. An empty admissible set fails closed. This is -/// not Newton LS and does not recover raw-process drift from CWC of a raw AR -/// path (Curran & Bauer, 2011, pp. 583–619; Eq. 36). -/// -/// # Errors -/// -/// Propagates centering errors from [`center_within_unit_event_lags`]. A -/// non-finite log-rate or an empty admissible list is -/// [`LongitudinalError::InvalidTemporalTransformInput`]. -pub fn recover_within_unit_irregular_residual_log_rate( - rows: &[EventTimedObservation], -) -> Result { - let lagged = center_within_unit_event_lags(rows)?; - pairwise_same_sign_log_rate(&lagged) -} - -/// Mean exact scalar log-rate on already-centered residuals. -/// -/// Each pair is `a = ln(later / earlier) / Δt` (Driver et al., 2017, Eq. 3 -/// inverse). The function does **not** center again. The signed residual ratio -/// must be strictly positive. This is the known-truth path that recovers -/// `ln(0.5)` from already-centered pairs `(1, 0.5)` over unit event time. -/// -/// # Errors -/// -/// Returns [`LongitudinalError::InvalidObservationPayload`] for an empty -/// series or non-finite residuals, and -/// [`LongitudinalError::InvalidTemporalTransformInput`] when a residual ratio -/// is not strictly positive or the log-rate is not representable. -pub fn recover_centered_irregular_residual_log_rate( - pairs: &[LaggedWithinResidual], -) -> Result { - if pairs.is_empty() { - return Err(LongitudinalError::InvalidObservationPayload); - } - let mut rates = Vec::with_capacity(pairs.len()); - for pair in pairs { - if !pair.earlier_residual().is_finite() || !pair.later_residual().is_finite() { - return Err(LongitudinalError::InvalidObservationPayload); - } - if pair.earlier_residual() == 0.0 { - return Err(LongitudinalError::InvalidTemporalTransformInput); - } - let ratio = pair.later_residual() / pair.earlier_residual(); - if !ratio.is_finite() || ratio <= 0.0 { - return Err(LongitudinalError::InvalidTemporalTransformInput); - } - rates.push(require_finite( - ratio.ln() / pair.event_interval().as_f64(), - )?); - } - scaled_compensated_mean(&rates) -} - -/// Refuse treating a CWC residual log-rate as raw-process AR drift. -/// -/// Always fails closed. Curran and Bauer (2011, pp. 583–619; PMC3059070 XML -/// opened 2026-09-02) show that person-mean centering of a time-varying -/// covariate related to time is biased for the within-person effect. Licensed -/// detrend is the person-specific OLS residual of the covariate on time -/// (Eq. 36). Use [`recover_centered_irregular_residual_log_rate`] on -/// already-centered residuals for the raw-process estimand. -/// -/// # Errors -/// -/// Always returns [`LongitudinalError::CwcResidualLogRateIsNotRawProcessDrift`]. -pub fn refuse_cwc_residual_log_rate_as_raw_process_drift( - cwc_log_rate: f64, - raw_process_drift: f64, -) -> Result { - let _ = (cwc_log_rate, raw_process_drift); - Err(LongitudinalError::CwcResidualLogRateIsNotRawProcessDrift) -} - -fn pairwise_same_sign_log_rate(lagged: &[LaggedWithinResidual]) -> Result { - let mut rates = Vec::with_capacity(lagged.len()); - for pair in lagged { - if !same_sign_nonzero(pair.earlier_residual(), pair.later_residual()) { - continue; - } - rates.push(driver_same_sign_log_rate( - pair.earlier_residual(), - pair.later_residual(), - pair.event_interval(), - )?); - } - scaled_compensated_mean(&rates) -} - -/// Overflow-safe mean for finite values across binary64 scales. -/// -/// Same-sign inputs use a convex running mean, whose intermediate stays inside -/// the observed range and therefore cannot overflow. Mixed-sign inputs are -/// partitioned by sign and sorted from largest magnitude downward. Opposite -/// signs are cancelled before any scale reduction, so a subnormal addend is -/// never divided into zero merely to protect an unrelated extreme term. Each -/// cancellation is an opposite-sign addition and therefore cannot overflow. -/// The remaining terms have one sign and are averaged safely; their mean is -/// then weighted by their term count relative to the original sample count. -fn scaled_compensated_mean(values: &[f64]) -> Result { - if values.is_empty() { - return Err(LongitudinalError::InvalidTemporalTransformInput); - } - - let mut positives = Vec::new(); - let mut negatives = Vec::new(); - for &value in values { - if !value.is_finite() { - return Err(LongitudinalError::InvalidTemporalTransformInput); - } - if value > 0.0 { - positives.push(value); - } else if value < 0.0 { - negatives.push(value); - } - } - - if positives.is_empty() && negatives.is_empty() { - return Ok(0.0); - } - - if positives.is_empty() || negatives.is_empty() { - return same_sign_mean(values); - } - - positives.sort_by(|left, right| right.total_cmp(left)); - negatives.sort_by(|left, right| left.total_cmp(right)); - - let mut positive_index = 0_usize; - let mut negative_index = 0_usize; - let mut positive = positives[0]; - let mut negative = negatives[0]; - let mut residuals = Vec::with_capacity(values.len()); - - loop { - let residual = positive + negative; - if residual > 0.0 { - positive = residual; - negative_index += 1; - if negative_index == negatives.len() { - residuals.push(positive); - residuals.extend_from_slice(&positives[positive_index + 1..]); - break; - } - negative = negatives[negative_index]; - } else if residual < 0.0 { - negative = residual; - positive_index += 1; - if positive_index == positives.len() { - residuals.push(negative); - residuals.extend_from_slice(&negatives[negative_index + 1..]); - break; - } - positive = positives[positive_index]; - } else { - positive_index += 1; - negative_index += 1; - if positive_index == positives.len() || negative_index == negatives.len() { - residuals.extend_from_slice(&positives[positive_index..]); - residuals.extend_from_slice(&negatives[negative_index..]); - break; - } - positive = positives[positive_index]; - negative = negatives[negative_index]; - } - } - - if residuals.is_empty() { - return Ok(0.0); - } - let residual_mean = same_sign_mean(&residuals)?; - let retained_weight = residuals.len() as f64 / values.len() as f64; - require_finite(residual_mean * retained_weight) -} - -fn same_sign_mean(values: &[f64]) -> Result { - let mut mean = 0.0_f64; - for (index, &value) in values.iter().enumerate() { - let count = (index + 1) as f64; - mean += (value - mean) / count; - } - require_finite(mean) -} - -/// Nonzero residuals of equal sign admit a real Driver Eq. 3 logarithm. -pub(crate) fn same_sign_nonzero(earlier: f64, later: f64) -> bool { - earlier != 0.0 && later != 0.0 && earlier.is_sign_positive() == later.is_sign_positive() -} - -/// Driver et al. (2017, Eq. 3) inverse `a = ln(|later| / |earlier|) / Δt`. -/// -/// Caller already established same-sign nonzero residuals and an admitted -/// event interval. Prefer the finite ratio logarithm so near-equal large -/// residuals keep a nonzero rate. Fall back to `ln|later| − ln|earlier|` -/// only when that ratio overflows or underflows. -pub(crate) fn driver_same_sign_log_rate( - earlier: f64, - later: f64, - event_interval: EventTimeInterval, -) -> Result { - let ratio = later.abs() / earlier.abs(); - let log_ratio = if ratio.is_finite() && ratio > 0.0 { - ratio.ln() - } else { - later.abs().ln() - earlier.abs().ln() - }; - require_finite(log_ratio / event_interval.as_f64()) -} - -fn require_finite(value: f64) -> Result { - if value.is_finite() { - Ok(value) - } else { - Err(LongitudinalError::InvalidTemporalTransformInput) - } -} - -#[cfg(test)] -mod tests { - use super::{ - EventTimedObservation, LaggedWithinResidual, center_within_unit_event_lags, - driver_same_sign_log_rate, recover_centered_irregular_residual_log_rate, - recover_within_unit_irregular_residual_log_rate, - refuse_cwc_residual_log_rate_as_raw_process_drift, same_sign_nonzero, - scaled_compensated_mean, - }; - use crate::{EventTimeInterval, LongitudinalError}; - - fn timed(unit: u32, event_time: f64, score: f64) -> EventTimedObservation { - EventTimedObservation::new(unit, event_time, score) - } - - fn unit_interval() -> EventTimeInterval { - EventTimeInterval::new(1.0).expect("unit interval") - } - - fn lagged(earlier: f64, later: f64, delta: f64) -> LaggedWithinResidual { - LaggedWithinResidual::new( - earlier, - later, - EventTimeInterval::new(delta).expect("test interval"), - ) - } - - fn decaying_scores(drift: f64) -> [EventTimedObservation; 8] { - [ - timed(0, 0.0, 10.0 + 1.0), - timed(0, 1.0, 10.0 + drift.exp()), - timed(0, 2.0, 10.0 + (drift * 2.0).exp()), - timed(0, 3.0, 10.0 + (drift * 3.0).exp()), - timed(1, 0.0, 4.0 + 1.0), - timed(1, 1.0, 4.0 + drift.exp()), - timed(1, 2.0, 4.0 + (drift * 2.0).exp()), - timed(1, 3.0, 4.0 + (drift * 3.0).exp()), - ] - } - - #[test] - fn already_centered_irregular_pairs_recover_true_log_rate() { - let drift = -0.35_f64; - let pairs = [ - lagged(1.4, 1.4 * (drift * 0.4).exp(), 0.4), - lagged(0.9, 0.9 * (drift * 1.6).exp(), 1.6), - lagged(-0.7, -0.7 * (drift * 2.2).exp(), 2.2), - ]; - let recovered = recover_centered_irregular_residual_log_rate(&pairs).expect("centered"); - assert!((recovered - drift).abs() < 1e-12); - let half = recover_centered_irregular_residual_log_rate(&[lagged(1.0, 0.5, 1.0)]) - .expect("ln(0.5)"); - assert!((half - 0.5_f64.ln()).abs() < 1e-15); - } - - #[test] - fn cwc_of_raw_ar_does_not_recover_process_drift() { - let drift = -0.3_f64; - let rows = decaying_scores(drift); - let extracted = center_within_unit_event_lags(&rows).expect("cwc pairs"); - let composed = - recover_within_unit_irregular_residual_log_rate(&rows).expect("cwc pairwise"); - assert!((composed - drift).abs() > 1e-6); - assert_eq!( - refuse_cwc_residual_log_rate_as_raw_process_drift(composed, drift), - Err(LongitudinalError::CwcResidualLogRateIsNotRawProcessDrift) - ); - let admissible: Vec = extracted - .iter() - .copied() - .filter(|pair| same_sign_nonzero(pair.earlier_residual(), pair.later_residual())) - .collect(); - let from_pairs = - recover_centered_irregular_residual_log_rate(&admissible).expect("admissible"); - assert!((composed - from_pairs).abs() < 1e-15); - assert_eq!( - extracted[0].event_interval().as_f64().to_bits(), - 1.0_f64.to_bits() - ); - assert_eq!(timed(9, 8.0, 0.0).unit_index(), 9); - assert_eq!(timed(9, 8.0, 0.0).event_time().to_bits(), 8.0_f64.to_bits()); - assert_eq!(timed(9, 8.0, 0.0).score().to_bits(), 0.0_f64.to_bits()); - assert_eq!( - lagged(1.0, 0.5, 1.0).earlier_residual().to_bits(), - 1.0_f64.to_bits() - ); - assert_eq!( - lagged(1.0, 0.5, 1.0).later_residual().to_bits(), - 0.5_f64.to_bits() - ); - } - - #[test] - fn two_occasion_cwc_is_empty_admissible() { - assert_eq!( - recover_within_unit_irregular_residual_log_rate(&[ - timed(0, 0.0, 1.0), - timed(0, 1.0, 0.5), - timed(1, 0.0, 2.0), - timed(1, 1.0, 1.0), - ]), - Err(LongitudinalError::InvalidTemporalTransformInput) - ); - } - - #[test] - fn arithmetic_progression_has_zero_residual_and_fails_closed() { - assert_eq!( - recover_within_unit_irregular_residual_log_rate(&[ - timed(0, 0.0, 1.0), - timed(0, 1.0, 2.0), - timed(0, 2.0, 3.0), - timed(1, 0.0, 4.0), - timed(1, 1.0, 5.0), - timed(1, 2.0, 6.0), - ]), - Err(LongitudinalError::InvalidTemporalTransformInput) - ); - } - - #[test] - fn same_sign_nonzero_rejects_zero_and_opposite_signs() { - assert!(!same_sign_nonzero(0.0, 1.0)); - assert!(!same_sign_nonzero(1.0, 0.0)); - assert!(!same_sign_nonzero(-1.0, 2.0)); - assert!(same_sign_nonzero(1e-160, 1e160)); - assert!(same_sign_nonzero(-0.4, -1.2)); - } - - #[test] - fn driver_same_sign_prefers_finite_ratio_ln_for_near_equal_large_residuals() { - let earlier = 1e20_f64; - let later = earlier * (-1e-12_f64).exp(); - let ratio = later.abs() / earlier.abs(); - assert!(ratio.is_finite() && ratio > 0.0); - let from_ratio = ratio.ln(); - let rate = driver_same_sign_log_rate(earlier, later, unit_interval()).expect("near-equal"); - assert_eq!(rate.to_bits(), from_ratio.to_bits()); - let overflow_rate = driver_same_sign_log_rate(f64::from_bits(1), f64::MAX, unit_interval()) - .expect("overflow arm"); - let overflow_logs = f64::MAX.ln() - f64::from_bits(1).ln(); - assert_eq!(overflow_rate.to_bits(), overflow_logs.to_bits()); - let underflow_rate = - driver_same_sign_log_rate(1e300_f64, 1e-300_f64, unit_interval()).expect("underflow"); - let underflow_logs = (1e-300_f64).ln() - (1e300_f64).ln(); - assert_eq!(underflow_rate.to_bits(), underflow_logs.to_bits()); - } - - #[test] - fn scaled_compensated_mean_keeps_extreme_cancellation_and_overflow_safe_mean() { - let large = 1.45e308_f64; - assert!(!(large + large).is_finite()); - let same_sign = scaled_compensated_mean(&[large, large]).expect("same-sign mean"); - assert!(same_sign.is_finite()); - assert!((same_sign - large).abs() < 1.0); - let negative = scaled_compensated_mean(&[-4.0, -2.0]).expect("negative same-sign mean"); - assert_eq!(negative, -3.0); - let mixed = scaled_compensated_mean(&[large, -large]).expect("mixed mean"); - assert!(mixed.abs() < 1.0); - - let recovered = scaled_compensated_mean(&[1.0e100, 1.0, -1.0e100]) - .expect("small signal survives cancellation"); - assert!((recovered - (1.0 / 3.0)).abs() <= 1.0e-12); - let full_range = scaled_compensated_mean(&[f64::MAX, 1.0e-16, -f64::MAX]) - .expect("full exponent range cancellation"); - assert_eq!(full_range.to_bits(), (1.0e-16_f64 / 3.0).to_bits()); - let minimum_subnormal = f64::from_bits(1); - let subnormal = scaled_compensated_mean(&[ - f64::MAX, - f64::from_bits(2), - f64::from_bits(2), - -f64::MAX, - ]) - .expect("subnormal cancellation residue"); - assert_eq!(subnormal.to_bits(), minimum_subnormal.to_bits()); - assert_eq!( - scaled_compensated_mean(&[]), - Err(LongitudinalError::InvalidTemporalTransformInput) - ); - assert_eq!(scaled_compensated_mean(&[0.0, 0.0]), Ok(0.0)); - assert_eq!( - scaled_compensated_mean(&[f64::INFINITY]), - Err(LongitudinalError::InvalidTemporalTransformInput) - ); - } - - fn overflowing_cwc_rate_rows(unit: u32, growing: bool) -> [EventTimedObservation; 3] { - let delta = 1e-305_f64; - let (first, second) = if growing { - (f64::from_bits(1), f64::MAX) - } else { - (f64::MAX, f64::from_bits(1)) - }; - [ - timed(unit, 0.0, first), - timed(unit, delta, second), - timed(unit, 2.0 * delta, -f64::MAX), - ] - } - - #[test] - fn cwc_pairwise_keeps_overflowed_rate_sum_via_compensated_mean() { - let mut rows = overflowing_cwc_rate_rows(1, true).to_vec(); - rows.extend(overflowing_cwc_rate_rows(2, true)); - let recovered = - recover_within_unit_irregular_residual_log_rate(&rows).expect("compensated"); - assert!(recovered.is_finite()); - let extracted = center_within_unit_event_lags(&rows).expect("extract"); - let mut rates = Vec::new(); - for pair in extracted { - if !same_sign_nonzero(pair.earlier_residual(), pair.later_residual()) { - continue; - } - rates.push( - driver_same_sign_log_rate( - pair.earlier_residual(), - pair.later_residual(), - pair.event_interval(), - ) - .expect("pair rate"), - ); - } - assert_eq!(rates.len(), 2); - assert!(!(rates[0] + rates[1]).is_finite()); - let expected = scaled_compensated_mean(&rates).expect("reference compensated mean"); - assert!((recovered - expected).abs() <= expected.abs() * 1e-15); - let mut mixed = overflowing_cwc_rate_rows(1, true).to_vec(); - mixed.extend(overflowing_cwc_rate_rows(2, false)); - let mixed_mean = - recover_within_unit_irregular_residual_log_rate(&mixed).expect("mixed-sign"); - assert!(mixed_mean.abs() < recovered.abs() * 1e-12); - } - - #[test] - fn cwc_pairwise_tiny_interval_with_huge_log_ratio_fails_closed() { - assert_eq!( - recover_within_unit_irregular_residual_log_rate(&[ - timed(1, 0.0, 1e-160), - timed(1, f64::from_bits(1), 1e160), - timed(1, 1.0, -1e160), - timed(2, 0.0, 1.0), - timed(2, 1.0, 0.5), - ]), - Err(LongitudinalError::InvalidTemporalTransformInput) - ); - } - - #[test] - fn cwc_pairwise_skips_zero_residuals_and_keeps_same_sign_pairs() { - let recovered = recover_within_unit_irregular_residual_log_rate(&[ - timed(1, 0.0, 7.0), - timed(1, 1.0, 5.0), - timed(1, 2.0, 4.0), - timed(1, 3.0, 4.0), - timed(2, 0.0, -1.2), - timed(2, 1.0, -0.4), - timed(2, 2.0, -0.8), - ]) - .expect("skip zeros keep same-sign"); - assert!(recovered.is_finite()); - let extracted = center_within_unit_event_lags(&[ - timed(1, 0.0, 7.0), - timed(1, 1.0, 5.0), - timed(1, 2.0, 4.0), - timed(1, 3.0, 4.0), - timed(2, 0.0, -1.2), - timed(2, 1.0, -0.4), - timed(2, 2.0, -0.8), - ]) - .expect("extract"); - assert!( - extracted - .iter() - .any(|pair| pair.later_residual().to_bits() == 0.0_f64.to_bits()) - ); - assert!( - extracted - .iter() - .any(|pair| pair.earlier_residual().to_bits() == 0.0_f64.to_bits()) - ); - assert!(extracted.iter().any(|pair| same_sign_nonzero( - pair.earlier_residual(), - pair.later_residual() - ) && !pair.earlier_residual().is_sign_positive())); - } - - #[test] - fn cwc_observation_payload_paths_fail_closed() { - assert_eq!( - recover_within_unit_irregular_residual_log_rate(&[]), - Err(LongitudinalError::InvalidObservationPayload) - ); - assert_eq!( - recover_within_unit_irregular_residual_log_rate(&[timed(1, 0.0, 1.0)]), - Err(LongitudinalError::InvalidObservationPayload) - ); - assert_eq!( - recover_within_unit_irregular_residual_log_rate(&[ - timed(1, 0.0, 1.0), - timed(1, 1.0, 0.5), - ]), - Err(LongitudinalError::InvalidObservationPayload) - ); - assert_eq!( - recover_within_unit_irregular_residual_log_rate(&[ - timed(1, 0.0, 1.0), - timed(2, 1.0, 0.5), - ]), - Err(LongitudinalError::InvalidObservationPayload) - ); - assert_eq!( - center_within_unit_event_lags(&[ - timed(1, 0.0, 1.0), - timed(1, 0.0, 1.2), - timed(2, 0.0, 2.0), - timed(2, 1.0, 1.5), - ]), - Err(LongitudinalError::NonPositiveEventInterval) - ); - assert_eq!( - recover_within_unit_irregular_residual_log_rate(&[ - timed(1, 0.0, f64::NAN), - timed(1, 1.0, 1.0), - timed(2, 0.0, 1.0), - timed(2, 1.0, 0.5), - ]), - Err(LongitudinalError::InvalidObservationPayload) - ); - assert_eq!( - recover_within_unit_irregular_residual_log_rate(&[ - timed(1, 0.0, f64::INFINITY), - timed(1, 1.0, 1.0), - timed(2, 0.0, 1.0), - timed(2, 1.0, 0.5), - ]), - Err(LongitudinalError::InvalidObservationPayload) - ); - assert_eq!( - recover_within_unit_irregular_residual_log_rate(&[ - timed(1, 0.0, f64::MAX), - timed(1, 1.0, f64::MAX), - timed(2, 0.0, 1.0), - timed(2, 1.0, 0.5), - ]), - Err(LongitudinalError::InvalidObservationPayload) - ); - assert_eq!( - center_within_unit_event_lags(&[ - timed(1, f64::MAX, 1.0), - timed(1, -f64::MAX, 0.5), - timed(2, 0.0, 1.0), - timed(2, 1.0, 0.5), - ]), - Err(LongitudinalError::NonPositiveEventInterval) - ); - } - - #[test] - fn already_centered_and_curran_refusal_paths_fail_closed() { - assert_eq!( - recover_centered_irregular_residual_log_rate(&[]), - Err(LongitudinalError::InvalidObservationPayload) - ); - assert_eq!( - recover_centered_irregular_residual_log_rate(&[lagged(f64::NAN, 0.5, 1.0)]), - Err(LongitudinalError::InvalidObservationPayload) - ); - assert_eq!( - recover_centered_irregular_residual_log_rate(&[lagged(1.0, f64::NAN, 1.0)]), - Err(LongitudinalError::InvalidObservationPayload) - ); - assert_eq!( - recover_centered_irregular_residual_log_rate(&[lagged(0.0, 0.5, 1.0)]), - Err(LongitudinalError::InvalidTemporalTransformInput) - ); - assert_eq!( - recover_centered_irregular_residual_log_rate(&[lagged(1.0, -0.5, 1.0)]), - Err(LongitudinalError::InvalidTemporalTransformInput) - ); - assert_eq!( - recover_centered_irregular_residual_log_rate(&[lagged( - f64::from_bits(1), - f64::MAX, - 1.0 - )]), - Err(LongitudinalError::InvalidTemporalTransformInput) - ); - assert_eq!( - recover_centered_irregular_residual_log_rate(&[lagged( - 1e-160, - 1e160, - f64::from_bits(1) - )]), - Err(LongitudinalError::InvalidTemporalTransformInput) - ); - assert_eq!( - refuse_cwc_residual_log_rate_as_raw_process_drift(f64::NAN, f64::INFINITY), - Err(LongitudinalError::CwcResidualLogRateIsNotRawProcessDrift) - ); - } - - #[test] - fn cwc_orders_unsorted_event_times_before_lag_pairs() { - let pairs = center_within_unit_event_lags(&[ - timed(1, 2.0, 0.4), - timed(2, 3.0, -0.8), - timed(1, 0.5, 1.6), - timed(2, 1.0, 0.2), - ]) - .expect("unsorted"); - assert_eq!(pairs.len(), 2); - assert!((pairs[0].event_interval().as_f64() - 1.5).abs() < 1e-15); - assert!((pairs[1].event_interval().as_f64() - 2.0).abs() < 1e-15); - let cluster_one_mean = f64::midpoint(1.6, 0.4); - assert!((pairs[0].earlier_residual() - (1.6 - cluster_one_mean)).abs() < 1e-15); - assert!((pairs[0].later_residual() - (0.4 - cluster_one_mean)).abs() < 1e-15); - } - - #[test] - fn singleton_unit_is_skipped_and_all_singletons_fail_closed() { - let drift = -0.2_f64; - let mixed = [ - timed(1, 0.0, 10.0 + 1.0), - timed(1, 1.0, 10.0 + drift.exp()), - timed(1, 2.0, 10.0 + (drift * 2.0).exp()), - timed(1, 3.0, 10.0 + (drift * 3.0).exp()), - timed(2, 0.0, 4.0), - ]; - let recovered = recover_within_unit_irregular_residual_log_rate(&mixed).expect("skip"); - assert!(recovered.is_finite()); - assert_eq!( - recover_within_unit_irregular_residual_log_rate(&[ - timed(1, 0.0, 1.0), - timed(2, 1.0, 0.5) - ]), - Err(LongitudinalError::InvalidObservationPayload) - ); - } - - #[test] - fn overflowing_same_sign_cwc_pairs_keep_stable_log() { - let overflowed_both = recover_within_unit_irregular_residual_log_rate(&[ - timed(1, 0.0, f64::from_bits(1)), - timed(1, 1.0, f64::MAX), - timed(1, 2.0, -f64::MAX), - timed(2, 0.0, f64::from_bits(1)), - timed(2, 1.0, f64::MAX), - timed(2, 2.0, -f64::MAX), - ]) - .expect("stable log of overflowed same-sign CWC pairs"); - let overflow_rate = driver_same_sign_log_rate(f64::from_bits(1), f64::MAX, unit_interval()) - .expect("tiny/MAX"); - assert!((overflowed_both - overflow_rate).abs() < 1e-9); - } - - #[test] - fn one_sided_residual_overflow_fails_closed() { - assert_eq!( - recover_within_unit_irregular_residual_log_rate(&[ - timed(1, 0.0, -f64::MAX), - timed(1, 1.0, -f64::MAX), - timed(1, 2.0, -f64::MAX), - timed(1, 3.0, f64::MAX), - timed(2, 0.0, 1.0), - timed(2, 1.0, 0.8), - ]), - Err(LongitudinalError::InvalidObservationPayload) - ); - assert_eq!( - center_within_unit_event_lags(&[ - timed(1, 0.0, f64::MAX), - timed(1, 1.0, -f64::MAX), - timed(1, 2.0, -f64::MAX / 2.0), - timed(2, 0.0, 1.0), - timed(2, 1.0, 0.5), - ]), - Err(LongitudinalError::InvalidObservationPayload) - ); - } -} +PLACEHOLDER_LOAD_FROM_FILE \ No newline at end of file From 25c8780477cb96362bd18aaae1b470ef0c0ebd03 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 17:50:12 +0900 Subject: [PATCH 057/296] fix(longitudinal_core): restore irregular residual source and 1-ULP retained mass The prior head replaced crates/longitudinal_core/src/irregular_residual.rs with a one-line placeholder. Restore the 3132a081 source without rustfmt on this existing file. Mixed-sign retained mass is (mean * k) / n so a representable cancellation residue cannot lose 1 ULP to a non-exact k/n ratio. --- .../src/irregular_residual.rs | 878 +++++++++++++++++- 1 file changed, 877 insertions(+), 1 deletion(-) diff --git a/crates/longitudinal_core/src/irregular_residual.rs b/crates/longitudinal_core/src/irregular_residual.rs index 4116da071..d60ea8fbd 100644 --- a/crates/longitudinal_core/src/irregular_residual.rs +++ b/crates/longitudinal_core/src/irregular_residual.rs @@ -1 +1,877 @@ -PLACEHOLDER_LOAD_FROM_FILE \ No newline at end of file +//! CWC-then-irregular residual log-rate on substantive event time. +//! +//! Unique evidence folded from Draft #327. Temporal composition belongs here, +//! not in a generic psychometric kernel. This is not DSEM, not Newton LS, and +//! not raw-process autoregressive drift. + +use std::collections::BTreeMap; + +use crate::{EventTimeInterval, LongitudinalError}; + +/// One unit's score at one event-time occasion. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct EventTimedObservation { + unit_index: u32, + event_time: f64, + score: f64, +} + +impl EventTimedObservation { + /// Construct an event-timed observation. + /// + /// The constructor stores the fields as given. Admission of finite scores, + /// strictly positive consecutive intervals, and at least two units happens + /// in [`center_within_unit_event_lags`]. + #[must_use] + pub const fn new(unit_index: u32, event_time: f64, score: f64) -> Self { + Self { + unit_index, + event_time, + score, + } + } + + /// Return the unit index. + #[must_use] + pub const fn unit_index(self) -> u32 { + self.unit_index + } + + /// Return the event time. + #[must_use] + pub const fn event_time(self) -> f64 { + self.event_time + } + + /// Return the observed score. + #[must_use] + pub const fn score(self) -> f64 { + self.score + } +} + +/// One already-formed lagged within residual pair on event time. +/// +/// The interval is admitted event time. Residuals are stored as given; the +/// recover functions decide whether a pair is an admissible log-rate input. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct LaggedWithinResidual { + earlier_residual: f64, + later_residual: f64, + event_interval: EventTimeInterval, +} + +impl LaggedWithinResidual { + /// Construct a lagged within-residual pair on an admitted event interval. + #[must_use] + pub const fn new( + earlier_residual: f64, + later_residual: f64, + event_interval: EventTimeInterval, + ) -> Self { + Self { + earlier_residual, + later_residual, + event_interval, + } + } + + /// Return the earlier within residual. + #[must_use] + pub const fn earlier_residual(self) -> f64 { + self.earlier_residual + } + + /// Return the later within residual. + #[must_use] + pub const fn later_residual(self) -> f64 { + self.later_residual + } + + /// Return the admitted event-time interval. + #[must_use] + pub const fn event_interval(self) -> EventTimeInterval { + self.event_interval + } +} + +/// Cluster-mean-center consecutive event-time lags inside each unit. +/// +/// Stable between-unit means are removed first (CWC). Consecutive within-unit +/// residuals then become [`LaggedWithinResidual`] pairs on possibly irregular +/// event intervals. Singleton units are skipped. Curran and Bauer (2011, +/// pp. 583–619; PMC3059070 XML opened 2026-09-02; Eq. 36) show that +/// person-mean subtraction of a time-varying covariate related to time is +/// biased for the within-person effect. The returned pairs are therefore not a +/// license to recover raw-process drift `a`. +/// +/// # Errors +/// +/// Returns [`LongitudinalError::InvalidObservationPayload`] for empty, +/// singleton-only, fewer-than-two-unit, or non-finite rows, including +/// overflowed unit means and overflowing CWC residuals after a finite mean, +/// and [`LongitudinalError::NonPositiveEventInterval`] when any consecutive +/// event interval is not strictly positive. +pub fn center_within_unit_event_lags( + rows: &[EventTimedObservation], +) -> Result, LongitudinalError> { + if rows.len() < 2 { + return Err(LongitudinalError::InvalidObservationPayload); + } + let mut groups: BTreeMap> = BTreeMap::new(); + for &row in rows { + if !row.event_time().is_finite() || !row.score().is_finite() { + return Err(LongitudinalError::InvalidObservationPayload); + } + groups.entry(row.unit_index()).or_default().push(row); + } + if groups.len() < 2 { + return Err(LongitudinalError::InvalidObservationPayload); + } + let mut pairs = Vec::new(); + for occasions in groups.values_mut() { + if occasions.len() < 2 { + continue; + } + occasions.sort_by(|left, right| left.event_time().total_cmp(&right.event_time())); + let count = occasions.len() as f64; + let mut total = 0.0_f64; + for row in occasions.iter() { + total += row.score(); + } + let mean = total / count; + if !mean.is_finite() { + return Err(LongitudinalError::InvalidObservationPayload); + } + for window in occasions.windows(2) { + let earlier_residual = window[0].score() - mean; + let later_residual = window[1].score() - mean; + if !earlier_residual.is_finite() || !later_residual.is_finite() { + return Err(LongitudinalError::InvalidObservationPayload); + } + let event_delta = window[1].event_time() - window[0].event_time(); + let event_interval = EventTimeInterval::new(event_delta)?; + pairs.push(LaggedWithinResidual::new( + earlier_residual, + later_residual, + event_interval, + )); + } + } + if pairs.is_empty() { + return Err(LongitudinalError::InvalidObservationPayload); + } + Ok(pairs) +} + +/// Pairwise-mean exact log-rate after CWC on irregular event intervals. +/// +/// This is [`center_within_unit_event_lags`] then the pairwise mean of the +/// Driver, Oud, and Voelkle (2017, Eq. 3) scalar inverse +/// `a = ln(|later| / |earlier|) / Δt` on nonzero same-sign residuals. When +/// `|later| / |earlier|` is finite and positive the finite ratio logarithm is +/// used so near-equal large residuals do not collapse to zero. Overflowed or +/// underflowed ratios fall back to `ln|later| − ln|earlier|`. Opposite-sign +/// and zero residuals have no real logarithm and are skipped. The pairwise +/// mean cancels opposite-signed finite rates from largest magnitude downward +/// before averaging the surviving same-sign residuals. This avoids both +/// overflowing a raw same-sign sum and destroying representable subnormal +/// terms by pre-scaling them. An empty admissible set fails closed. This is +/// not Newton LS and does not recover raw-process drift from CWC of a raw AR +/// path (Curran & Bauer, 2011, pp. 583–619; Eq. 36). +/// +/// # Errors +/// +/// Propagates centering errors from [`center_within_unit_event_lags`]. A +/// non-finite log-rate or an empty admissible list is +/// [`LongitudinalError::InvalidTemporalTransformInput`]. +pub fn recover_within_unit_irregular_residual_log_rate( + rows: &[EventTimedObservation], +) -> Result { + let lagged = center_within_unit_event_lags(rows)?; + pairwise_same_sign_log_rate(&lagged) +} + +/// Mean exact scalar log-rate on already-centered residuals. +/// +/// Each pair is `a = ln(later / earlier) / Δt` (Driver et al., 2017, Eq. 3 +/// inverse). The function does **not** center again. The signed residual ratio +/// must be strictly positive. This is the known-truth path that recovers +/// `ln(0.5)` from already-centered pairs `(1, 0.5)` over unit event time. +/// +/// # Errors +/// +/// Returns [`LongitudinalError::InvalidObservationPayload`] for an empty +/// series or non-finite residuals, and +/// [`LongitudinalError::InvalidTemporalTransformInput`] when a residual ratio +/// is not strictly positive or the log-rate is not representable. +pub fn recover_centered_irregular_residual_log_rate( + pairs: &[LaggedWithinResidual], +) -> Result { + if pairs.is_empty() { + return Err(LongitudinalError::InvalidObservationPayload); + } + let mut rates = Vec::with_capacity(pairs.len()); + for pair in pairs { + if !pair.earlier_residual().is_finite() || !pair.later_residual().is_finite() { + return Err(LongitudinalError::InvalidObservationPayload); + } + if pair.earlier_residual() == 0.0 { + return Err(LongitudinalError::InvalidTemporalTransformInput); + } + let ratio = pair.later_residual() / pair.earlier_residual(); + if !ratio.is_finite() || ratio <= 0.0 { + return Err(LongitudinalError::InvalidTemporalTransformInput); + } + rates.push(require_finite( + ratio.ln() / pair.event_interval().as_f64(), + )?); + } + scaled_compensated_mean(&rates) +} + +/// Refuse treating a CWC residual log-rate as raw-process AR drift. +/// +/// Always fails closed. Curran and Bauer (2011, pp. 583–619; PMC3059070 XML +/// opened 2026-09-02) show that person-mean centering of a time-varying +/// covariate related to time is biased for the within-person effect. Licensed +/// detrend is the person-specific OLS residual of the covariate on time +/// (Eq. 36). Use [`recover_centered_irregular_residual_log_rate`] on +/// already-centered residuals for the raw-process estimand. +/// +/// # Errors +/// +/// Always returns [`LongitudinalError::CwcResidualLogRateIsNotRawProcessDrift`]. +pub fn refuse_cwc_residual_log_rate_as_raw_process_drift( + cwc_log_rate: f64, + raw_process_drift: f64, +) -> Result { + let _ = (cwc_log_rate, raw_process_drift); + Err(LongitudinalError::CwcResidualLogRateIsNotRawProcessDrift) +} + +fn pairwise_same_sign_log_rate(lagged: &[LaggedWithinResidual]) -> Result { + let mut rates = Vec::with_capacity(lagged.len()); + for pair in lagged { + if !same_sign_nonzero(pair.earlier_residual(), pair.later_residual()) { + continue; + } + rates.push(driver_same_sign_log_rate( + pair.earlier_residual(), + pair.later_residual(), + pair.event_interval(), + )?); + } + scaled_compensated_mean(&rates) +} + +/// Overflow-safe mean for finite values across binary64 scales. +/// +/// Same-sign inputs use a convex running mean, whose intermediate stays inside +/// the observed range and therefore cannot overflow. Mixed-sign inputs are +/// partitioned by sign and sorted from largest magnitude downward. Opposite +/// signs are cancelled before any scale reduction, so a subnormal addend is +/// never divided into zero merely to protect an unrelated extreme term. Each +/// cancellation is an opposite-sign addition and therefore cannot overflow. +/// The remaining terms have one sign and are averaged safely; their mean is +/// then weighted by their term count relative to the original sample count. +/// Retained mass is `(mean * k) / n`, not `mean * (k / n)`, so a representable +/// cancellation residue cannot lose 1 ULP to a non-exact ratio. +fn scaled_compensated_mean(values: &[f64]) -> Result { + if values.is_empty() { + return Err(LongitudinalError::InvalidTemporalTransformInput); + } + + let mut positives = Vec::new(); + let mut negatives = Vec::new(); + for &value in values { + if !value.is_finite() { + return Err(LongitudinalError::InvalidTemporalTransformInput); + } + if value > 0.0 { + positives.push(value); + } else if value < 0.0 { + negatives.push(value); + } + } + + if positives.is_empty() && negatives.is_empty() { + return Ok(0.0); + } + + if positives.is_empty() || negatives.is_empty() { + return same_sign_mean(values); + } + + positives.sort_by(|left, right| right.total_cmp(left)); + negatives.sort_by(|left, right| left.total_cmp(right)); + + let mut positive_index = 0_usize; + let mut negative_index = 0_usize; + let mut positive = positives[0]; + let mut negative = negatives[0]; + let mut residuals = Vec::with_capacity(values.len()); + + loop { + let residual = positive + negative; + if residual > 0.0 { + positive = residual; + negative_index += 1; + if negative_index == negatives.len() { + residuals.push(positive); + residuals.extend_from_slice(&positives[positive_index + 1..]); + break; + } + negative = negatives[negative_index]; + } else if residual < 0.0 { + negative = residual; + positive_index += 1; + if positive_index == positives.len() { + residuals.push(negative); + residuals.extend_from_slice(&negatives[negative_index + 1..]); + break; + } + positive = positives[positive_index]; + } else { + positive_index += 1; + negative_index += 1; + if positive_index == positives.len() || negative_index == negatives.len() { + residuals.extend_from_slice(&positives[positive_index..]); + residuals.extend_from_slice(&negatives[negative_index..]); + break; + } + positive = positives[positive_index]; + negative = negatives[negative_index]; + } + } + + if residuals.is_empty() { + return Ok(0.0); + } + let residual_mean = same_sign_mean(&residuals)?; + let retained_mass = residual_mean * residuals.len() as f64; + require_finite(retained_mass / values.len() as f64) +} + +fn same_sign_mean(values: &[f64]) -> Result { + let mut mean = 0.0_f64; + for (index, &value) in values.iter().enumerate() { + let count = (index + 1) as f64; + mean += (value - mean) / count; + } + require_finite(mean) +} + +/// Nonzero residuals of equal sign admit a real Driver Eq. 3 logarithm. +pub(crate) fn same_sign_nonzero(earlier: f64, later: f64) -> bool { + earlier != 0.0 && later != 0.0 && earlier.is_sign_positive() == later.is_sign_positive() +} + +/// Driver et al. (2017, Eq. 3) inverse `a = ln(|later| / |earlier|) / Δt`. +/// +/// Caller already established same-sign nonzero residuals and an admitted +/// event interval. Prefer the finite ratio logarithm so near-equal large +/// residuals keep a nonzero rate. Fall back to `ln|later| − ln|earlier|` +/// only when that ratio overflows or underflows. +pub(crate) fn driver_same_sign_log_rate( + earlier: f64, + later: f64, + event_interval: EventTimeInterval, +) -> Result { + let ratio = later.abs() / earlier.abs(); + let log_ratio = if ratio.is_finite() && ratio > 0.0 { + ratio.ln() + } else { + later.abs().ln() - earlier.abs().ln() + }; + require_finite(log_ratio / event_interval.as_f64()) +} + +fn require_finite(value: f64) -> Result { + if value.is_finite() { + Ok(value) + } else { + Err(LongitudinalError::InvalidTemporalTransformInput) + } +} + +#[cfg(test)] +mod tests { + use super::{ + EventTimedObservation, LaggedWithinResidual, center_within_unit_event_lags, + driver_same_sign_log_rate, recover_centered_irregular_residual_log_rate, + recover_within_unit_irregular_residual_log_rate, + refuse_cwc_residual_log_rate_as_raw_process_drift, same_sign_nonzero, + scaled_compensated_mean, + }; + use crate::{EventTimeInterval, LongitudinalError}; + + fn timed(unit: u32, event_time: f64, score: f64) -> EventTimedObservation { + EventTimedObservation::new(unit, event_time, score) + } + + fn unit_interval() -> EventTimeInterval { + EventTimeInterval::new(1.0).expect("unit interval") + } + + fn lagged(earlier: f64, later: f64, delta: f64) -> LaggedWithinResidual { + LaggedWithinResidual::new( + earlier, + later, + EventTimeInterval::new(delta).expect("test interval"), + ) + } + + fn decaying_scores(drift: f64) -> [EventTimedObservation; 8] { + [ + timed(0, 0.0, 10.0 + 1.0), + timed(0, 1.0, 10.0 + drift.exp()), + timed(0, 2.0, 10.0 + (drift * 2.0).exp()), + timed(0, 3.0, 10.0 + (drift * 3.0).exp()), + timed(1, 0.0, 4.0 + 1.0), + timed(1, 1.0, 4.0 + drift.exp()), + timed(1, 2.0, 4.0 + (drift * 2.0).exp()), + timed(1, 3.0, 4.0 + (drift * 3.0).exp()), + ] + } + + #[test] + fn already_centered_irregular_pairs_recover_true_log_rate() { + let drift = -0.35_f64; + let pairs = [ + lagged(1.4, 1.4 * (drift * 0.4).exp(), 0.4), + lagged(0.9, 0.9 * (drift * 1.6).exp(), 1.6), + lagged(-0.7, -0.7 * (drift * 2.2).exp(), 2.2), + ]; + let recovered = recover_centered_irregular_residual_log_rate(&pairs).expect("centered"); + assert!((recovered - drift).abs() < 1e-12); + let half = recover_centered_irregular_residual_log_rate(&[lagged(1.0, 0.5, 1.0)]) + .expect("ln(0.5)"); + assert!((half - 0.5_f64.ln()).abs() < 1e-15); + } + + #[test] + fn cwc_of_raw_ar_does_not_recover_process_drift() { + let drift = -0.3_f64; + let rows = decaying_scores(drift); + let extracted = center_within_unit_event_lags(&rows).expect("cwc pairs"); + let composed = + recover_within_unit_irregular_residual_log_rate(&rows).expect("cwc pairwise"); + assert!((composed - drift).abs() > 1e-6); + assert_eq!( + refuse_cwc_residual_log_rate_as_raw_process_drift(composed, drift), + Err(LongitudinalError::CwcResidualLogRateIsNotRawProcessDrift) + ); + let admissible: Vec = extracted + .iter() + .copied() + .filter(|pair| same_sign_nonzero(pair.earlier_residual(), pair.later_residual())) + .collect(); + let from_pairs = + recover_centered_irregular_residual_log_rate(&admissible).expect("admissible"); + assert!((composed - from_pairs).abs() < 1e-15); + assert_eq!( + extracted[0].event_interval().as_f64().to_bits(), + 1.0_f64.to_bits() + ); + assert_eq!(timed(9, 8.0, 0.0).unit_index(), 9); + assert_eq!(timed(9, 8.0, 0.0).event_time().to_bits(), 8.0_f64.to_bits()); + assert_eq!(timed(9, 8.0, 0.0).score().to_bits(), 0.0_f64.to_bits()); + assert_eq!( + lagged(1.0, 0.5, 1.0).earlier_residual().to_bits(), + 1.0_f64.to_bits() + ); + assert_eq!( + lagged(1.0, 0.5, 1.0).later_residual().to_bits(), + 0.5_f64.to_bits() + ); + } + + #[test] + fn two_occasion_cwc_is_empty_admissible() { + assert_eq!( + recover_within_unit_irregular_residual_log_rate(&[ + timed(0, 0.0, 1.0), + timed(0, 1.0, 0.5), + timed(1, 0.0, 2.0), + timed(1, 1.0, 1.0), + ]), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); + } + + #[test] + fn arithmetic_progression_has_zero_residual_and_fails_closed() { + assert_eq!( + recover_within_unit_irregular_residual_log_rate(&[ + timed(0, 0.0, 1.0), + timed(0, 1.0, 2.0), + timed(0, 2.0, 3.0), + timed(1, 0.0, 4.0), + timed(1, 1.0, 5.0), + timed(1, 2.0, 6.0), + ]), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); + } + + #[test] + fn same_sign_nonzero_rejects_zero_and_opposite_signs() { + assert!(!same_sign_nonzero(0.0, 1.0)); + assert!(!same_sign_nonzero(1.0, 0.0)); + assert!(!same_sign_nonzero(-1.0, 2.0)); + assert!(same_sign_nonzero(1e-160, 1e160)); + assert!(same_sign_nonzero(-0.4, -1.2)); + } + + #[test] + fn driver_same_sign_prefers_finite_ratio_ln_for_near_equal_large_residuals() { + let earlier = 1e20_f64; + let later = earlier * (-1e-12_f64).exp(); + let ratio = later.abs() / earlier.abs(); + assert!(ratio.is_finite() && ratio > 0.0); + let from_ratio = ratio.ln(); + let rate = driver_same_sign_log_rate(earlier, later, unit_interval()).expect("near-equal"); + assert_eq!(rate.to_bits(), from_ratio.to_bits()); + let overflow_rate = driver_same_sign_log_rate(f64::from_bits(1), f64::MAX, unit_interval()) + .expect("overflow arm"); + let overflow_logs = f64::MAX.ln() - f64::from_bits(1).ln(); + assert_eq!(overflow_rate.to_bits(), overflow_logs.to_bits()); + let underflow_rate = + driver_same_sign_log_rate(1e300_f64, 1e-300_f64, unit_interval()).expect("underflow"); + let underflow_logs = (1e-300_f64).ln() - (1e300_f64).ln(); + assert_eq!(underflow_rate.to_bits(), underflow_logs.to_bits()); + } + + #[test] + fn scaled_compensated_mean_keeps_extreme_cancellation_and_overflow_safe_mean() { + let large = 1.45e308_f64; + assert!(!(large + large).is_finite()); + let same_sign = scaled_compensated_mean(&[large, large]).expect("same-sign mean"); + assert!(same_sign.is_finite()); + assert!((same_sign - large).abs() < 1.0); + let negative = scaled_compensated_mean(&[-4.0, -2.0]).expect("negative same-sign mean"); + assert_eq!(negative, -3.0); + let mixed = scaled_compensated_mean(&[large, -large]).expect("mixed mean"); + assert!(mixed.abs() < 1.0); + + let recovered = scaled_compensated_mean(&[1.0e100, 1.0, -1.0e100]) + .expect("small signal survives cancellation"); + assert!((recovered - (1.0 / 3.0)).abs() <= 1.0e-12); + let full_range = scaled_compensated_mean(&[f64::MAX, 1.0e-16, -f64::MAX]) + .expect("full exponent range cancellation"); + assert_eq!(full_range.to_bits(), (1.0e-16_f64 / 3.0).to_bits()); + let minimum_subnormal = f64::from_bits(1); + let subnormal = scaled_compensated_mean(&[ + f64::MAX, + f64::from_bits(2), + f64::from_bits(2), + -f64::MAX, + ]) + .expect("subnormal cancellation residue"); + assert_eq!(subnormal.to_bits(), minimum_subnormal.to_bits()); + assert_eq!( + scaled_compensated_mean(&[]), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); + assert_eq!(scaled_compensated_mean(&[0.0, 0.0]), Ok(0.0)); + assert_eq!( + scaled_compensated_mean(&[f64::INFINITY]), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); + } + + fn overflowing_cwc_rate_rows(unit: u32, growing: bool) -> [EventTimedObservation; 3] { + let delta = 1e-305_f64; + let (first, second) = if growing { + (f64::from_bits(1), f64::MAX) + } else { + (f64::MAX, f64::from_bits(1)) + }; + [ + timed(unit, 0.0, first), + timed(unit, delta, second), + timed(unit, 2.0 * delta, -f64::MAX), + ] + } + + #[test] + fn cwc_pairwise_keeps_overflowed_rate_sum_via_compensated_mean() { + let mut rows = overflowing_cwc_rate_rows(1, true).to_vec(); + rows.extend(overflowing_cwc_rate_rows(2, true)); + let recovered = + recover_within_unit_irregular_residual_log_rate(&rows).expect("compensated"); + assert!(recovered.is_finite()); + let extracted = center_within_unit_event_lags(&rows).expect("extract"); + let mut rates = Vec::new(); + for pair in extracted { + if !same_sign_nonzero(pair.earlier_residual(), pair.later_residual()) { + continue; + } + rates.push( + driver_same_sign_log_rate( + pair.earlier_residual(), + pair.later_residual(), + pair.event_interval(), + ) + .expect("pair rate"), + ); + } + assert_eq!(rates.len(), 2); + assert!(!(rates[0] + rates[1]).is_finite()); + let expected = scaled_compensated_mean(&rates).expect("reference compensated mean"); + assert!((recovered - expected).abs() <= expected.abs() * 1e-15); + let mut mixed = overflowing_cwc_rate_rows(1, true).to_vec(); + mixed.extend(overflowing_cwc_rate_rows(2, false)); + let mixed_mean = + recover_within_unit_irregular_residual_log_rate(&mixed).expect("mixed-sign"); + assert!(mixed_mean.abs() < recovered.abs() * 1e-12); + } + + #[test] + fn cwc_pairwise_tiny_interval_with_huge_log_ratio_fails_closed() { + assert_eq!( + recover_within_unit_irregular_residual_log_rate(&[ + timed(1, 0.0, 1e-160), + timed(1, f64::from_bits(1), 1e160), + timed(1, 1.0, -1e160), + timed(2, 0.0, 1.0), + timed(2, 1.0, 0.5), + ]), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); + } + + #[test] + fn cwc_pairwise_skips_zero_residuals_and_keeps_same_sign_pairs() { + let recovered = recover_within_unit_irregular_residual_log_rate(&[ + timed(1, 0.0, 7.0), + timed(1, 1.0, 5.0), + timed(1, 2.0, 4.0), + timed(1, 3.0, 4.0), + timed(2, 0.0, -1.2), + timed(2, 1.0, -0.4), + timed(2, 2.0, -0.8), + ]) + .expect("skip zeros keep same-sign"); + assert!(recovered.is_finite()); + let extracted = center_within_unit_event_lags(&[ + timed(1, 0.0, 7.0), + timed(1, 1.0, 5.0), + timed(1, 2.0, 4.0), + timed(1, 3.0, 4.0), + timed(2, 0.0, -1.2), + timed(2, 1.0, -0.4), + timed(2, 2.0, -0.8), + ]) + .expect("extract"); + assert!( + extracted + .iter() + .any(|pair| pair.later_residual().to_bits() == 0.0_f64.to_bits()) + ); + assert!( + extracted + .iter() + .any(|pair| pair.earlier_residual().to_bits() == 0.0_f64.to_bits()) + ); + assert!(extracted.iter().any(|pair| same_sign_nonzero( + pair.earlier_residual(), + pair.later_residual() + ) && !pair.earlier_residual().is_sign_positive())); + } + + #[test] + fn cwc_observation_payload_paths_fail_closed() { + assert_eq!( + recover_within_unit_irregular_residual_log_rate(&[]), + Err(LongitudinalError::InvalidObservationPayload) + ); + assert_eq!( + recover_within_unit_irregular_residual_log_rate(&[timed(1, 0.0, 1.0)]), + Err(LongitudinalError::InvalidObservationPayload) + ); + assert_eq!( + recover_within_unit_irregular_residual_log_rate(&[ + timed(1, 0.0, 1.0), + timed(1, 1.0, 0.5), + ]), + Err(LongitudinalError::InvalidObservationPayload) + ); + assert_eq!( + recover_within_unit_irregular_residual_log_rate(&[ + timed(1, 0.0, 1.0), + timed(2, 1.0, 0.5), + ]), + Err(LongitudinalError::InvalidObservationPayload) + ); + assert_eq!( + center_within_unit_event_lags(&[ + timed(1, 0.0, 1.0), + timed(1, 0.0, 1.2), + timed(2, 0.0, 2.0), + timed(2, 1.0, 1.5), + ]), + Err(LongitudinalError::NonPositiveEventInterval) + ); + assert_eq!( + recover_within_unit_irregular_residual_log_rate(&[ + timed(1, 0.0, f64::NAN), + timed(1, 1.0, 1.0), + timed(2, 0.0, 1.0), + timed(2, 1.0, 0.5), + ]), + Err(LongitudinalError::InvalidObservationPayload) + ); + assert_eq!( + recover_within_unit_irregular_residual_log_rate(&[ + timed(1, 0.0, f64::INFINITY), + timed(1, 1.0, 1.0), + timed(2, 0.0, 1.0), + timed(2, 1.0, 0.5), + ]), + Err(LongitudinalError::InvalidObservationPayload) + ); + assert_eq!( + recover_within_unit_irregular_residual_log_rate(&[ + timed(1, 0.0, f64::MAX), + timed(1, 1.0, f64::MAX), + timed(2, 0.0, 1.0), + timed(2, 1.0, 0.5), + ]), + Err(LongitudinalError::InvalidObservationPayload) + ); + assert_eq!( + center_within_unit_event_lags(&[ + timed(1, f64::MAX, 1.0), + timed(1, -f64::MAX, 0.5), + timed(2, 0.0, 1.0), + timed(2, 1.0, 0.5), + ]), + Err(LongitudinalError::NonPositiveEventInterval) + ); + } + + #[test] + fn already_centered_and_curran_refusal_paths_fail_closed() { + assert_eq!( + recover_centered_irregular_residual_log_rate(&[]), + Err(LongitudinalError::InvalidObservationPayload) + ); + assert_eq!( + recover_centered_irregular_residual_log_rate(&[lagged(f64::NAN, 0.5, 1.0)]), + Err(LongitudinalError::InvalidObservationPayload) + ); + assert_eq!( + recover_centered_irregular_residual_log_rate(&[lagged(1.0, f64::NAN, 1.0)]), + Err(LongitudinalError::InvalidObservationPayload) + ); + assert_eq!( + recover_centered_irregular_residual_log_rate(&[lagged(0.0, 0.5, 1.0)]), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); + assert_eq!( + recover_centered_irregular_residual_log_rate(&[lagged(1.0, -0.5, 1.0)]), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); + assert_eq!( + recover_centered_irregular_residual_log_rate(&[lagged( + f64::from_bits(1), + f64::MAX, + 1.0 + )]), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); + assert_eq!( + recover_centered_irregular_residual_log_rate(&[lagged( + 1e-160, + 1e160, + f64::from_bits(1) + )]), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); + assert_eq!( + refuse_cwc_residual_log_rate_as_raw_process_drift(f64::NAN, f64::INFINITY), + Err(LongitudinalError::CwcResidualLogRateIsNotRawProcessDrift) + ); + } + + #[test] + fn cwc_orders_unsorted_event_times_before_lag_pairs() { + let pairs = center_within_unit_event_lags(&[ + timed(1, 2.0, 0.4), + timed(2, 3.0, -0.8), + timed(1, 0.5, 1.6), + timed(2, 1.0, 0.2), + ]) + .expect("unsorted"); + assert_eq!(pairs.len(), 2); + assert!((pairs[0].event_interval().as_f64() - 1.5).abs() < 1e-15); + assert!((pairs[1].event_interval().as_f64() - 2.0).abs() < 1e-15); + let cluster_one_mean = f64::midpoint(1.6, 0.4); + assert!((pairs[0].earlier_residual() - (1.6 - cluster_one_mean)).abs() < 1e-15); + assert!((pairs[0].later_residual() - (0.4 - cluster_one_mean)).abs() < 1e-15); + } + + #[test] + fn singleton_unit_is_skipped_and_all_singletons_fail_closed() { + let drift = -0.2_f64; + let mixed = [ + timed(1, 0.0, 10.0 + 1.0), + timed(1, 1.0, 10.0 + drift.exp()), + timed(1, 2.0, 10.0 + (drift * 2.0).exp()), + timed(1, 3.0, 10.0 + (drift * 3.0).exp()), + timed(2, 0.0, 4.0), + ]; + let recovered = recover_within_unit_irregular_residual_log_rate(&mixed).expect("skip"); + assert!(recovered.is_finite()); + assert_eq!( + recover_within_unit_irregular_residual_log_rate(&[ + timed(1, 0.0, 1.0), + timed(2, 1.0, 0.5) + ]), + Err(LongitudinalError::InvalidObservationPayload) + ); + } + + #[test] + fn overflowing_same_sign_cwc_pairs_keep_stable_log() { + let overflowed_both = recover_within_unit_irregular_residual_log_rate(&[ + timed(1, 0.0, f64::from_bits(1)), + timed(1, 1.0, f64::MAX), + timed(1, 2.0, -f64::MAX), + timed(2, 0.0, f64::from_bits(1)), + timed(2, 1.0, f64::MAX), + timed(2, 2.0, -f64::MAX), + ]) + .expect("stable log of overflowed same-sign CWC pairs"); + let overflow_rate = driver_same_sign_log_rate(f64::from_bits(1), f64::MAX, unit_interval()) + .expect("tiny/MAX"); + assert!((overflowed_both - overflow_rate).abs() < 1e-9); + } + + #[test] + fn one_sided_residual_overflow_fails_closed() { + assert_eq!( + recover_within_unit_irregular_residual_log_rate(&[ + timed(1, 0.0, -f64::MAX), + timed(1, 1.0, -f64::MAX), + timed(1, 2.0, -f64::MAX), + timed(1, 3.0, f64::MAX), + timed(2, 0.0, 1.0), + timed(2, 1.0, 0.8), + ]), + Err(LongitudinalError::InvalidObservationPayload) + ); + assert_eq!( + center_within_unit_event_lags(&[ + timed(1, 0.0, f64::MAX), + timed(1, 1.0, -f64::MAX), + timed(1, 2.0, -f64::MAX / 2.0), + timed(2, 0.0, 1.0), + timed(2, 1.0, 0.5), + ]), + Err(LongitudinalError::InvalidObservationPayload) + ); + } +} From a82222282917945beca456476f643387de325230 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 18:10:46 +0900 Subject: [PATCH 058/296] test(longitudinal_core): reproduce representable retained-mass overflow --- .../tests/intermediate_overflow_regression.rs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 crates/longitudinal_core/tests/intermediate_overflow_regression.rs diff --git a/crates/longitudinal_core/tests/intermediate_overflow_regression.rs b/crates/longitudinal_core/tests/intermediate_overflow_regression.rs new file mode 100644 index 000000000..0fb9e158f --- /dev/null +++ b/crates/longitudinal_core/tests/intermediate_overflow_regression.rs @@ -0,0 +1,28 @@ +use longitudinal_core::{ + EventTimeInterval, LaggedWithinResidual, recover_centered_irregular_residual_log_rate, +}; + +#[test] +fn representable_mixed_sign_mean_survives_retained_mass_intermediate_overflow() { + let target_positive_rate = 1.45e308_f64; + let extreme_interval = + EventTimeInterval::new(1.0 / target_positive_rate).expect("positive extreme interval"); + let unit_interval = EventTimeInterval::new(1.0).expect("unit interval"); + + let pairs = [ + LaggedWithinResidual::new(1.0, std::f64::consts::E, extreme_interval), + LaggedWithinResidual::new(1.0, std::f64::consts::E, extreme_interval), + LaggedWithinResidual::new(std::f64::consts::E, 1.0, unit_interval), + LaggedWithinResidual::new(std::f64::consts::E, 1.0, unit_interval), + ]; + + let positive_rate = std::f64::consts::E.ln() / extreme_interval.as_f64(); + assert!(positive_rate.is_finite()); + assert!(!(positive_rate * 2.0).is_finite()); + let expected = positive_rate / 2.0; + + let recovered = recover_centered_irregular_residual_log_rate(&pairs) + .expect("finite mathematical mean must not fail on intermediate overflow"); + assert!(recovered.is_finite()); + assert!((recovered - expected).abs() <= expected.abs() * 4.0 * f64::EPSILON); +} From aef400be9bc7ce65974954aaabcbb017fc6cf1fc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 18:15:52 +0900 Subject: [PATCH 059/296] fix(longitudinal_core): avoid retained-mass intermediate overflow --- .../src/stable_irregular_rate.rs | 220 ++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 crates/longitudinal_core/src/stable_irregular_rate.rs diff --git a/crates/longitudinal_core/src/stable_irregular_rate.rs b/crates/longitudinal_core/src/stable_irregular_rate.rs new file mode 100644 index 000000000..b5aa13868 --- /dev/null +++ b/crates/longitudinal_core/src/stable_irregular_rate.rs @@ -0,0 +1,220 @@ +//! Numerically stable public composition for irregular residual log-rates. +//! +//! This module keeps the public longitudinal estimand unchanged while making +//! the final count weighting robust when an intermediate `mean * count` would +//! overflow even though the mathematical weighted mean is representable. + +use crate::irregular_residual::{ + EventTimedObservation, LaggedWithinResidual, center_within_unit_event_lags, + driver_same_sign_log_rate, same_sign_nonzero, +}; +use crate::LongitudinalError; + +/// Pairwise-mean exact log-rate after CWC on irregular event intervals. +/// +/// Temporal admission and CWC remain owned by the longitudinal composition in +/// [`center_within_unit_event_lags`]. Same-sign finite pair rates are then +/// averaged without requiring an overflowing intermediate sum or count product. +/// +/// # Errors +/// +/// Propagates temporal-admission errors and returns +/// [`LongitudinalError::InvalidTemporalTransformInput`] when no admissible +/// same-sign pair exists or a finite rate cannot be represented. +pub fn recover_within_unit_irregular_residual_log_rate( + rows: &[EventTimedObservation], +) -> Result { + let lagged = center_within_unit_event_lags(rows)?; + let mut rates = Vec::with_capacity(lagged.len()); + for pair in &lagged { + if !same_sign_nonzero(pair.earlier_residual(), pair.later_residual()) { + continue; + } + rates.push(driver_same_sign_log_rate( + pair.earlier_residual(), + pair.later_residual(), + pair.event_interval(), + )?); + } + stable_mean(&rates) +} + +/// Mean exact scalar log-rate on already-centered residuals. +/// +/// Each pair is `ln(later / earlier) / Δt`. The signed ratio must be finite and +/// strictly positive. Opposing extreme finite rates are cancelled before count +/// weighting so a representable final mean is not rejected merely because an +/// intermediate multiplication overflows. +/// +/// # Errors +/// +/// Returns [`LongitudinalError::InvalidObservationPayload`] for an empty input +/// or non-finite residuals, and +/// [`LongitudinalError::InvalidTemporalTransformInput`] for an inadmissible +/// ratio, non-finite rate, or non-representable final mean. +pub fn recover_centered_irregular_residual_log_rate( + pairs: &[LaggedWithinResidual], +) -> Result { + if pairs.is_empty() { + return Err(LongitudinalError::InvalidObservationPayload); + } + + let mut rates = Vec::with_capacity(pairs.len()); + for pair in pairs { + if !pair.earlier_residual().is_finite() || !pair.later_residual().is_finite() { + return Err(LongitudinalError::InvalidObservationPayload); + } + if pair.earlier_residual() == 0.0 { + return Err(LongitudinalError::InvalidTemporalTransformInput); + } + let ratio = pair.later_residual() / pair.earlier_residual(); + if !ratio.is_finite() || ratio <= 0.0 { + return Err(LongitudinalError::InvalidTemporalTransformInput); + } + let rate = ratio.ln() / pair.event_interval().as_f64(); + if !rate.is_finite() { + return Err(LongitudinalError::InvalidTemporalTransformInput); + } + rates.push(rate); + } + + stable_mean(&rates) +} + +fn stable_mean(values: &[f64]) -> Result { + if values.is_empty() { + return Err(LongitudinalError::InvalidTemporalTransformInput); + } + + let mut positives = Vec::new(); + let mut negatives = Vec::new(); + for &value in values { + if !value.is_finite() { + return Err(LongitudinalError::InvalidTemporalTransformInput); + } + if value > 0.0 { + positives.push(value); + } else if value < 0.0 { + negatives.push(value); + } + } + + if positives.is_empty() && negatives.is_empty() { + return Ok(0.0); + } + if positives.is_empty() || negatives.is_empty() { + return same_sign_mean(values); + } + + positives.sort_by(|left, right| right.total_cmp(left)); + negatives.sort_by(|left, right| left.total_cmp(right)); + + let mut positive_index = 0_usize; + let mut negative_index = 0_usize; + let mut positive = positives[0]; + let mut negative = negatives[0]; + let mut residuals = Vec::with_capacity(values.len()); + + loop { + let residual = positive + negative; + if residual > 0.0 { + positive = residual; + negative_index += 1; + if negative_index == negatives.len() { + residuals.push(positive); + residuals.extend_from_slice(&positives[positive_index + 1..]); + break; + } + negative = negatives[negative_index]; + } else if residual < 0.0 { + negative = residual; + positive_index += 1; + if positive_index == positives.len() { + residuals.push(negative); + residuals.extend_from_slice(&negatives[negative_index + 1..]); + break; + } + positive = positives[positive_index]; + } else { + positive_index += 1; + negative_index += 1; + if positive_index == positives.len() || negative_index == negatives.len() { + residuals.extend_from_slice(&positives[positive_index..]); + residuals.extend_from_slice(&negatives[negative_index..]); + break; + } + positive = positives[positive_index]; + negative = negatives[negative_index]; + } + } + + if residuals.is_empty() { + return Ok(0.0); + } + + let residual_mean = same_sign_mean(&residuals)?; + scale_mean_by_count_ratio(residual_mean, residuals.len(), values.len()) +} + +fn scale_mean_by_count_ratio( + mean: f64, + retained_count: usize, + total_count: usize, +) -> Result { + let retained_count = retained_count as f64; + let total_count = total_count as f64; + + let retained_mass = mean * retained_count; + let result = if retained_mass.is_finite() { + retained_mass / total_count + } else { + // Overflow here implies a large finite mean. Divide first only on this + // branch; unlike unconditional `mean * (k / n)`, it cannot erase the + // subnormal retained-mass cases that motivated the preceding repair. + (mean / total_count) * retained_count + }; + + if result.is_finite() { + Ok(result) + } else { + Err(LongitudinalError::InvalidTemporalTransformInput) + } +} + +fn same_sign_mean(values: &[f64]) -> Result { + let mut mean = 0.0_f64; + for (index, &value) in values.iter().enumerate() { + let count = (index + 1) as f64; + mean += (value - mean) / count; + } + if mean.is_finite() { + Ok(mean) + } else { + Err(LongitudinalError::InvalidTemporalTransformInput) + } +} + +#[cfg(test)] +mod tests { + use super::stable_mean; + + #[test] + fn preserves_subnormal_retained_mass_without_overflowing_large_case() { + let minimum_subnormal = f64::from_bits(1); + let subnormal = stable_mean(&[ + f64::MAX, + f64::from_bits(2), + f64::from_bits(2), + -f64::MAX, + ]) + .expect("subnormal retained mass"); + assert_eq!(subnormal.to_bits(), minimum_subnormal.to_bits()); + + let large = 1.45e308_f64; + assert!(!(large * 2.0).is_finite()); + let finite = stable_mean(&[large, large, -1.0, -1.0]) + .expect("representable final mean"); + assert!(finite.is_finite()); + assert!((finite - large / 2.0).abs() <= (large / 2.0) * 4.0 * f64::EPSILON); + } +} From 1bdec54912d7c09662055a79cdb0c7521cea4a39 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 18:16:39 +0900 Subject: [PATCH 060/296] fix(longitudinal_core): route public irregular rates through stable mean --- crates/longitudinal_core/src/lib.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/longitudinal_core/src/lib.rs b/crates/longitudinal_core/src/lib.rs index e1a838704..d47dd0acf 100644 --- a/crates/longitudinal_core/src/lib.rs +++ b/crates/longitudinal_core/src/lib.rs @@ -20,6 +20,7 @@ mod error; mod event_time; mod irregular_residual; mod level; +mod stable_irregular_rate; mod stationary; mod temporal_association; @@ -59,16 +60,16 @@ pub use irregular_residual::EventTimedObservation; pub use irregular_residual::LaggedWithinResidual; /// Cluster-mean-center consecutive event-time lags inside each unit. pub use irregular_residual::center_within_unit_event_lags; -/// Mean exact scalar log-rate on already-centered residuals. -pub use irregular_residual::recover_centered_irregular_residual_log_rate; -/// Pairwise-mean exact log-rate after CWC on irregular event intervals. -pub use irregular_residual::recover_within_unit_irregular_residual_log_rate; /// Refuse treating a CWC residual log-rate as raw-process AR drift. pub use irregular_residual::refuse_cwc_residual_log_rate_as_raw_process_drift; /// Established longitudinal component level. pub use level::ComponentLevel; /// Refuse to treat a between-unit component as within-unit change. pub use level::refuse_between_as_within_change; +/// Mean exact scalar log-rate on already-centered residuals with stable count weighting. +pub use stable_irregular_rate::recover_centered_irregular_residual_log_rate; +/// Pairwise-mean exact log-rate after CWC with stable count weighting. +pub use stable_irregular_rate::recover_within_unit_irregular_residual_log_rate; /// Recover a valid event-time lagged correlation from covariance and both /// marginal variances through the typed event-time boundary. pub use temporal_association::recover_event_time_lagged_correlation; From 13dc230a39d6777b10cf83cea9ddcfca1d3b0360 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:00:00 +0900 Subject: [PATCH 061/296] test(longitudinal): reproduce nonrepresentable residual ratio --- .../tests/cwc_irregular_residual_contract.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/crates/longitudinal_core/tests/cwc_irregular_residual_contract.rs b/crates/longitudinal_core/tests/cwc_irregular_residual_contract.rs index 6c67b9247..6b85ab7a5 100644 --- a/crates/longitudinal_core/tests/cwc_irregular_residual_contract.rs +++ b/crates/longitudinal_core/tests/cwc_irregular_residual_contract.rs @@ -21,6 +21,24 @@ fn already_centered_unit_interval_recovers_ln_half() { assert!((recovered - 0.5_f64.ln()).abs() < f64::EPSILON); } +#[test] +fn centered_log_rate_survives_nonrepresentable_intermediate_ratios() { + let interval = longitudinal_core::EventTimeInterval::new(1.0).expect("event time"); + let overflow_pair = LaggedWithinResidual::new(f64::MIN_POSITIVE, f64::MAX, interval); + assert!(!(f64::MAX / f64::MIN_POSITIVE).is_finite()); + let overflow_expected = f64::MAX.ln() - f64::MIN_POSITIVE.ln(); + let overflow_recovered = recover_centered_irregular_residual_log_rate(&[overflow_pair]) + .expect("finite log-domain rate despite overflowing direct ratio"); + assert_eq!(overflow_recovered.to_bits(), overflow_expected.to_bits()); + + let underflow_pair = LaggedWithinResidual::new(f64::MAX, f64::MIN_POSITIVE, interval); + assert_eq!(f64::MIN_POSITIVE / f64::MAX, 0.0); + let underflow_expected = f64::MIN_POSITIVE.ln() - f64::MAX.ln(); + let underflow_recovered = recover_centered_irregular_residual_log_rate(&[underflow_pair]) + .expect("finite log-domain rate despite underflowing direct ratio"); + assert_eq!(underflow_recovered.to_bits(), underflow_expected.to_bits()); +} + #[test] fn cwc_mean_is_deterministic_under_input_row_permutation() { let canonical = [ From feb05e6f48095d61996c2a328f85c02b4b797e9a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:00:24 +0900 Subject: [PATCH 062/296] fix(longitudinal): recover finite log rates in log domain --- .../src/stable_irregular_rate.rs | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/crates/longitudinal_core/src/stable_irregular_rate.rs b/crates/longitudinal_core/src/stable_irregular_rate.rs index b5aa13868..029bbf886 100644 --- a/crates/longitudinal_core/src/stable_irregular_rate.rs +++ b/crates/longitudinal_core/src/stable_irregular_rate.rs @@ -41,17 +41,20 @@ pub fn recover_within_unit_irregular_residual_log_rate( /// Mean exact scalar log-rate on already-centered residuals. /// -/// Each pair is `ln(later / earlier) / Δt`. The signed ratio must be finite and -/// strictly positive. Opposing extreme finite rates are cancelled before count -/// weighting so a representable final mean is not rejected merely because an -/// intermediate multiplication overflows. +/// Each pair is `ln(|later| / |earlier|) / Δt` and therefore requires finite, +/// nonzero residuals of equal sign. Direct division is used when its positive +/// ratio is representable; otherwise the equivalent log-domain difference is +/// used so ratio overflow or underflow cannot reject a finite log-rate. +/// Opposing extreme finite rates are cancelled before count weighting so a +/// representable final mean is not rejected merely because an intermediate +/// multiplication overflows. /// /// # Errors /// /// Returns [`LongitudinalError::InvalidObservationPayload`] for an empty input /// or non-finite residuals, and -/// [`LongitudinalError::InvalidTemporalTransformInput`] for an inadmissible -/// ratio, non-finite rate, or non-representable final mean. +/// [`LongitudinalError::InvalidTemporalTransformInput`] for opposite-sign or +/// zero residuals, a non-finite rate, or a non-representable final mean. pub fn recover_centered_irregular_residual_log_rate( pairs: &[LaggedWithinResidual], ) -> Result { @@ -64,18 +67,14 @@ pub fn recover_centered_irregular_residual_log_rate( if !pair.earlier_residual().is_finite() || !pair.later_residual().is_finite() { return Err(LongitudinalError::InvalidObservationPayload); } - if pair.earlier_residual() == 0.0 { - return Err(LongitudinalError::InvalidTemporalTransformInput); - } - let ratio = pair.later_residual() / pair.earlier_residual(); - if !ratio.is_finite() || ratio <= 0.0 { - return Err(LongitudinalError::InvalidTemporalTransformInput); - } - let rate = ratio.ln() / pair.event_interval().as_f64(); - if !rate.is_finite() { + if !same_sign_nonzero(pair.earlier_residual(), pair.later_residual()) { return Err(LongitudinalError::InvalidTemporalTransformInput); } - rates.push(rate); + rates.push(driver_same_sign_log_rate( + pair.earlier_residual(), + pair.later_residual(), + pair.event_interval(), + )?); } stable_mean(&rates) From cbeb6275bb133b7d143b81daa631b66a4410f265 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 20:05:38 +0900 Subject: [PATCH 063/296] test(longitudinal): reproduce shadow irregular-rate estimand drift --- ...rregular_rate_shadow_authority_contract.rs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 crates/longitudinal_core/tests/irregular_rate_shadow_authority_contract.rs diff --git a/crates/longitudinal_core/tests/irregular_rate_shadow_authority_contract.rs b/crates/longitudinal_core/tests/irregular_rate_shadow_authority_contract.rs new file mode 100644 index 000000000..05bcf9e5c --- /dev/null +++ b/crates/longitudinal_core/tests/irregular_rate_shadow_authority_contract.rs @@ -0,0 +1,23 @@ +#![forbid(unsafe_code)] + +//! Architecture fitness for one Longitudinal Modeling irregular-rate estimand. +//! +//! The crate-public implementation already survives non-representable direct +//! residual ratios by evaluating the equivalent log-domain difference. The +//! private composition module must not retain the superseded direct-ratio +//! rejection path, because that would leave two scientifically different +//! implementations of the same named estimand in one bounded context. + +#[test] +fn shadowed_centered_rate_does_not_reintroduce_direct_ratio_rejection() { + let source = include_str!("../src/irregular_residual.rs"); + + assert!( + !source.contains("let ratio = pair.later_residual() / pair.earlier_residual();"), + "the internal centered-rate path still rejects finite log-rates solely because the direct residual ratio overflows or underflows" + ); + assert!( + source.contains("driver_same_sign_log_rate("), + "the internal composition must reuse the Longitudinal Modeling same-sign log-rate primitive" + ); +} From cebcced0d3cd75066d58f641949b0ef2494ff5dd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 20:07:35 +0900 Subject: [PATCH 064/296] fix(longitudinal): unify shadow irregular-rate estimand --- .../src/irregular_residual.rs | 76 +++++++++++++------ 1 file changed, 51 insertions(+), 25 deletions(-) diff --git a/crates/longitudinal_core/src/irregular_residual.rs b/crates/longitudinal_core/src/irregular_residual.rs index d60ea8fbd..97404e0e9 100644 --- a/crates/longitudinal_core/src/irregular_residual.rs +++ b/crates/longitudinal_core/src/irregular_residual.rs @@ -194,17 +194,22 @@ pub fn recover_within_unit_irregular_residual_log_rate( /// Mean exact scalar log-rate on already-centered residuals. /// -/// Each pair is `a = ln(later / earlier) / Δt` (Driver et al., 2017, Eq. 3 -/// inverse). The function does **not** center again. The signed residual ratio -/// must be strictly positive. This is the known-truth path that recovers -/// `ln(0.5)` from already-centered pairs `(1, 0.5)` over unit event time. +/// Each pair is `a = ln(|later| / |earlier|) / Δt` (Driver et al., 2017, +/// Eq. 3 inverse). The function does **not** center again. Residuals must be +/// finite, nonzero, and of equal sign. A finite positive direct ratio is used +/// when representable; ratio overflow or underflow falls back to the equivalent +/// `ln|later| - ln|earlier|` so a representable final log-rate is not rejected +/// because of a non-representable intermediate. This is the known-truth path +/// that recovers `ln(0.5)` from already-centered pairs `(1, 0.5)` over unit +/// event time. /// /// # Errors /// /// Returns [`LongitudinalError::InvalidObservationPayload`] for an empty /// series or non-finite residuals, and -/// [`LongitudinalError::InvalidTemporalTransformInput`] when a residual ratio -/// is not strictly positive or the log-rate is not representable. +/// [`LongitudinalError::InvalidTemporalTransformInput`] for zero or +/// opposite-sign residuals, a non-finite log-rate, or a non-representable +/// final mean. pub fn recover_centered_irregular_residual_log_rate( pairs: &[LaggedWithinResidual], ) -> Result { @@ -216,15 +221,13 @@ pub fn recover_centered_irregular_residual_log_rate( if !pair.earlier_residual().is_finite() || !pair.later_residual().is_finite() { return Err(LongitudinalError::InvalidObservationPayload); } - if pair.earlier_residual() == 0.0 { - return Err(LongitudinalError::InvalidTemporalTransformInput); - } - let ratio = pair.later_residual() / pair.earlier_residual(); - if !ratio.is_finite() || ratio <= 0.0 { + if !same_sign_nonzero(pair.earlier_residual(), pair.later_residual()) { return Err(LongitudinalError::InvalidTemporalTransformInput); } - rates.push(require_finite( - ratio.ln() / pair.event_interval().as_f64(), + rates.push(driver_same_sign_log_rate( + pair.earlier_residual(), + pair.later_residual(), + pair.event_interval(), )?); } scaled_compensated_mean(&rates) @@ -275,8 +278,9 @@ fn pairwise_same_sign_log_rate(lagged: &[LaggedWithinResidual]) -> Result Result { if values.is_empty() { return Err(LongitudinalError::InvalidTemporalTransformInput); @@ -349,8 +353,15 @@ fn scaled_compensated_mean(values: &[f64]) -> Result { return Ok(0.0); } let residual_mean = same_sign_mean(&residuals)?; - let retained_mass = residual_mean * residuals.len() as f64; - require_finite(retained_mass / values.len() as f64) + let retained_count = residuals.len() as f64; + let total_count = values.len() as f64; + let retained_mass = residual_mean * retained_count; + let result = if retained_mass.is_finite() { + retained_mass / total_count + } else { + (residual_mean / total_count) * retained_count + }; + require_finite(result) } fn same_sign_mean(values: &[f64]) -> Result { @@ -570,6 +581,13 @@ mod tests { ]) .expect("subnormal cancellation residue"); assert_eq!(subnormal.to_bits(), minimum_subnormal.to_bits()); + let finite_after_mass_overflow = scaled_compensated_mean(&[large, large, -1.0, -1.0]) + .expect("representable final mean after retained-mass overflow"); + assert!(finite_after_mass_overflow.is_finite()); + assert!( + (finite_after_mass_overflow - large / 2.0).abs() + <= (large / 2.0) * 4.0 * f64::EPSILON + ); assert_eq!( scaled_compensated_mean(&[]), Err(LongitudinalError::InvalidTemporalTransformInput) @@ -774,14 +792,22 @@ mod tests { recover_centered_irregular_residual_log_rate(&[lagged(1.0, -0.5, 1.0)]), Err(LongitudinalError::InvalidTemporalTransformInput) ); - assert_eq!( - recover_centered_irregular_residual_log_rate(&[lagged( - f64::from_bits(1), - f64::MAX, - 1.0 - )]), - Err(LongitudinalError::InvalidTemporalTransformInput) - ); + let overflow_ratio = recover_centered_irregular_residual_log_rate(&[lagged( + f64::from_bits(1), + f64::MAX, + 1.0, + )]) + .expect("finite log-domain overflow fallback"); + let expected_overflow = f64::MAX.ln() - f64::from_bits(1).ln(); + assert_eq!(overflow_ratio.to_bits(), expected_overflow.to_bits()); + let underflow_ratio = recover_centered_irregular_residual_log_rate(&[lagged( + f64::MAX, + f64::from_bits(1), + 1.0, + )]) + .expect("finite log-domain underflow fallback"); + let expected_underflow = f64::from_bits(1).ln() - f64::MAX.ln(); + assert_eq!(underflow_ratio.to_bits(), expected_underflow.to_bits()); assert_eq!( recover_centered_irregular_residual_log_rate(&[lagged( 1e-160, From 8d4d0a655df9cb622fc105b012c7d916866724b1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 20:10:06 +0900 Subject: [PATCH 065/296] test(longitudinal): require one irregular-rate numerical authority --- ...rregular_rate_shadow_authority_contract.rs | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/crates/longitudinal_core/tests/irregular_rate_shadow_authority_contract.rs b/crates/longitudinal_core/tests/irregular_rate_shadow_authority_contract.rs index 05bcf9e5c..8ae91fd12 100644 --- a/crates/longitudinal_core/tests/irregular_rate_shadow_authority_contract.rs +++ b/crates/longitudinal_core/tests/irregular_rate_shadow_authority_contract.rs @@ -2,11 +2,10 @@ //! Architecture fitness for one Longitudinal Modeling irregular-rate estimand. //! -//! The crate-public implementation already survives non-representable direct -//! residual ratios by evaluating the equivalent log-domain difference. The -//! private composition module must not retain the superseded direct-ratio -//! rejection path, because that would leave two scientifically different -//! implementations of the same named estimand in one bounded context. +//! The crate-public path and its internal temporal composition must share one +//! numerical authority. A compatibility/public facade may forward the named +//! operations, but it must not carry a second mean/log-rate implementation +//! whose floating-point edge semantics can drift independently. #[test] fn shadowed_centered_rate_does_not_reintroduce_direct_ratio_rejection() { @@ -21,3 +20,21 @@ fn shadowed_centered_rate_does_not_reintroduce_direct_ratio_rejection() { "the internal composition must reuse the Longitudinal Modeling same-sign log-rate primitive" ); } + +#[test] +fn public_irregular_rate_facade_has_no_second_numerical_implementation() { + let facade = include_str!("../src/stable_irregular_rate.rs"); + + assert!( + !facade.contains("fn stable_mean("), + "stable_irregular_rate still owns a second floating-point mean implementation" + ); + assert!( + facade.contains("irregular_residual::recover_centered_irregular_residual_log_rate"), + "the public facade must delegate the centered-rate estimand to the canonical Longitudinal Modeling implementation" + ); + assert!( + facade.contains("irregular_residual::recover_within_unit_irregular_residual_log_rate"), + "the public facade must delegate the CWC irregular-rate estimand to the canonical Longitudinal Modeling implementation" + ); +} From a9f84756d9e47f945040470b04268fb837ddfe4e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 20:10:22 +0900 Subject: [PATCH 066/296] refactor(longitudinal): make stable rate API a canonical facade --- .../src/stable_irregular_rate.rs | 218 ++---------------- 1 file changed, 22 insertions(+), 196 deletions(-) diff --git a/crates/longitudinal_core/src/stable_irregular_rate.rs b/crates/longitudinal_core/src/stable_irregular_rate.rs index 029bbf886..59334d24c 100644 --- a/crates/longitudinal_core/src/stable_irregular_rate.rs +++ b/crates/longitudinal_core/src/stable_irregular_rate.rs @@ -1,219 +1,45 @@ -//! Numerically stable public composition for irregular residual log-rates. +//! Public facade for the canonical irregular residual log-rate composition. //! -//! This module keeps the public longitudinal estimand unchanged while making -//! the final count weighting robust when an intermediate `mean * count` would -//! overflow even though the mathematical weighted mean is representable. +//! Numerical ownership lives in [`crate::irregular_residual`], alongside event +//! ordering, CWC admission, and the shared same-sign log-rate primitive. This +//! module preserves the crate-public API introduced during the overflow repair +//! without maintaining a second floating-point implementation that can drift +//! from the Longitudinal Modeling estimand. -use crate::irregular_residual::{ - EventTimedObservation, LaggedWithinResidual, center_within_unit_event_lags, - driver_same_sign_log_rate, same_sign_nonzero, -}; use crate::LongitudinalError; +use crate::irregular_residual::{self, EventTimedObservation, LaggedWithinResidual}; /// Pairwise-mean exact log-rate after CWC on irregular event intervals. /// -/// Temporal admission and CWC remain owned by the longitudinal composition in -/// [`center_within_unit_event_lags`]. Same-sign finite pair rates are then -/// averaged without requiring an overflowing intermediate sum or count product. +/// This is a public facade over the canonical Longitudinal Modeling +/// implementation. Temporal admission, same-sign log-domain fallback, and +/// overflow-safe count weighting are therefore identical to the internal +/// composition used by the bounded context. /// /// # Errors /// -/// Propagates temporal-admission errors and returns -/// [`LongitudinalError::InvalidTemporalTransformInput`] when no admissible -/// same-sign pair exists or a finite rate cannot be represented. +/// Propagates the canonical temporal-admission and numerical errors from +/// [`irregular_residual::recover_within_unit_irregular_residual_log_rate`]. pub fn recover_within_unit_irregular_residual_log_rate( rows: &[EventTimedObservation], ) -> Result { - let lagged = center_within_unit_event_lags(rows)?; - let mut rates = Vec::with_capacity(lagged.len()); - for pair in &lagged { - if !same_sign_nonzero(pair.earlier_residual(), pair.later_residual()) { - continue; - } - rates.push(driver_same_sign_log_rate( - pair.earlier_residual(), - pair.later_residual(), - pair.event_interval(), - )?); - } - stable_mean(&rates) + irregular_residual::recover_within_unit_irregular_residual_log_rate(rows) } /// Mean exact scalar log-rate on already-centered residuals. /// -/// Each pair is `ln(|later| / |earlier|) / Δt` and therefore requires finite, -/// nonzero residuals of equal sign. Direct division is used when its positive -/// ratio is representable; otherwise the equivalent log-domain difference is -/// used so ratio overflow or underflow cannot reject a finite log-rate. -/// Opposing extreme finite rates are cancelled before count weighting so a -/// representable final mean is not rejected merely because an intermediate -/// multiplication overflows. +/// This facade delegates to the single canonical implementation. Finite, +/// nonzero same-sign residuals use the direct positive ratio logarithm when it +/// is representable and the equivalent log-domain difference otherwise; a +/// representable final mean is not rejected only because an intermediate +/// ratio or retained-count product overflows or underflows. /// /// # Errors /// -/// Returns [`LongitudinalError::InvalidObservationPayload`] for an empty input -/// or non-finite residuals, and -/// [`LongitudinalError::InvalidTemporalTransformInput`] for opposite-sign or -/// zero residuals, a non-finite rate, or a non-representable final mean. +/// Propagates payload, sign/zero, event-interval, and representability errors +/// from [`irregular_residual::recover_centered_irregular_residual_log_rate`]. pub fn recover_centered_irregular_residual_log_rate( pairs: &[LaggedWithinResidual], ) -> Result { - if pairs.is_empty() { - return Err(LongitudinalError::InvalidObservationPayload); - } - - let mut rates = Vec::with_capacity(pairs.len()); - for pair in pairs { - if !pair.earlier_residual().is_finite() || !pair.later_residual().is_finite() { - return Err(LongitudinalError::InvalidObservationPayload); - } - if !same_sign_nonzero(pair.earlier_residual(), pair.later_residual()) { - return Err(LongitudinalError::InvalidTemporalTransformInput); - } - rates.push(driver_same_sign_log_rate( - pair.earlier_residual(), - pair.later_residual(), - pair.event_interval(), - )?); - } - - stable_mean(&rates) -} - -fn stable_mean(values: &[f64]) -> Result { - if values.is_empty() { - return Err(LongitudinalError::InvalidTemporalTransformInput); - } - - let mut positives = Vec::new(); - let mut negatives = Vec::new(); - for &value in values { - if !value.is_finite() { - return Err(LongitudinalError::InvalidTemporalTransformInput); - } - if value > 0.0 { - positives.push(value); - } else if value < 0.0 { - negatives.push(value); - } - } - - if positives.is_empty() && negatives.is_empty() { - return Ok(0.0); - } - if positives.is_empty() || negatives.is_empty() { - return same_sign_mean(values); - } - - positives.sort_by(|left, right| right.total_cmp(left)); - negatives.sort_by(|left, right| left.total_cmp(right)); - - let mut positive_index = 0_usize; - let mut negative_index = 0_usize; - let mut positive = positives[0]; - let mut negative = negatives[0]; - let mut residuals = Vec::with_capacity(values.len()); - - loop { - let residual = positive + negative; - if residual > 0.0 { - positive = residual; - negative_index += 1; - if negative_index == negatives.len() { - residuals.push(positive); - residuals.extend_from_slice(&positives[positive_index + 1..]); - break; - } - negative = negatives[negative_index]; - } else if residual < 0.0 { - negative = residual; - positive_index += 1; - if positive_index == positives.len() { - residuals.push(negative); - residuals.extend_from_slice(&negatives[negative_index + 1..]); - break; - } - positive = positives[positive_index]; - } else { - positive_index += 1; - negative_index += 1; - if positive_index == positives.len() || negative_index == negatives.len() { - residuals.extend_from_slice(&positives[positive_index..]); - residuals.extend_from_slice(&negatives[negative_index..]); - break; - } - positive = positives[positive_index]; - negative = negatives[negative_index]; - } - } - - if residuals.is_empty() { - return Ok(0.0); - } - - let residual_mean = same_sign_mean(&residuals)?; - scale_mean_by_count_ratio(residual_mean, residuals.len(), values.len()) -} - -fn scale_mean_by_count_ratio( - mean: f64, - retained_count: usize, - total_count: usize, -) -> Result { - let retained_count = retained_count as f64; - let total_count = total_count as f64; - - let retained_mass = mean * retained_count; - let result = if retained_mass.is_finite() { - retained_mass / total_count - } else { - // Overflow here implies a large finite mean. Divide first only on this - // branch; unlike unconditional `mean * (k / n)`, it cannot erase the - // subnormal retained-mass cases that motivated the preceding repair. - (mean / total_count) * retained_count - }; - - if result.is_finite() { - Ok(result) - } else { - Err(LongitudinalError::InvalidTemporalTransformInput) - } -} - -fn same_sign_mean(values: &[f64]) -> Result { - let mut mean = 0.0_f64; - for (index, &value) in values.iter().enumerate() { - let count = (index + 1) as f64; - mean += (value - mean) / count; - } - if mean.is_finite() { - Ok(mean) - } else { - Err(LongitudinalError::InvalidTemporalTransformInput) - } -} - -#[cfg(test)] -mod tests { - use super::stable_mean; - - #[test] - fn preserves_subnormal_retained_mass_without_overflowing_large_case() { - let minimum_subnormal = f64::from_bits(1); - let subnormal = stable_mean(&[ - f64::MAX, - f64::from_bits(2), - f64::from_bits(2), - -f64::MAX, - ]) - .expect("subnormal retained mass"); - assert_eq!(subnormal.to_bits(), minimum_subnormal.to_bits()); - - let large = 1.45e308_f64; - assert!(!(large * 2.0).is_finite()); - let finite = stable_mean(&[large, large, -1.0, -1.0]) - .expect("representable final mean"); - assert!(finite.is_finite()); - assert!((finite - large / 2.0).abs() <= (large / 2.0) * 4.0 * f64::EPSILON); - } + irregular_residual::recover_centered_irregular_residual_log_rate(pairs) } From cb416f7cc63f4ee9d08ef51e23ad48b62ba977dc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 22:32:59 +0900 Subject: [PATCH 067/296] test(longitudinal): expose stationary fallback double rounding --- crates/longitudinal_core/src/stationary.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crates/longitudinal_core/src/stationary.rs b/crates/longitudinal_core/src/stationary.rs index e483d0590..143167e72 100644 --- a/crates/longitudinal_core/src/stationary.rs +++ b/crates/longitudinal_core/src/stationary.rs @@ -61,6 +61,18 @@ mod tests { assert_eq!(stationary.to_bits(), 1); } + #[test] + fn overflow_fallback_does_not_double_round_a_minimum_subnormal_result() { + // The exact q / (-2a) rounds to the minimum positive subnormal. The + // predecessor fallback rounded once during division and again during + // its final quarter-scale, returning two subnormal ulps instead. + let diffusion = f64::from_bits(0x3cdad6b3492a639e); + let log_rate = -f64::from_bits(0x7fe2342c95642bec); + let stationary = recover_stationary_within_variance(diffusion, log_rate) + .expect("the final stationary variance is representable"); + assert_eq!(stationary.to_bits(), 1); + } + #[test] fn stationary_variance_admission_is_fail_closed() { assert_eq!(recover_stationary_within_variance(0.0, -0.5), Ok(0.0)); From 5c21ca130c1054307b00d369d970bc8caa31ff03 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 22:33:31 +0900 Subject: [PATCH 068/296] fix(longitudinal): avoid stationary fallback double rounding --- crates/longitudinal_core/src/stationary.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/longitudinal_core/src/stationary.rs b/crates/longitudinal_core/src/stationary.rs index 143167e72..f2524c98b 100644 --- a/crates/longitudinal_core/src/stationary.rs +++ b/crates/longitudinal_core/src/stationary.rs @@ -27,10 +27,12 @@ pub(crate) fn recover_stationary_within_variance( let stationary = if twice_rate.is_finite() { continuous_diffusion / -twice_rate } else { - // Scale the overflowing denominator down before division and compensate - // afterward. This keeps a representable minimum-subnormal result from - // being rounded to one subnormal and then erased by a final halving. - (continuous_diffusion / (-log_rate * 0.5)) * 0.25 + // In this branch |a| > MAX/2, so halving the finite numerator before + // division is the exact power-of-two rescaling q/(2|a|). It avoids the + // predecessor's division-then-quarter sequence, which could round twice + // at the subnormal boundary. If q/2 itself underflows here, q/(2|a|) + // is necessarily far below the minimum representable positive value. + (continuous_diffusion * 0.5) / -log_rate }; if !stationary.is_finite() { return Err(LongitudinalError::InvalidTemporalTransformInput); From 9a706c3c0e9e0db68e88f89b94c64c13ea7fafd0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 23:27:52 +0900 Subject: [PATCH 069/296] test(longitudinal): reproduce overflowing CWC raw sum --- .../tests/cwc_irregular_residual_contract.rs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/crates/longitudinal_core/tests/cwc_irregular_residual_contract.rs b/crates/longitudinal_core/tests/cwc_irregular_residual_contract.rs index 6b85ab7a5..6a493df63 100644 --- a/crates/longitudinal_core/tests/cwc_irregular_residual_contract.rs +++ b/crates/longitudinal_core/tests/cwc_irregular_residual_contract.rs @@ -61,6 +61,28 @@ fn cwc_mean_is_deterministic_under_input_row_permutation() { assert_eq!(reordered, expected, "CWC output must not depend on input row order"); } +#[test] +fn cwc_mean_survives_overflowing_raw_sum_when_centered_values_are_finite() { + let rows = [ + timed(1, 0.0, f64::MAX * 0.75), + timed(1, 1.0, f64::MAX * 0.75), + timed(1, 2.0, -f64::MAX * 0.5), + timed(2, 0.0, 2.0), + timed(2, 1.0, 1.0), + ]; + assert!( + (rows[0].score() + rows[1].score()).is_infinite(), + "fixture must overflow naive same-order summation" + ); + + let pairs = center_within_unit_event_lags(&rows) + .expect("finite CWC residuals must not be rejected because a raw sum overflows"); + assert_eq!(pairs.len(), 3); + assert!(pairs.iter().all(|pair| { + pair.earlier_residual().is_finite() && pair.later_residual().is_finite() + })); +} + #[test] fn cwc_of_raw_autoregressive_path_is_not_process_drift() { let drift = (0.5_f64).ln(); From 260413efb9d95039b5fbba41919cba8097fcf8b5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 23:31:18 +0900 Subject: [PATCH 070/296] fix(longitudinal): stabilize CWC unit means across overflow --- .../src/irregular_residual.rs | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/crates/longitudinal_core/src/irregular_residual.rs b/crates/longitudinal_core/src/irregular_residual.rs index 97404e0e9..b03bdbf21 100644 --- a/crates/longitudinal_core/src/irregular_residual.rs +++ b/crates/longitudinal_core/src/irregular_residual.rs @@ -109,9 +109,9 @@ impl LaggedWithinResidual { /// /// Returns [`LongitudinalError::InvalidObservationPayload`] for empty, /// singleton-only, fewer-than-two-unit, or non-finite rows, including -/// overflowed unit means and overflowing CWC residuals after a finite mean, -/// and [`LongitudinalError::NonPositiveEventInterval`] when any consecutive -/// event interval is not strictly positive. +/// non-representable stable unit means and overflowing CWC residuals after a +/// finite mean, and [`LongitudinalError::NonPositiveEventInterval`] when any +/// consecutive event interval is not strictly positive. pub fn center_within_unit_event_lags( rows: &[EventTimedObservation], ) -> Result, LongitudinalError> { @@ -134,15 +134,9 @@ pub fn center_within_unit_event_lags( continue; } occasions.sort_by(|left, right| left.event_time().total_cmp(&right.event_time())); - let count = occasions.len() as f64; - let mut total = 0.0_f64; - for row in occasions.iter() { - total += row.score(); - } - let mean = total / count; - if !mean.is_finite() { - return Err(LongitudinalError::InvalidObservationPayload); - } + let scores: Vec = occasions.iter().map(|row| row.score()).collect(); + let mean = scaled_compensated_mean(&scores) + .map_err(|_| LongitudinalError::InvalidObservationPayload)?; for window in occasions.windows(2) { let earlier_residual = window[0].score() - mean; let later_residual = window[1].score() - mean; @@ -757,7 +751,7 @@ mod tests { timed(2, 0.0, 1.0), timed(2, 1.0, 0.5), ]), - Err(LongitudinalError::InvalidObservationPayload) + Err(LongitudinalError::InvalidTemporalTransformInput) ); assert_eq!( center_within_unit_event_lags(&[ From d72ba2b6e28909c6def73a2638ebd63258dec500 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 00:08:11 +0900 Subject: [PATCH 071/296] test(longitudinal): reproduce representable RMSE after residual overflow --- crates/longitudinal_core/src/component.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/crates/longitudinal_core/src/component.rs b/crates/longitudinal_core/src/component.rs index 846487af6..51808311b 100644 --- a/crates/longitudinal_core/src/component.rs +++ b/crates/longitudinal_core/src/component.rs @@ -129,6 +129,26 @@ mod tests { assert!((got - expected).abs() <= expected * 4.0 * f64::EPSILON); } + #[test] + fn representable_rmse_survives_an_overflowing_individual_residual() { + let truth = [ + ComponentValue::new(0, 0, ComponentLevel::Between, -f64::MAX), + ComponentValue::new(1, 0, ComponentLevel::Between, 0.0), + ComponentValue::new(2, 0, ComponentLevel::Between, 0.0), + ComponentValue::new(3, 0, ComponentLevel::Between, 0.0), + ]; + let decided = [ + ComponentValue::new(0, 0, ComponentLevel::Between, f64::MAX), + ComponentValue::new(1, 0, ComponentLevel::Between, 0.0), + ComponentValue::new(2, 0, ComponentLevel::Between, 0.0), + ComponentValue::new(3, 0, ComponentLevel::Between, 0.0), + ]; + assert_eq!( + component_root_mean_square_error(&truth, &decided), + Ok(f64::MAX) + ); + } + #[test] fn overflowing_residual_fails_closed() { let truth = [ComponentValue::new(0, 0, ComponentLevel::Within, -f64::MAX)]; From 406e6ae2b2a8fd99494e9bc82e61ced9b81bffe0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 00:09:02 +0900 Subject: [PATCH 072/296] fix(longitudinal): scale RMSE before residual subtraction --- crates/longitudinal_core/src/component.rs | 50 ++++++++++++++--------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/crates/longitudinal_core/src/component.rs b/crates/longitudinal_core/src/component.rs index 51808311b..b99d8535a 100644 --- a/crates/longitudinal_core/src/component.rs +++ b/crates/longitudinal_core/src/component.rs @@ -58,14 +58,17 @@ impl ComponentValue { /// RMSE of recovered components against known-truth components. /// -/// The sum of squared residuals is accumulated with max-magnitude scaling so -/// large finite residuals cannot overflow to infinity. +/// Finite truth and decided values are normalized by their largest absolute +/// component magnitude before residual subtraction. The normalized residual is +/// bounded by two, so an otherwise representable RMSE is not rejected merely +/// because one raw `decided - truth` intermediate exceeds binary64 range. +/// Scaling back happens only after the mean square and square root are formed. /// /// # Errors /// /// Returns [`LongitudinalError::InvalidComponentPayload`] when either slice is -/// empty, the lengths differ, a unit/occasion/level identity mismatches, a -/// value or a computed residual is non-finite. +/// empty, the lengths differ, a unit/occasion/level identity mismatches, an +/// input value is non-finite, or the final RMSE is not representable. pub fn component_root_mean_square_error( truth: &[ComponentValue], decided: &[ComponentValue], @@ -73,8 +76,8 @@ pub fn component_root_mean_square_error( if truth.is_empty() || truth.len() != decided.len() { return Err(LongitudinalError::InvalidComponentPayload); } - let mut scale = 0.0_f64; - let mut scaled_sum_squares = 0.0_f64; + + let mut component_scale = 0.0_f64; for (truth_row, decided_row) in truth.iter().zip(decided) { if truth_row.unit_index() != decided_row.unit_index() || truth_row.occasion_index() != decided_row.occasion_index() @@ -84,21 +87,28 @@ pub fn component_root_mean_square_error( { return Err(LongitudinalError::InvalidComponentPayload); } - let residual = decided_row.value() - truth_row.value(); - if !residual.is_finite() { - return Err(LongitudinalError::InvalidComponentPayload); - } - let magnitude = residual.abs(); - if magnitude > scale { - let ratio = scale / magnitude; - scaled_sum_squares = 1.0 + scaled_sum_squares * ratio * ratio; - scale = magnitude; - } else if scale > 0.0 { - let ratio = magnitude / scale; - scaled_sum_squares += ratio * ratio; - } + component_scale = component_scale + .max(truth_row.value().abs()) + .max(decided_row.value().abs()); + } + + if component_scale == 0.0 { + return Ok(0.0); + } + + let mut scaled_sum_squares = 0.0_f64; + for (truth_row, decided_row) in truth.iter().zip(decided) { + let normalized_residual = + decided_row.value() / component_scale - truth_row.value() / component_scale; + scaled_sum_squares += normalized_residual * normalized_residual; + } + let normalized_rmse = (scaled_sum_squares / truth.len() as f64).sqrt(); + let rmse = component_scale * normalized_rmse; + if rmse.is_finite() { + Ok(rmse) + } else { + Err(LongitudinalError::InvalidComponentPayload) } - Ok(scale * (scaled_sum_squares / truth.len() as f64).sqrt()) } #[cfg(test)] From 1292fdc77b810dedf3d75b836744ac9ce8611014 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 00:17:48 +0900 Subject: [PATCH 073/296] test(longitudinal): reproduce representable unit mean after sum overflow --- crates/longitudinal_core/src/decompose.rs | 26 ++++++++++++++--------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/crates/longitudinal_core/src/decompose.rs b/crates/longitudinal_core/src/decompose.rs index 0a24ca650..3ff7db810 100644 --- a/crates/longitudinal_core/src/decompose.rs +++ b/crates/longitudinal_core/src/decompose.rs @@ -170,16 +170,6 @@ mod tests { decompose_within_between(&nan), Err(LongitudinalError::InvalidObservationPayload) ); - let overflow = [ - OccasionObservation::new(0, 0, f64::MAX), - OccasionObservation::new(0, 1, f64::MAX), - OccasionObservation::new(1, 0, 0.0), - OccasionObservation::new(1, 1, 0.0), - ]; - assert_eq!( - decompose_within_between(&overflow), - Err(LongitudinalError::InvalidObservationPayload) - ); let recovered = decompose_within_between(&[ OccasionObservation::new(1, 1, 4.0), OccasionObservation::new(0, 1, 2.0), @@ -192,4 +182,20 @@ mod tests { assert!((recovered[0].value() - 1.0).abs() < f64::EPSILON); assert_eq!(OccasionObservation::new(9, 8, 0.0).unit_index(), 9); } + + #[test] + fn representable_unit_mean_survives_raw_sum_overflow() { + let recovered = decompose_within_between(&[ + OccasionObservation::new(0, 0, f64::MAX), + OccasionObservation::new(0, 1, f64::MAX), + OccasionObservation::new(1, 0, 0.0), + OccasionObservation::new(1, 1, 0.0), + ]) + .expect("representable unit mean must not fail on an overflowing partial sum"); + + assert_eq!(recovered[0].level(), ComponentLevel::Between); + assert_eq!(recovered[0].value(), f64::MAX); + assert_eq!(recovered[1].value(), 0.0); + assert_eq!(recovered[2].value(), 0.0); + } } From 6bf3661bf390e0de00e1bb83539e6d62ee06b85f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 00:18:48 +0900 Subject: [PATCH 074/296] fix(longitudinal): scale unit means before accumulation --- crates/longitudinal_core/src/decompose.rs | 54 ++++++++++++++++++----- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/crates/longitudinal_core/src/decompose.rs b/crates/longitudinal_core/src/decompose.rs index 3ff7db810..4300d54b0 100644 --- a/crates/longitudinal_core/src/decompose.rs +++ b/crates/longitudinal_core/src/decompose.rs @@ -42,17 +42,52 @@ impl OccasionObservation { } } +fn stable_unit_mean(rows: &[OccasionObservation]) -> Result { + let scale = rows + .iter() + .map(|row| row.score().abs()) + .fold(0.0_f64, f64::max); + if scale == 0.0 { + return Ok(0.0); + } + + // Normalization keeps every addend in [-1, 1], while Neumaier + // compensation preserves cancellation that a raw `sum / n` would lose. + let mut sum = 0.0_f64; + let mut correction = 0.0_f64; + for row in rows { + let value = row.score() / scale; + let next = sum + value; + if sum.abs() >= value.abs() { + correction += (sum - next) + value; + } else { + correction += (value - next) + sum; + } + sum = next; + } + let normalized_mean = (sum + correction) / rows.len() as f64; + let mean = scale * normalized_mean; + if mean.is_finite() { + Ok(mean) + } else { + Err(LongitudinalError::InvalidObservationPayload) + } +} + /// Decompose occasion scores into unit means and within residuals. /// /// Each unit contributes one between component at occasion `0` and one within /// residual per observed occasion. Units and occasions are emitted in sorted -/// order so recovery tests can pair known truth without extra matching. +/// order so recovery tests can pair known truth without extra matching. Unit +/// means are accumulated after max-magnitude normalization so a representable +/// mean is not rejected merely because its raw partial sum exceeds binary64. /// /// # Errors /// /// Returns [`LongitudinalError::InvalidObservationPayload`] when fewer than two /// units are present, any unit has fewer than two occasions, a `(unit, -/// occasion)` pair is duplicated, or a score is non-finite. +/// occasion)` pair is duplicated, a score is non-finite, or a resulting mean +/// or within residual is not representable. pub fn decompose_within_between( observations: &[OccasionObservation], ) -> Result, LongitudinalError> { @@ -93,21 +128,18 @@ pub fn decompose_within_between( if count < 2 { return Err(LongitudinalError::InvalidObservationPayload); } - let mut total = 0.0_f64; - for row in &rows[start..end] { - total += row.score(); - } - let mean = total / count as f64; - if !mean.is_finite() { - return Err(LongitudinalError::InvalidObservationPayload); - } + let mean = stable_unit_mean(&rows[start..end])?; components.push(ComponentValue::new(unit, 0, ComponentLevel::Between, mean)); for row in &rows[start..end] { + let residual = row.score() - mean; + if !residual.is_finite() { + return Err(LongitudinalError::InvalidObservationPayload); + } components.push(ComponentValue::new( unit, row.occasion_index(), ComponentLevel::Within, - row.score() - mean, + residual, )); } } From dd0718e5d1b91baccc7efa4d196825c9119cd8e7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 00:21:35 +0900 Subject: [PATCH 075/296] test(longitudinal): preserve finite residuals beside extreme equal endpoints --- crates/longitudinal_core/src/component.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/crates/longitudinal_core/src/component.rs b/crates/longitudinal_core/src/component.rs index b99d8535a..3eaf0c4f7 100644 --- a/crates/longitudinal_core/src/component.rs +++ b/crates/longitudinal_core/src/component.rs @@ -159,6 +159,23 @@ mod tests { ); } + #[test] + fn finite_residual_precision_is_not_lost_to_unrelated_endpoint_scale() { + let truth = [ + ComponentValue::new(0, 0, ComponentLevel::Between, f64::MAX), + ComponentValue::new(1, 0, ComponentLevel::Between, 0.0), + ]; + let decided = [ + ComponentValue::new(0, 0, ComponentLevel::Between, f64::MAX), + ComponentValue::new(1, 0, ComponentLevel::Between, 1.0), + ]; + let expected = 1.0 / f64::sqrt(2.0); + assert_eq!( + component_root_mean_square_error(&truth, &decided), + Ok(expected) + ); + } + #[test] fn overflowing_residual_fails_closed() { let truth = [ComponentValue::new(0, 0, ComponentLevel::Within, -f64::MAX)]; From 312dbcd25a7246683d5596385571068d475fc4c3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 00:22:34 +0900 Subject: [PATCH 076/296] fix(longitudinal): scale only overflowed RMSE residuals --- crates/longitudinal_core/src/component.rs | 73 +++++++++++++++++------ 1 file changed, 54 insertions(+), 19 deletions(-) diff --git a/crates/longitudinal_core/src/component.rs b/crates/longitudinal_core/src/component.rs index 3eaf0c4f7..53199e059 100644 --- a/crates/longitudinal_core/src/component.rs +++ b/crates/longitudinal_core/src/component.rs @@ -56,13 +56,38 @@ impl ComponentValue { } } +fn add_scaled_square( + scale: &mut f64, + scaled_sum_squares: &mut f64, + residual_scale: f64, + residual_ratio: f64, +) { + if residual_scale == 0.0 || residual_ratio == 0.0 { + return; + } + if *scale == 0.0 { + *scale = residual_scale; + *scaled_sum_squares = residual_ratio * residual_ratio; + } else if residual_scale > *scale { + let ratio = *scale / residual_scale; + *scaled_sum_squares = + *scaled_sum_squares * ratio * ratio + residual_ratio * residual_ratio; + *scale = residual_scale; + } else { + let ratio = residual_scale / *scale; + let normalized = ratio * residual_ratio; + *scaled_sum_squares += normalized * normalized; + } +} + /// RMSE of recovered components against known-truth components. /// -/// Finite truth and decided values are normalized by their largest absolute -/// component magnitude before residual subtraction. The normalized residual is -/// bounded by two, so an otherwise representable RMSE is not rejected merely -/// because one raw `decided - truth` intermediate exceeds binary64 range. -/// Scaling back happens only after the mean square and square root are formed. +/// Direct finite residuals keep their own magnitude so small recovery errors +/// are not erased merely because an unrelated matched component is extreme. +/// If one finite endpoint subtraction overflows, that residual alone is +/// represented as `endpoint_scale × normalized_difference`, where the latter +/// is bounded by two. The root-mean-square accumulator then rescales those +/// representations without ever materializing a non-representable residual. /// /// # Errors /// @@ -77,7 +102,8 @@ pub fn component_root_mean_square_error( return Err(LongitudinalError::InvalidComponentPayload); } - let mut component_scale = 0.0_f64; + let mut scale = 0.0_f64; + let mut scaled_sum_squares = 0.0_f64; for (truth_row, decided_row) in truth.iter().zip(decided) { if truth_row.unit_index() != decided_row.unit_index() || truth_row.occasion_index() != decided_row.occasion_index() @@ -87,23 +113,32 @@ pub fn component_root_mean_square_error( { return Err(LongitudinalError::InvalidComponentPayload); } - component_scale = component_scale - .max(truth_row.value().abs()) - .max(decided_row.value().abs()); - } - if component_scale == 0.0 { - return Ok(0.0); + let residual = decided_row.value() - truth_row.value(); + if residual.is_finite() { + add_scaled_square( + &mut scale, + &mut scaled_sum_squares, + residual.abs(), + 1.0, + ); + } else { + let endpoint_scale = truth_row.value().abs().max(decided_row.value().abs()); + let normalized_residual = + decided_row.value() / endpoint_scale - truth_row.value() / endpoint_scale; + add_scaled_square( + &mut scale, + &mut scaled_sum_squares, + endpoint_scale, + normalized_residual.abs(), + ); + } } - let mut scaled_sum_squares = 0.0_f64; - for (truth_row, decided_row) in truth.iter().zip(decided) { - let normalized_residual = - decided_row.value() / component_scale - truth_row.value() / component_scale; - scaled_sum_squares += normalized_residual * normalized_residual; + if scale == 0.0 { + return Ok(0.0); } - let normalized_rmse = (scaled_sum_squares / truth.len() as f64).sqrt(); - let rmse = component_scale * normalized_rmse; + let rmse = scale * (scaled_sum_squares / truth.len() as f64).sqrt(); if rmse.is_finite() { Ok(rmse) } else { From 8111b58ac3374ae26b159868ce002d755e9e7d9e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 00:23:34 +0900 Subject: [PATCH 077/296] test(longitudinal): compare finite RMSE at binary64 tolerance --- crates/longitudinal_core/src/component.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/longitudinal_core/src/component.rs b/crates/longitudinal_core/src/component.rs index 53199e059..4baf8eb36 100644 --- a/crates/longitudinal_core/src/component.rs +++ b/crates/longitudinal_core/src/component.rs @@ -205,10 +205,8 @@ mod tests { ComponentValue::new(1, 0, ComponentLevel::Between, 1.0), ]; let expected = 1.0 / f64::sqrt(2.0); - assert_eq!( - component_root_mean_square_error(&truth, &decided), - Ok(expected) - ); + let got = component_root_mean_square_error(&truth, &decided).expect("finite residual"); + assert!((got - expected).abs() <= expected * 4.0 * f64::EPSILON); } #[test] From 5d0be3993e3736b55f74659e910fe5bfd5248954 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 00:45:36 +0900 Subject: [PATCH 078/296] test(longitudinal): require one crate-public irregular-rate authority --- ...rregular_rate_shadow_authority_contract.rs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/crates/longitudinal_core/tests/irregular_rate_shadow_authority_contract.rs b/crates/longitudinal_core/tests/irregular_rate_shadow_authority_contract.rs index 8ae91fd12..0b4fb7710 100644 --- a/crates/longitudinal_core/tests/irregular_rate_shadow_authority_contract.rs +++ b/crates/longitudinal_core/tests/irregular_rate_shadow_authority_contract.rs @@ -21,6 +21,28 @@ fn shadowed_centered_rate_does_not_reintroduce_direct_ratio_rejection() { ); } +#[test] +fn canonical_irregular_rate_implementation_is_not_a_second_public_api() { + let source = include_str!("../src/irregular_residual.rs"); + + assert!( + !source.contains("pub fn recover_centered_irregular_residual_log_rate("), + "the canonical implementation must stay crate-private; stable_irregular_rate is the only crate-public facade" + ); + assert!( + !source.contains("pub fn recover_within_unit_irregular_residual_log_rate("), + "the canonical implementation must stay crate-private; stable_irregular_rate is the only crate-public facade" + ); + assert!( + source.contains("pub(crate) fn recover_centered_irregular_residual_log_rate("), + "the public facade still needs crate-visible access to the canonical centered-rate implementation" + ); + assert!( + source.contains("pub(crate) fn recover_within_unit_irregular_residual_log_rate("), + "the public facade still needs crate-visible access to the canonical CWC irregular-rate implementation" + ); +} + #[test] fn public_irregular_rate_facade_has_no_second_numerical_implementation() { let facade = include_str!("../src/stable_irregular_rate.rs"); From 75306b2445b071fa1c3201f5dd31a8c1eba08383 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 00:46:25 +0900 Subject: [PATCH 079/296] test(longitudinal): avoid overconstraining private module visibility --- ...rregular_rate_shadow_authority_contract.rs | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/crates/longitudinal_core/tests/irregular_rate_shadow_authority_contract.rs b/crates/longitudinal_core/tests/irregular_rate_shadow_authority_contract.rs index 0b4fb7710..8ae91fd12 100644 --- a/crates/longitudinal_core/tests/irregular_rate_shadow_authority_contract.rs +++ b/crates/longitudinal_core/tests/irregular_rate_shadow_authority_contract.rs @@ -21,28 +21,6 @@ fn shadowed_centered_rate_does_not_reintroduce_direct_ratio_rejection() { ); } -#[test] -fn canonical_irregular_rate_implementation_is_not_a_second_public_api() { - let source = include_str!("../src/irregular_residual.rs"); - - assert!( - !source.contains("pub fn recover_centered_irregular_residual_log_rate("), - "the canonical implementation must stay crate-private; stable_irregular_rate is the only crate-public facade" - ); - assert!( - !source.contains("pub fn recover_within_unit_irregular_residual_log_rate("), - "the canonical implementation must stay crate-private; stable_irregular_rate is the only crate-public facade" - ); - assert!( - source.contains("pub(crate) fn recover_centered_irregular_residual_log_rate("), - "the public facade still needs crate-visible access to the canonical centered-rate implementation" - ); - assert!( - source.contains("pub(crate) fn recover_within_unit_irregular_residual_log_rate("), - "the public facade still needs crate-visible access to the canonical CWC irregular-rate implementation" - ); -} - #[test] fn public_irregular_rate_facade_has_no_second_numerical_implementation() { let facade = include_str!("../src/stable_irregular_rate.rs"); From 4d1031092388f6237d99fe29149c9fbccf453d1c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 01:00:59 +0900 Subject: [PATCH 080/296] test(longitudinal): reject underflowed nonzero irregular rate --- .../tests/irregular_rate_underflow_contract.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 crates/longitudinal_core/tests/irregular_rate_underflow_contract.rs diff --git a/crates/longitudinal_core/tests/irregular_rate_underflow_contract.rs b/crates/longitudinal_core/tests/irregular_rate_underflow_contract.rs new file mode 100644 index 000000000..53e5d2caf --- /dev/null +++ b/crates/longitudinal_core/tests/irregular_rate_underflow_contract.rs @@ -0,0 +1,18 @@ +use longitudinal_core::{ + EventTimeInterval, LaggedWithinResidual, LongitudinalError, + recover_centered_irregular_residual_log_rate, +}; + +#[test] +fn distinct_residuals_with_nonrepresentable_tiny_rate_fail_closed() { + let event_interval = EventTimeInterval::new(f64::MAX).expect("finite positive event interval"); + let earlier = 1.0_f64; + let later = f64::from_bits(earlier.to_bits() + 1); + assert_ne!(earlier.to_bits(), later.to_bits()); + + let pair = LaggedWithinResidual::new(earlier, later, event_interval); + assert_eq!( + recover_centered_irregular_residual_log_rate(&[pair]), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); +} From 021130aa09584c56d77c91a2b090c34da4a484b9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 01:04:19 +0900 Subject: [PATCH 081/296] fix(longitudinal): fail closed on underflowed nonzero log rate --- .../longitudinal_core/src/irregular_residual.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/crates/longitudinal_core/src/irregular_residual.rs b/crates/longitudinal_core/src/irregular_residual.rs index b03bdbf21..d314552f1 100644 --- a/crates/longitudinal_core/src/irregular_residual.rs +++ b/crates/longitudinal_core/src/irregular_residual.rs @@ -377,19 +377,28 @@ pub(crate) fn same_sign_nonzero(earlier: f64, later: f64) -> bool { /// Caller already established same-sign nonzero residuals and an admitted /// event interval. Prefer the finite ratio logarithm so near-equal large /// residuals keep a nonzero rate. Fall back to `ln|later| − ln|earlier|` -/// only when that ratio overflows or underflows. +/// only when that ratio overflows or underflows. A represented zero rate is +/// accepted only when the residual magnitudes are exactly equal; otherwise it +/// is a non-representable nonzero change and fails closed. pub(crate) fn driver_same_sign_log_rate( earlier: f64, later: f64, event_interval: EventTimeInterval, ) -> Result { - let ratio = later.abs() / earlier.abs(); + let earlier_magnitude = earlier.abs(); + let later_magnitude = later.abs(); + let ratio = later_magnitude / earlier_magnitude; let log_ratio = if ratio.is_finite() && ratio > 0.0 { ratio.ln() } else { - later.abs().ln() - earlier.abs().ln() + later_magnitude.ln() - earlier_magnitude.ln() }; - require_finite(log_ratio / event_interval.as_f64()) + let rate = log_ratio / event_interval.as_f64(); + if !rate.is_finite() || (rate == 0.0 && later_magnitude != earlier_magnitude) { + Err(LongitudinalError::InvalidTemporalTransformInput) + } else { + Ok(rate) + } } fn require_finite(value: f64) -> Result { From 440ac6902d86dce48fedd1229ac71ab8c133bdf0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 01:05:34 +0900 Subject: [PATCH 082/296] test(longitudinal): preserve exact zero irregular rate --- .../tests/irregular_rate_underflow_contract.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/longitudinal_core/tests/irregular_rate_underflow_contract.rs b/crates/longitudinal_core/tests/irregular_rate_underflow_contract.rs index 53e5d2caf..bc6827776 100644 --- a/crates/longitudinal_core/tests/irregular_rate_underflow_contract.rs +++ b/crates/longitudinal_core/tests/irregular_rate_underflow_contract.rs @@ -16,3 +16,13 @@ fn distinct_residuals_with_nonrepresentable_tiny_rate_fail_closed() { Err(LongitudinalError::InvalidTemporalTransformInput) ); } + +#[test] +fn equal_residuals_preserve_exact_zero_rate() { + let event_interval = EventTimeInterval::new(f64::MAX).expect("finite positive event interval"); + let pair = LaggedWithinResidual::new(1.0, 1.0, event_interval); + + let recovered = recover_centered_irregular_residual_log_rate(&[pair]) + .expect("equal residual magnitudes are exact no-change"); + assert_eq!(recovered.to_bits(), 0.0_f64.to_bits()); +} From 0544def0f7e39ab50e6de963ec4332ca6ccf01f4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 02:05:48 +0900 Subject: [PATCH 083/296] test(longitudinal): reproduce subnormal unit-mean cancellation --- crates/longitudinal_core/src/decompose.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/crates/longitudinal_core/src/decompose.rs b/crates/longitudinal_core/src/decompose.rs index 4300d54b0..efe2113b0 100644 --- a/crates/longitudinal_core/src/decompose.rs +++ b/crates/longitudinal_core/src/decompose.rs @@ -230,4 +230,20 @@ mod tests { assert_eq!(recovered[1].value(), 0.0); assert_eq!(recovered[2].value(), 0.0); } + + #[test] + fn representable_subnormal_unit_mean_survives_extreme_cancellation() { + let minimum_subnormal = f64::from_bits(1); + let recovered = decompose_within_between(&[ + OccasionObservation::new(0, 0, f64::MAX), + OccasionObservation::new(0, 1, -f64::MAX), + OccasionObservation::new(0, 2, f64::from_bits(4)), + OccasionObservation::new(1, 0, 0.0), + OccasionObservation::new(1, 1, 0.0), + ]) + .expect("a representable subnormal unit mean must survive extreme cancellation"); + + assert_eq!(recovered[0].level(), ComponentLevel::Between); + assert_eq!(recovered[0].value().to_bits(), minimum_subnormal.to_bits()); + } } From bd7191cf176c9e68e450757294086429cd880b09 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 02:06:27 +0900 Subject: [PATCH 084/296] fix(longitudinal): preserve subnormal unit means after cancellation --- crates/longitudinal_core/src/decompose.rs | 96 ++++++++++++++++++----- 1 file changed, 75 insertions(+), 21 deletions(-) diff --git a/crates/longitudinal_core/src/decompose.rs b/crates/longitudinal_core/src/decompose.rs index efe2113b0..262163539 100644 --- a/crates/longitudinal_core/src/decompose.rs +++ b/crates/longitudinal_core/src/decompose.rs @@ -42,31 +42,84 @@ impl OccasionObservation { } } +fn same_sign_unit_mean(values: &[f64]) -> Result { + let mut mean = 0.0_f64; + for (index, &value) in values.iter().enumerate() { + mean += (value - mean) / (index + 1) as f64; + } + if mean.is_finite() { + Ok(mean) + } else { + Err(LongitudinalError::InvalidObservationPayload) + } +} + fn stable_unit_mean(rows: &[OccasionObservation]) -> Result { - let scale = rows - .iter() - .map(|row| row.score().abs()) - .fold(0.0_f64, f64::max); - if scale == 0.0 { + let values: Vec = rows.iter().map(|row| row.score()).collect(); + let mut positives: Vec = values.iter().copied().filter(|value| *value > 0.0).collect(); + let mut negatives: Vec = values.iter().copied().filter(|value| *value < 0.0).collect(); + + if positives.is_empty() && negatives.is_empty() { return Ok(0.0); } + if positives.is_empty() || negatives.is_empty() { + return same_sign_unit_mean(&values); + } - // Normalization keeps every addend in [-1, 1], while Neumaier - // compensation preserves cancellation that a raw `sum / n` would lose. - let mut sum = 0.0_f64; - let mut correction = 0.0_f64; - for row in rows { - let value = row.score() / scale; - let next = sum + value; - if sum.abs() >= value.abs() { - correction += (sum - next) + value; + positives.sort_by(|left, right| right.total_cmp(left)); + negatives.sort_by(|left, right| left.total_cmp(right)); + + let mut positive_index = 0_usize; + let mut negative_index = 0_usize; + let mut positive = positives[0]; + let mut negative = negatives[0]; + let mut residuals = Vec::with_capacity(values.len()); + + loop { + let residual = positive + negative; + if residual > 0.0 { + positive = residual; + negative_index += 1; + if negative_index == negatives.len() { + residuals.push(positive); + residuals.extend_from_slice(&positives[positive_index + 1..]); + break; + } + negative = negatives[negative_index]; + } else if residual < 0.0 { + negative = residual; + positive_index += 1; + if positive_index == positives.len() { + residuals.push(negative); + residuals.extend_from_slice(&negatives[negative_index + 1..]); + break; + } + positive = positives[positive_index]; } else { - correction += (value - next) + sum; + positive_index += 1; + negative_index += 1; + if positive_index == positives.len() || negative_index == negatives.len() { + residuals.extend_from_slice(&positives[positive_index..]); + residuals.extend_from_slice(&negatives[negative_index..]); + break; + } + positive = positives[positive_index]; + negative = negatives[negative_index]; } - sum = next; } - let normalized_mean = (sum + correction) / rows.len() as f64; - let mean = scale * normalized_mean; + + if residuals.is_empty() { + return Ok(0.0); + } + let residual_mean = same_sign_unit_mean(&residuals)?; + let retained_count = residuals.len() as f64; + let total_count = values.len() as f64; + let retained_mass = residual_mean * retained_count; + let mean = if retained_mass.is_finite() { + retained_mass / total_count + } else { + (residual_mean / total_count) * retained_count + }; if mean.is_finite() { Ok(mean) } else { @@ -78,9 +131,10 @@ fn stable_unit_mean(rows: &[OccasionObservation]) -> Result Date: Thu, 3 Sep 2026 02:31:28 +0900 Subject: [PATCH 085/296] test(longitudinal): pin stationary overflow guidance --- ...est_longitudinal_scientific_authority_sync.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 tests/quality/test_longitudinal_scientific_authority_sync.py diff --git a/tests/quality/test_longitudinal_scientific_authority_sync.py b/tests/quality/test_longitudinal_scientific_authority_sync.py new file mode 100644 index 000000000..161d629a4 --- /dev/null +++ b/tests/quality/test_longitudinal_scientific_authority_sync.py @@ -0,0 +1,16 @@ +"""Keep Longitudinal Modeling numerical guidance synchronized with production contracts.""" + +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[2] +CLAUDE = ROOT / "CLAUDE.md" + + +def test_stationary_variance_overflow_guidance_matches_source_contract() -> None: + """Reject the retired ratio-first overflow rewrite and require the current fallback.""" + guidance = CLAUDE.read_text(encoding="utf-8") + + assert "When `2 a` overflows, form `(q / a) * -0.5`." not in guidance + assert "Do not form `0.5 q` first (`q = from_bits(1)` underflows)." not in guidance + assert "When `2 a` overflows, form `(q * 0.5) / |a|`." in guidance From 9c962205dca26925c2e60d1e15ec4ce15681bbee Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 02:33:16 +0900 Subject: [PATCH 086/296] docs(longitudinal): align stationary overflow guidance --- CLAUDE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index b8761cf53..b6cdd2e35 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -15,7 +15,7 @@ Read and follow `AGENTS.md` before changing this repository. The repository-wide - Do not remove repeated report language with global stopword lists or use TF-IDF/BM25 as inferential weights. Model template, section, copied-text, style, modality, and corpus-background sources explicitly. - Do not treat raw topic proportions as ordinary Euclidean indicators. Use logistic-normal coordinates or valid log-ratio coordinates and propagate posterior uncertainty into ESEM/DSEM. - Do not treat metric/weak invariance as a latent-mean license. Strong (equal loading and intercept) or strict is required; `#84` `metric` licenses shared metric meaning only. Putnick and Bornstein (2016, PMC5145197 opened 2026-08-19T22:15Z) require scalar invariance before latent-mean comparison; residual invariance is not a prerequisite. Two-observation series have no residual degrees of freedom (`ordinary_least_squares_fit` returns residual variance `0`) and cap at strong/scalar; they still license means. This is two-group OLS, not MGCFA. Meredith (1993) names remain unread labels (Unpaywall/OpenAlex 2026-08-25T11:32Z: closed). -- Do not use the difference quotient as a continuous-time rate. The scalar map is `a = ln(φ) / Δt` on event time. Discrete lags from unequal event intervals are not one coefficient; remap them through that log-rate. Binary64 `exp(a Δt) = 0` is not a discrete lag. A constant predictor's discrete effect is Voelkle et al. (2012, Eq. 12), evaluated as `a_yx (expm1(z) / a_xx)` with `z = a_xx Δt` so a finite result is not lost when `z` overflows to `-∞` or when `a_yx Δt` overflows. When `expm1(z)` overflows at a finite `z`, rewrite in log space; a zero continuous effect is exactly zero; an overflowing `a_yx/a_xx` rewrite term fails closed. The first-order product is the underflow limit of that equation, not the general constant-predictor discrete effect. A time-varying predictor whose sampling interval equals its constancy interval uses Voelkle et al. (2012, Eq. 14): `b* = a_yx Δt`. Unmatched intervals fail closed (Oud & Jansen, 2000, unread). Discrete process noise is Driver et al. (2017, Eq. 3): `Q_Δt = 0.5 q (expm1(z) / a)` with `z = 2 (a Δt)` and `q = G G⊤ ≥ 0`; do not form `2 a` first; `a = 0` and `z → 0` recover `q Δt`; a zero diffusion is exactly zero; an overflowing rewrite scale `0.5 q / a` fails closed; this is not a Kalman filter. `Q_Δt` is `cov(η_t | η_{t-1})`, not `Var(η_t)`. The lagged covariance is `exp(a Δt) p` and the unconditional variance is `exp(2 a Δt) p + Q_Δt` (Driver et al., 2017, Eq. 3–4, pp. 4–5; JSS has no numbered §2.2). A zero diffusion whose `2 (a Δt)` overflows to `+∞` is not a finite `Var(η_t)`. The stationary within-subject variance is the `Δt → ∞` limit of Eq. 4: `-q / (2 a)` for stable `a < 0` (JSS p. 16 `asymDIFFUSION`; §4.3). When `2 a` is finite, form `q / -(2 a)` so `q / a` overflow does not lose a finite result (`q = MAX`, `a = -0.75` → `MAX / 1.5`). When `2 a` overflows, form `(q / a) * -0.5`. Do not form `0.5 q` first (`q = from_bits(1)` underflows). `a ≥ 0` has no finite stationary variance. Finite-interval `Q_Δt` is not that limit. Trait-plus-state variance is `trait + state` and lagged covariance is `trait + exp(a Δt) p` (Driver et al., 2017, §4.3, p. 9). Trait variance is not process noise and not `asymDIFFUSION`. Evolving the summed variance as if it were all state is not that map. This is not RI-CLPM. Observed-indicator variance is `λ² Var(η) + θ` when `MANIFESTTRAITVAR` is zero and `λ² Var(η) + θ + ψ` otherwise (Driver et al., 2017, Eq. 5, p. 5; Table 2, p. 12). Lagged observed covariance is `λ² cov(η_t, η_{t-1}) + ψ`; `MANIFESTVAR` does not enter. Observed-indicator mean is `τ + λ μ` (Driver et al., 2017, Eq. 5; Table 2, p. 12). `MANIFESTMEANS` is `τ`, not `E(y)`. `E(η)` is not `E(y)`. `CINT` is not `MANIFESTMEANS`. `T0MEANS` is not `E(y)`. The discrete latent mean is `μ_t = exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ` (Driver et al., 2017, Eq. 3, p. 4; Table 2, p. 12). `T0MEANS` is not `μ_t`. `CINT` is not that discrete increment. A zero drift is `κ Δt`. Underflow of `exp(a Δt)` to `+0` drops the carried `T0MEANS` and keeps `−κ / a`. The evolved observed mean is `τ + λ μ_t` (Driver et al., 2017, Eq. 5 of that Eq. 3 map). The first-occasion map `τ + λ μ_0` is not `E(y_t)`. `μ_t` is not `E(y_t)`. The contemporaneous time-dependent predictor impulse is `m x` (Driver et al., 2017, Eq. 3 fourth summand; Table 2 `TDPREDEFFECT` is `M`). Form `μ_t` first, then add `m x`. `TDPREDEFFECT` is not `CINT`. `M x` is not `A^{-1}[e^{A Δt} − I] B z` and is not Voelkle et al. (2012, Eq. 14). The §7.2 level-change form is not that impulse. The observed mean of that contemporaneous impulse is `τ + λ(μ_t + m x)` (Driver et al., 2017, Eq. 5 of the Eq. 3 fourth-summand composition). The evolved map `τ + λ μ_t` is not that observed mean. The carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`. The evolved-plus-impulse latent mean is not `E(y_t)`. The time-independent predictor increment is `A^{-1}[e^{A Δt} − I] B z` (Driver et al., 2017, Eq. 3 second summand; Table 2 `TIPREDEFFECT` is `B`). Form `B z` first, then the discrete intercept map. A zero drift is `B z Δt`. `TIPREDEFFECT` is `B`, not that discrete increment. `A^{-1}[e^{A Δt} − I] B z` is not `CINT`, not `M x`, and not Voelkle et al. (2012, Eq. 14). The observed mean of that increment is `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` (Driver et al., 2017, Eq. 5 of the Eq. 3 printed addend after the `T0MEANS` carry and the `CINT` increment). The evolved map `τ + λ μ_t` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`. The evolved-plus-increment latent mean is not `E(y_t)`. The within-interval time-dependent impulse carry is `e^{A(t−u)} M x` for `t0 < u < t` (Driver et al., 2017, Eq. 1–2 Green-function integral of Eq. 2; §7.2 dissipation). Form `m x` first, then `e^{a(t−u)} m x`. A zero drift is `m x` with no dissipation. Underflow of `e^{a(t−u)}` to `+0` is vanishing dissipation and is kept. `e^{A(t−u)} M x` is not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle et al. (2012, Eq. 14). An impulse at `u = t` is the contemporaneous map. An impulse at `u ≤ t0` is already in `η(t0)`. The observed mean of that carry is `τ + λ(μ_t + e^{a(t−u)} m x)` (Driver et al., 2017, Eq. 5 of the Eq. 1–2 carried latent mean). The evolved map `τ + λ μ_t` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean when `u ≠ t`. `MANIFESTMEANS` is not `E(y_t)`. The carried latent mean is not `E(y_t)`. The first-occasion time-independent predictor shift is `t0_b z` (Driver et al., 2017, Table 3 `T0TIPREDEFFECT`; Eq. 3 first summand). Form `t0_b z` first, then `e^{a Δt} t0_b z`. Form `μ_t` first, then add that carry. A zero drift is `t0_b z`. Underflow of `e^{a Δt}` to `+0` is a vanishing carry of the first-occasion shift and is kept. `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`. `e^{A Δt} t0_b z` is not `t0_b z`. `T0TIPREDEFFECT` is the coefficient, not the shift. The observed mean of that first-occasion carry is `τ + λ(μ_t + e^{a Δt} t0_b z)` (Driver et al., 2017, Eq. 5 of the Table 3 / Eq. 3 first-summand composition). The evolved map `τ + λ μ_t` is not that observed mean. The process-increment map `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The impulse-carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t0`. The evolved-plus-carry latent mean is not `E(y_t)`. The first-occasion time-dependent predictor shift is `t0_m x0` (Driver et al., 2017, Table 3 `T0TDPREDEFFECT`; Eq. 3 first summand; JSS PDF re-opened 2026-08-20T19:10Z). Form `t0_m x0` first, then `e^{a Δt} t0_m x0`. Form `μ_t` first, then add that carry. A zero drift is `t0_m x0`. Underflow of `e^{a Δt}` to `+0` is a vanishing carry of the first-occasion shift and is kept. `t0_m x0` is not `M x`, not `e^{A(t−u)} M x` for `t0 < u < t`, not `t0_b z`, not `A^{-1}[e^{A Δt} − I] B z`, and not `CINT`. `e^{A Δt} t0_m x0` is not `t0_m x0`. `T0TDPREDEFFECT` is the coefficient, not the shift. An impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`. The observed mean of that first-occasion TD carry is `τ + λ(μ_t + e^{a Δt} t0_m x0)` (Driver et al., 2017, Eq. 5 of the Table 3 / Eq. 3 first-summand TD composition; JSS PDF re-opened 2026-08-20T19:07Z). The evolved map `τ + λ μ_t` is not that observed mean. The process-increment map `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The impulse-carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t0`. The first-occasion TI map `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean. The evolved-plus-carry latent mean is not `E(y_t)`. The lasting level-change `CINT` is `κ = −a m x` (Driver et al., 2017, §7.2, pp. 20–21; JSS PDF re-opened 2026-08-20T19:45Z). Form `m x` first, then multiply by `−a`. Stable `a < 0` is required so `−κ / a = m x` is an equilibrium offset. `a ≥ 0` cannot hold a new process mean. `−a m x` is not the dissipating Dirac `m x`, not a free `CINT`, and not `A^{-1}[e^{A Δt} − I] B z`. The extra near-zero-drift latent process also named in §7.2 is a different specification and is not this `CINT` setting. Equation 3 maps that intercept as `(1 − e^{a Δt}) m x` (JSS PDF re-opened 2026-08-20T19:50Z). Form the level-change `CINT` first, then the discrete intercept map. Underflow of `e^{a Δt}` to `+0` keeps `m x`. `(1 − e^{a Δt}) m x` is not `m x`, not `κ`, and not `A^{-1}[e^{A Δt} − I] B z`. The printed §7.2 lasting level change is an extra near-zero-drift latent process (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-20T23:10Z). `T0MEANS`, `CINT`, `T0VAR`, `DIFFUSION`, and `TRAITVAR` of that process are fixed to 0; `TDPREDEFFECT` on it is fixed to 1; its `DRIFT` diagonal is very close to 0 (printed example `−0.000001`; precisely 0 causes computational problems); the original process is driven by the `DRIFT` coupling `a_{ηξ}`. After a unit identification impulse the scalar contribution is `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (`ε = a` is `a_{ηξ} x Δt e^{a Δt}`). Form `a_{ηξ} x` first. A zero coupling or zero predictor is exactly zero. `ε ≥ 0` fails closed. That contribution is not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`. The observed mean of that extra-process contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a))` (Driver et al., 2017, Eq. 5 of that §7.2 contribution; JSS PDF re-opened 2026-08-21T06:12Z). The extra process has `LAMBDA` 0 and is not an observed indicator. Original indicators load on the original process after the `DRIFT` coupling. The evolved map `τ + λ μ_t` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The contribution is not `E(y_t)`. The evolved-plus-contribution latent mean is not `E(y_t)`. `T0TDPREDEFFECT` on the extra process begins at `t = 0` and uses `Δt = t − t0` for both the original-process evolution and the extra drive. `TDPREDEFFECT` after `t0` uses `t − u` with `t0 < u < t` for the extra drive while `μ_t` still uses `Δt`. The observed mean of that after-t0 extra-process contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a))` (Driver et al., 2017, Eq. 5 of that §7.2 after-t0 contribution; JSS PDF re-opened 2026-08-21T06:32Z). The first-occasion extra-process observed mean is not that observed mean when `u ≠ t0`. The impulse-carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is a Dirac on the original process and is not that `DRIFT` drive. An impulse at `u = t0` or `u = t` is not interior. The asymptotic time-independent predictor effect is `-B z / a` (Driver et al., 2017, §7.2, pp. 20–21; JSS PDF opened 2026-08-21T13:08Z). Form `B z` first, then divide by `-a`. Stable `a < 0` is required. `a ≥ 0` cannot hold a finite process-mean change. `-B z / a` is not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`. The asymptotic time-independent predictor variance is `(B / a)² v` (Driver et al., 2017, §7.2, pp. 20–21 `addedTIPREDVAR`). Form the unit asymptotic effect first, then square, then multiply by `v`. `(B / a)² v` is not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`. The asymptotic continuous intercept is `-κ / a` (Driver et al., 2017, Table 2, p. 12 `asymCINT`; Eq. 3 as `Δt → ∞`; JSS PDF opened 2026-08-21T16:13Z). Form `κ` first, then divide by `-a`. Stable `a < 0` is required. `-κ / a` is not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`. The p. 16 stationary `T0MEANS` constraint is `-κ / a + −B z / a`. Form the intercept contribution first, then include the TI extra effect, then add. That constrained first-occasion mean is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean. Equation 5 of that constrained mean is `τ + λ(−κ / a + −B z / a)` (Driver et al., 2017, §4.3, pp. 9–10; Eq. 5, p. 5; JSS PDF re-opened 2026-08-21T20:07Z). Form the stationary latent mean first, then `τ + λ` of that mean. `τ + λ μ_0` for free `T0MEANS` is not that composition. `τ + λ(−κ / a)` is not that composition when `B z ≠ 0`. `τ + λ μ_t` is not that composition. `MANIFESTMEANS` is not `E(y_0)`. The constrained latent mean is not `E(y_0)`. The p. 16 constrained first-occasion variance `trait + −q / (2 a) + (B / a)² v` is not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`). The lagged covariance of that constrained process is `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T19:13Z). Trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`. Contemporaneous `T0VAR` is not that lagged map. Decaying the constrained total as if it were all state is not that lagged map. Equation 5 of that lagged covariance is `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ`. `Θ` does not enter. Contemporaneous `Var(y_0)` is not that lagged observed covariance. The lagged latent covariance is not that observed covariance. The later-occasion variance of that constrained process is `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T23:12Z). Trait and `addedTIPREDVAR` do not enter `Q_Δt`. Under stationarity that composition equals contemporaneous `T0VAR`. Evolving the constrained total as if it were all state is not that later map. The lagged covariance omits `Q_Δt` and is not that later map. `Q_Δt` is not that later map. Equation 5 of that later-occasion variance is `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ`. The lagged observed covariance omits `Q_Δt` and `θ`. `MANIFESTVAR` is not `Var(y_t)`. The later-occasion latent variance is not `Var(y_t)`. The later-occasion variance of §4.3 predetermined `T0VAR` is `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T05:12Z). Trait and `addedTIPREDVAR` do not enter `Q_Δt`. Free `T0VAR` `p_0` is not that later map. Setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map. Stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free. Evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map. As `Δt → ∞` with stable `a < 0` the composition approaches contemporaneous stationary `T0VAR`. As `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`. Nonzero diffusion with `a ≥ 0` is a growing process and is kept. Equation 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ`. `MANIFESTVAR` is not `Var(y_t)`. The predetermined later-occasion latent variance is not `Var(y_t)`. Stationary later observed variance is not that observed variance when `p_0` is free. The lagged covariance of §4.3 predetermined `T0VAR` is `trait + e^{a Δt} p_0 + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T09:04Z). Trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`. Free `T0VAR` `p_0` is not that lagged map. Setting `p_0 = −q / (2 a)` recovers the stationary lagged map. Stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free. Evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map. Later-occasion variance includes `Q_Δt` and is not that lagged map. As `Δt → ∞` with stable `a < 0` the state term vanishes. As `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`. Equation 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ`. `MANIFESTVAR` does not enter. The predetermined lagged latent covariance is not that observed covariance. Predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance. Stationary lagged observed covariance is not that observed covariance when `p_0` is free. The predetermined first-occasion variance of §4.3 predetermined `T0VAR` is `trait + p_0 + (B / a)² v`. Free `p_0` is not that map. Stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free. Lagged covariance decays the state and is not that map. Later-occasion variance includes `Q_Δt` and is not that map. Equation 5 of that predetermined first-occasion variance is `λ²(trait + p_0 + (B / a)² v) + θ + ψ`. `MANIFESTVAR` is not that first-occasion observed variance. The predetermined first-occasion latent variance is not that observed variance. Stationary first-occasion observed variance is not that observed variance when `p_0` is free. Predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance. Later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z). First-occasion lagged omits `e^{a s} Q_u`. Later-occasion variance does not lag. Stationary lagged uses `−q / (2 a)`. Decaying the later total is not that map. Equation 5 of that later-start lagged covariance is `λ²` of it plus `ψ`. Independent `ε_t` does not enter. First-occasion lagged observed omits `e^{a s} Q_u`. Predetermined later observed variance includes `Q_u` and `θ` and is not that later-start lagged observed covariance. Later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z). Later-occasion variance at `u` omits `Q_s`. Later-start lagged covariance omits `Q_s`. Stationary later uses `−q / (2 a)`. Evolving the later total as if it were all state is not that map. Ignoring `startoffset` omits `e^{2 a s} Q_u`. Equation 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`. `MANIFESTVAR` is not that observed variance. Page 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; footnote 4; §7.1; JSS PDF re-opened 2026-08-23T11:40Z). Unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`. The §7.1 trait-plus-state autocorrelation `(trait + e^{a Δt} p + added) / (trait + p + added)` uses `TRAITVAR` and is not `discreteDRIFTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:06Z). Unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`. The continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`. `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`. `TRAITVAR` is not the standardisation variance. Page 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z). Unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`. The discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`. `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`. `TRAITVAR` is not the standardisation variance. Page 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z). Unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`. The discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`. `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z). Unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`. The finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`. `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z). Unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`. The asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`. The finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`. `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Page 16 / Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z). The affected variance is free first-occasion `T0VAR`, not `asymDIFFUSION`. Unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`. `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`. `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`. `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. 2017-era `addedT0TIPREDVAR` is `t0_b² v` after a first-occasion time-independent predictor (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z). Form `t0_b` first, then square, then multiply by `v`. A zero coefficient or zero predictor variance is exactly zero. Free `T0TIPREDEFFECT` does not require `a < 0`. `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map. `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance. Free `T0VAR` is not this extra TI variance. `TRAITVAR` is not this extra TI variance. Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z). Form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`. A zero loading or zero extra is exactly zero. `t0_b² v` is the latent extra, not the observed extra. `λ² p_0 + θ` is first-occasion observed variance, not this extra. `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra. `MANIFESTVAR` `θ` is not this extra. Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v` (Driver et al., 2017, Eq. 5, p. 5; Table 2, p. 12; §7.2, pp. 20–21; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:23Z). Form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`. A zero loading or zero extra is exactly zero. Lasting asymptotic extra requires `a < 0`. `(B / a)² v` is the latent extra, not the observed extra. `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra. `λ² p + θ` is stationary observed variance, not this extra. `MANIFESTVAR` `θ` is not this extra. Page 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance. Unstandardised `M` is not `TDPREDEFFECTstd`. `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`. intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`. `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`. Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance. Unstandardised `t0_m` is not `T0TDPREDEFFECTstd`. `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`. `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`. `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`. Free `T0VAR` does not require `a < 0`. Page 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; the default ridge is 0). Unstandardised `T0VAR` is not `T0VARstd`. `T0TDPREDEFFECTstd` is not `T0VARstd`. `addedT0TIPREDVAR` is not `T0VARstd`. Page 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend). Unstandardised `TRAITVAR` is not `TRAITVARstd`. `T0VARstd` is not `TRAITVARstd` even when both equal 1. `addedT0TIPREDVAR` is not `TRAITVARstd`. Page 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0). Unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`. `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1. `MANIFESTVAR` is not `MANIFESTTRAITVARstd`. Page 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug). Unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`. `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1. Equation 5 `Var(y)` is not `MANIFESTVARstd`. Page 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`). Unstandardised `TIPREDVAR` is not `TIPREDVARstd`. `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1. Section 7.2 `addedTIPREDVAR` is not `TIPREDVARstd`. Page 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`). Unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`. `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1. `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`. Page 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`. Unstandardised `discreteCINT` is not `discreteCINTstd`. `κ / √p` is not `discreteCINTstd`. `(-κ / a) / √p` is not `discreteCINTstd`. `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`. Unstandardised `asymCINT` is not `asymCINTstd`. `κ / √p` is not `asymCINTstd`. `discreteCINTstd` is not `asymCINTstd`. `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`. Unstandardised `T0MEANS` is not `T0MEANSstd`. `T0VARstd` is not `T0MEANSstd`. `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`. Page 16 `MANIFESTMEANSstd` is `τ / √θ` after strictly positive `MANIFESTVAR`. Unstandardised `MANIFESTMEANS` is not `MANIFESTMEANSstd`. `MANIFESTVARstd` is not `MANIFESTMEANSstd`. `τ / √(λ² Var(η) + θ)` is not `MANIFESTMEANSstd`. Page 16 `CINTstd` is `κ / √p` after strictly positive `asymDIFFUSION`. Unstandardised `CINT` is not `CINTstd`. `asymCINTstd` is not `CINTstd`. `discreteCINTstd` is not `CINTstd`. `κ / √(trait + p + added)` is not `CINTstd`. Evolving from that stationary start with `CINT` and `TIPREDEFFECT` stays at the stationary mean. Equation 1 is the latent SDE, not the measurement model. Form `(λ p) λ` then add `θ`, then add `ψ`. `MANIFESTVAR` is `Θ`, not `Var(y)`. `MANIFESTTRAITVAR` is `Ψ_τ`, not `Θ`. `TRAITVAR` is latent and scaled by `λ²`. `Var(η)` is not `Var(y)`. +- Do not use the difference quotient as a continuous-time rate. The scalar map is `a = ln(φ) / Δt` on event time. Discrete lags from unequal event intervals are not one coefficient; remap them through that log-rate. Binary64 `exp(a Δt) = 0` is not a discrete lag. A constant predictor's discrete effect is Voelkle et al. (2012, Eq. 12), evaluated as `a_yx (expm1(z) / a_xx)` with `z = a_xx Δt` so a finite result is not lost when `z` overflows to `-∞` or when `a_yx Δt` overflows. When `expm1(z)` overflows at a finite `z`, rewrite in log space; a zero continuous effect is exactly zero; an overflowing `a_yx/a_xx` rewrite term fails closed. The first-order product is the underflow limit of that equation, not the general constant-predictor discrete effect. A time-varying predictor whose sampling interval equals its constancy interval uses Voelkle et al. (2012, Eq. 14): `b* = a_yx Δt`. Unmatched intervals fail closed (Oud & Jansen, 2000, unread). Discrete process noise is Driver et al. (2017, Eq. 3): `Q_Δt = 0.5 q (expm1(z) / a)` with `z = 2 (a Δt)` and `q = G G⊤ ≥ 0`; do not form `2 a` first; `a = 0` and `z → 0` recover `q Δt`; a zero diffusion is exactly zero; an overflowing rewrite scale `0.5 q / a` fails closed; this is not a Kalman filter. `Q_Δt` is `cov(η_t | η_{t-1})`, not `Var(η_t)`. The lagged covariance is `exp(a Δt) p` and the unconditional variance is `exp(2 a Δt) p + Q_Δt` (Driver et al., 2017, Eq. 3–4, pp. 4–5; JSS has no numbered §2.2). A zero diffusion whose `2 (a Δt)` overflows to `+∞` is not a finite `Var(η_t)`. The stationary within-subject variance is the `Δt → ∞` limit of Eq. 4: `-q / (2 a)` for stable `a < 0` (JSS p. 16 `asymDIFFUSION`; §4.3). When `2 a` is finite, form `q / -(2 a)` so `q / a` overflow does not lose a finite result (`q = MAX`, `a = -0.75` → `MAX / 1.5`). When `2 a` overflows, form `(q * 0.5) / |a|`. In that branch `|a|` is already so large that halving a minimum-subnormal `q` cannot discard a representable positive final stationary variance; the ratio-first rewrite can instead overflow before the final factor restores a representable result. `a ≥ 0` has no finite stationary variance. Finite-interval `Q_Δt` is not that limit. Trait-plus-state variance is `trait + state` and lagged covariance is `trait + exp(a Δt) p` (Driver et al., 2017, §4.3, p. 9). Trait variance is not process noise and not `asymDIFFUSION`. Evolving the summed variance as if it were all state is not that map. This is not RI-CLPM. Observed-indicator variance is `λ² Var(η) + θ` when `MANIFESTTRAITVAR` is zero and `λ² Var(η) + θ + ψ` otherwise (Driver et al., 2017, Eq. 5, p. 5; Table 2, p. 12). Lagged observed covariance is `λ² cov(η_t, η_{t-1}) + ψ`; `MANIFESTVAR` does not enter. Observed-indicator mean is `τ + λ μ` (Driver et al., 2017, Eq. 5; Table 2, p. 12). `MANIFESTMEANS` is `τ`, not `E(y)`. `E(η)` is not `E(y)`. `CINT` is not `MANIFESTMEANS`. `T0MEANS` is not `E(y)`. The discrete latent mean is `μ_t = exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ` (Driver et al., 2017, Eq. 3, p. 4; Table 2, p. 12). `T0MEANS` is not `μ_t`. `CINT` is not that discrete increment. A zero drift is `κ Δt`. Underflow of `exp(a Δt)` to `+0` drops the carried `T0MEANS` and keeps `−κ / a`. The evolved observed mean is `τ + λ μ_t` (Driver et al., 2017, Eq. 5 of that Eq. 3 map). The first-occasion map `τ + λ μ_0` is not `E(y_t)`. `μ_t` is not `E(y_t)`. The contemporaneous time-dependent predictor impulse is `m x` (Driver et al., 2017, Eq. 3 fourth summand; Table 2 `TDPREDEFFECT` is `M`). Form `μ_t` first, then add `m x`. `TDPREDEFFECT` is not `CINT`. `M x` is not `A^{-1}[e^{A Δt} − I] B z` and is not Voelkle et al. (2012, Eq. 14). The §7.2 level-change form is not that impulse. The observed mean of that contemporaneous impulse is `τ + λ(μ_t + m x)` (Driver et al., 2017, Eq. 5 of the Eq. 3 fourth-summand composition). The evolved map `τ + λ μ_t` is not that observed mean. The carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`. The evolved-plus-impulse latent mean is not `E(y_t)`. The time-independent predictor increment is `A^{-1}[e^{A Δt} − I] B z` (Driver et al., 2017, Eq. 3 second summand; Table 2 `TIPREDEFFECT` is `B`). Form `B z` first, then the discrete intercept map. A zero drift is `B z Δt`. `TIPREDEFFECT` is `B`, not that discrete increment. `A^{-1}[e^{A Δt} − I] B z` is not `CINT`, not `M x`, and not Voelkle et al. (2012, Eq. 14). The observed mean of that increment is `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` (Driver et al., 2017, Eq. 5 of the Eq. 3 printed addend after the `T0MEANS` carry and the `CINT` increment). The evolved map `τ + λ μ_t` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`. The evolved-plus-increment latent mean is not `E(y_t)`. The within-interval time-dependent impulse carry is `e^{A(t−u)} M x` for `t0 < u < t` (Driver et al., 2017, Eq. 1–2 Green-function integral of Eq. 2; §7.2 dissipation). Form `m x` first, then `e^{a(t−u)} m x`. A zero drift is `m x` with no dissipation. Underflow of `e^{a(t−u)}` to `+0` is vanishing dissipation and is kept. `e^{A(t−u)} M x` is not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle et al. (2012, Eq. 14). An impulse at `u = t` is the contemporaneous map. An impulse at `u ≤ t0` is already in `η(t0)`. The observed mean of that carry is `τ + λ(μ_t + e^{a(t−u)} m x)` (Driver et al., 2017, Eq. 5 of the Eq. 1–2 carried latent mean). The evolved map `τ + λ μ_t` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean when `u ≠ t`. `MANIFESTMEANS` is not `E(y_t)`. The carried latent mean is not `E(y_t)`. The first-occasion time-independent predictor shift is `t0_b z` (Driver et al., 2017, Table 3 `T0TIPREDEFFECT`; Eq. 3 first summand). Form `t0_b z` first, then `e^{a Δt} t0_b z`. Form `μ_t` first, then add that carry. A zero drift is `t0_b z`. Underflow of `e^{a Δt}` to `+0` is a vanishing carry of the first-occasion shift and is kept. `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`. `e^{A Δt} t0_b z` is not `t0_b z`. `T0TIPREDEFFECT` is the coefficient, not the shift. The observed mean of that first-occasion carry is `τ + λ(μ_t + e^{a Δt} t0_b z)` (Driver et al., 2017, Eq. 5 of the Table 3 / Eq. 3 first-summand composition). The evolved map `τ + λ μ_t` is not that observed mean. The process-increment map `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The impulse-carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t0`. The evolved-plus-carry latent mean is not `E(y_t)`. The first-occasion time-dependent predictor shift is `t0_m x0` (Driver et al., 2017, Table 3 `T0TDPREDEFFECT`; Eq. 3 first summand; JSS PDF re-opened 2026-08-20T19:10Z). Form `t0_m x0` first, then `e^{a Δt} t0_m x0`. Form `μ_t` first, then add that carry. A zero drift is `t0_m x0`. Underflow of `e^{a Δt}` to `+0` is a vanishing carry of the first-occasion shift and is kept. `t0_m x0` is not `M x`, not `e^{A(t−u)} M x` for `t0 < u < t`, not `t0_b z`, not `A^{-1}[e^{A Δt} − I] B z`, and not `CINT`. `e^{A Δt} t0_m x0` is not `t0_m x0`. `T0TDPREDEFFECT` is the coefficient, not the shift. An impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`. The observed mean of that first-occasion TD carry is `τ + λ(μ_t + e^{a Δt} t0_m x0)` (Driver et al., 2017, Eq. 5 of the Table 3 / Eq. 3 first-summand TD composition; JSS PDF re-opened 2026-08-20T19:07Z). The evolved map `τ + λ μ_t` is not that observed mean. The process-increment map `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The impulse-carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t0`. The first-occasion TI map `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean. The evolved-plus-carry latent mean is not `E(y_t)`. The lasting level-change `CINT` is `κ = −a m x` (Driver et al., 2017, §7.2, pp. 20–21; JSS PDF re-opened 2026-08-20T19:45Z). Form `m x` first, then multiply by `−a`. Stable `a < 0` is required so `−κ / a = m x` is an equilibrium offset. `a ≥ 0` cannot hold a new process mean. `−a m x` is not the dissipating Dirac `m x`, not a free `CINT`, and not `A^{-1}[e^{A Δt} − I] B z`. The extra near-zero-drift latent process also named in §7.2 is a different specification and is not this `CINT` setting. Equation 3 maps that intercept as `(1 − e^{a Δt}) m x` (JSS PDF re-opened 2026-08-20T19:50Z). Form the level-change `CINT` first, then the discrete intercept map. Underflow of `e^{a Δt}` to `+0` keeps `m x`. `(1 − e^{a Δt}) m x` is not `m x`, not `κ`, and not `A^{-1}[e^{A Δt} − I] B z`. The printed §7.2 lasting level change is an extra near-zero-drift latent process (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-20T23:10Z). `T0MEANS`, `CINT`, `T0VAR`, `DIFFUSION`, and `TRAITVAR` of that process are fixed to 0; `TDPREDEFFECT` on it is fixed to 1; its `DRIFT` diagonal is very close to 0 (printed example `−0.000001`; precisely 0 causes computational problems); the original process is driven by the `DRIFT` coupling `a_{ηξ}`. After a unit identification impulse the scalar contribution is `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (`ε = a` is `a_{ηξ} x Δt e^{a Δt}`). Form `a_{ηξ} x` first. A zero coupling or zero predictor is exactly zero. `ε ≥ 0` fails closed. That contribution is not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`. The observed mean of that extra-process contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a))` (Driver et al., 2017, Eq. 5 of that §7.2 contribution; JSS PDF re-opened 2026-08-21T06:12Z). The extra process has `LAMBDA` 0 and is not an observed indicator. Original indicators load on the original process after the `DRIFT` coupling. The evolved map `τ + λ μ_t` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The contribution is not `E(y_t)`. The evolved-plus-contribution latent mean is not `E(y_t)`. `T0TDPREDEFFECT` on the extra process begins at `t = 0` and uses `Δt = t − t0` for both the original-process evolution and the extra drive. `TDPREDEFFECT` after `t0` uses `t − u` with `t0 < u < t` for the extra drive while `μ_t` still uses `Δt`. The observed mean of that after-t0 extra-process contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a))` (Driver et al., 2017, Eq. 5 of that §7.2 after-t0 contribution; JSS PDF re-opened 2026-08-21T06:32Z). The first-occasion extra-process observed mean is not that observed mean when `u ≠ t0`. The impulse-carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is a Dirac on the original process and is not that `DRIFT` drive. An impulse at `u = t0` or `u = t` is not interior. The asymptotic time-independent predictor effect is `-B z / a` (Driver et al., 2017, §7.2, pp. 20–21; JSS PDF opened 2026-08-21T13:08Z). Form `B z` first, then divide by `-a`. Stable `a < 0` is required. `a ≥ 0` cannot hold a finite process-mean change. `-B z / a` is not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`. The asymptotic time-independent predictor variance is `(B / a)² v` (Driver et al., 2017, §7.2, pp. 20–21 `addedTIPREDVAR`). Form the unit asymptotic effect first, then square, then multiply by `v`. `(B / a)² v` is not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`. The asymptotic continuous intercept is `-κ / a` (Driver et al., 2017, Table 2, p. 12 `asymCINT`; Eq. 3 as `Δt → ∞`; JSS PDF opened 2026-08-21T16:13Z). Form `κ` first, then divide by `-a`. Stable `a < 0` is required. `-κ / a` is not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`. The p. 16 stationary `T0MEANS` constraint is `-κ / a + −B z / a`. Form the intercept contribution first, then include the TI extra effect, then add. That constrained first-occasion mean is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean. Equation 5 of that constrained mean is `τ + λ(−κ / a + −B z / a)` (Driver et al., 2017, §4.3, pp. 9–10; Eq. 5, p. 5; JSS PDF re-opened 2026-08-21T20:07Z). Form the stationary latent mean first, then `τ + λ` of that mean. `τ + λ μ_0` for free `T0MEANS` is not that composition. `τ + λ(−κ / a)` is not that composition when `B z ≠ 0`. `τ + λ μ_t` is not that composition. `MANIFESTMEANS` is not `E(y_0)`. The constrained latent mean is not `E(y_0)`. The p. 16 constrained first-occasion variance `trait + −q / (2 a) + (B / a)² v` is not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`). The lagged covariance of that constrained process is `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T19:13Z). Trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`. Contemporaneous `T0VAR` is not that lagged map. Decaying the constrained total as if it were all state is not that lagged map. Equation 5 of that lagged covariance is `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ`. `Θ` does not enter. Contemporaneous `Var(y_0)` is not that lagged observed covariance. The lagged latent covariance is not that observed covariance. The later-occasion variance of that constrained process is `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T23:12Z). Trait and `addedTIPREDVAR` do not enter `Q_Δt`. Under stationarity that composition equals contemporaneous `T0VAR`. Evolving the constrained total as if it were all state is not that later map. The lagged covariance omits `Q_Δt` and is not that later map. `Q_Δt` is not that later map. Equation 5 of that later-occasion variance is `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ`. The lagged observed covariance omits `Q_Δt` and `θ`. `MANIFESTVAR` is not `Var(y_t)`. The later-occasion latent variance is not `Var(y_t)`. The later-occasion variance of §4.3 predetermined `T0VAR` is `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T05:12Z). Trait and `addedTIPREDVAR` do not enter `Q_Δt`. Free `T0VAR` `p_0` is not that later map. Setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map. Stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free. Evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map. As `Δt → ∞` with stable `a < 0` the composition approaches contemporaneous stationary `T0VAR`. As `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`. Nonzero diffusion with `a ≥ 0` is a growing process and is kept. Equation 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ`. `MANIFESTVAR` is not `Var(y_t)`. The predetermined later-occasion latent variance is not `Var(y_t)`. Stationary later observed variance is not that observed variance when `p_0` is free. The lagged covariance of §4.3 predetermined `T0VAR` is `trait + e^{a Δt} p_0 + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T09:04Z). Trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`. Free `T0VAR` `p_0` is not that lagged map. Setting `p_0 = −q / (2 a)` recovers the stationary lagged map. Stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free. Evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map. Later-occasion variance includes `Q_Δt` and is not that lagged map. As `Δt → ∞` with stable `a < 0` the state term vanishes. As `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`. Equation 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ`. `MANIFESTVAR` does not enter. The predetermined lagged latent covariance is not that observed covariance. Predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance. Stationary lagged observed covariance is not that observed covariance when `p_0` is free. The predetermined first-occasion variance of §4.3 predetermined `T0VAR` is `trait + p_0 + (B / a)² v`. Free `p_0` is not that map. Stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free. Lagged covariance decays the state and is not that map. Later-occasion variance includes `Q_Δt` and is not that map. Equation 5 of that predetermined first-occasion variance is `λ²(trait + p_0 + (B / a)² v) + θ + ψ`. `MANIFESTVAR` is not that first-occasion observed variance. The predetermined first-occasion latent variance is not that observed variance. Stationary first-occasion observed variance is not that observed variance when `p_0` is free. Predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance. Later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z). First-occasion lagged omits `e^{a s} Q_u`. Later-occasion variance does not lag. Stationary lagged uses `−q / (2 a)`. Decaying the later total is not that map. Equation 5 of that later-start lagged covariance is `λ²` of it plus `ψ`. Independent `ε_t` does not enter. First-occasion lagged observed omits `e^{a s} Q_u`. Predetermined later observed variance includes `Q_u` and `θ` and is not that later-start lagged observed covariance. Later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z). Later-occasion variance at `u` omits `Q_s`. Later-start lagged covariance omits `Q_s`. Stationary later uses `−q / (2 a)`. Evolving the later total as if it were all state is not that map. Ignoring `startoffset` omits `e^{2 a s} Q_u`. Equation 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`. `MANIFESTVAR` is not that observed variance. Page 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; footnote 4; §7.1; JSS PDF re-opened 2026-08-23T11:40Z). Unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`. The §7.1 trait-plus-state autocorrelation `(trait + e^{a Δt} p + added) / (trait + p + added)` uses `TRAITVAR` and is not `discreteDRIFTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:06Z). Unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`. The continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`. `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`. `TRAITVAR` is not the standardisation variance. Page 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z). Unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`. The discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`. `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`. `TRAITVAR` is not the standardisation variance. Page 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z). Unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`. The discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`. `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z). Unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`. The finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`. `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z). Unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`. The asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`. The finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`. `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Page 16 / Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z). The affected variance is free first-occasion `T0VAR`, not `asymDIFFUSION`. Unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`. `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`. `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`. `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. 2017-era `addedT0TIPREDVAR` is `t0_b² v` after a first-occasion time-independent predictor (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z). Form `t0_b` first, then square, then multiply by `v`. A zero coefficient or zero predictor variance is exactly zero. Free `T0TIPREDEFFECT` does not require `a < 0`. `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map. `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance. Free `T0VAR` is not this extra TI variance. `TRAITVAR` is not this extra TI variance. Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z). Form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`. A zero loading or zero extra is exactly zero. `t0_b² v` is the latent extra, not the observed extra. `λ² p_0 + θ` is first-occasion observed variance, not this extra. `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra. `MANIFESTVAR` `θ` is not this extra. Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v` (Driver et al., 2017, Eq. 5, p. 5; Table 2, p. 12; §7.2, pp. 20–21; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:23Z). Form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`. A zero loading or zero extra is exactly zero. Lasting asymptotic extra requires `a < 0`. `(B / a)² v` is the latent extra, not the observed extra. `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra. `λ² p + θ` is stationary observed variance, not this extra. `MANIFESTVAR` `θ` is not this extra. Page 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance. Unstandardised `M` is not `TDPREDEFFECTstd`. `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`. intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`. `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`. Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance. Unstandardised `t0_m` is not `T0TDPREDEFFECTstd`. `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`. `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`. `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`. Free `T0VAR` does not require `a < 0`. Page 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; the default ridge is 0). Unstandardised `T0VAR` is not `T0VARstd`. `T0TDPREDEFFECTstd` is not `T0VARstd`. `addedT0TIPREDVAR` is not `T0VARstd`. Page 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend). Unstandardised `TRAITVAR` is not `TRAITVARstd`. `T0VARstd` is not `TRAITVARstd` even when both equal 1. `addedT0TIPREDVAR` is not `TRAITVARstd`. Page 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0). Unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`. `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1. `MANIFESTVAR` is not `MANIFESTTRAITVARstd`. Page 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug). Unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`. `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1. Equation 5 `Var(y)` is not `MANIFESTVARstd`. Page 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`). Unstandardised `TIPREDVAR` is not `TIPREDVARstd`. `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1. Section 7.2 `addedTIPREDVAR` is not `TIPREDVARstd`. Page 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`). Unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`. `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1. `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`. Page 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`. Unstandardised `discreteCINT` is not `discreteCINTstd`. `κ / √p` is not `discreteCINTstd`. `(-κ / a) / √p` is not `discreteCINTstd`. `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`. Unstandardised `asymCINT` is not `asymCINTstd`. `κ / √p` is not `asymCINTstd`. `discreteCINTstd` is not `asymCINTstd`. `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`. Unstandardised `T0MEANS` is not `T0MEANSstd`. `T0VARstd` is not `T0MEANSstd`. `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`. Page 16 `MANIFESTMEANSstd` is `τ / √θ` after strictly positive `MANIFESTVAR`. Unstandardised `MANIFESTMEANS` is not `MANIFESTMEANSstd`. `MANIFESTVARstd` is not `MANIFESTMEANSstd`. `τ / √(λ² Var(η) + θ)` is not `MANIFESTMEANSstd`. Page 16 `CINTstd` is `κ / √p` after strictly positive `asymDIFFUSION`. Unstandardised `CINT` is not `CINTstd`. `asymCINTstd` is not `CINTstd`. `discreteCINTstd` is not `CINTstd`. `κ / √(trait + p + added)` is not `CINTstd`. Evolving from that stationary start with `CINT` and `TIPREDEFFECT` stays at the stationary mean. Equation 1 is the latent SDE, not the measurement model. Form `(λ p) λ` then add `θ`, then add `ψ`. `MANIFESTVAR` is `Θ`, not `Var(y)`. `MANIFESTTRAITVAR` is `Ψ_τ`, not `Θ`. `TRAITVAR` is latent and scaled by `λ²`. `Var(η)` is not `Var(y)`. - Separate cluster means before within-unit lag. CWC plus an event-time lag is not DSEM. Subtracting the person-specific mean from a raw autoregressive series does not isolate the lagged within-person effect (Curran & Bauer, 2011, pp. 583–619; PMC3059070 XML opened 2026-09-02; Eq. 36). `longitudinal_core` owns CWC-then-pairwise-mean residual log-rate on typed `EventTimeInterval`; for an already-centered `(1, 0.5)` pair it recovers `ln(0.5) / Δt`, equal to `ln(0.5)` only when `Δt = 1`. CWC log-rate is not raw-process drift. - Do not treat the CWC cluster-mean coefficient as the between-cluster effect. It is the contextual effect `between − within` (Enders & Tofighi, 2007, Table 2, pp. 124–127). - Never use future-available evidence in historical model fits. From c345ee7b8bdf642430669b7b0e1d7fc6873a84af Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 03:04:55 +0900 Subject: [PATCH 087/296] test(longitudinal): reject collapsed nonzero lag correlation --- crates/longitudinal_core/src/association.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/crates/longitudinal_core/src/association.rs b/crates/longitudinal_core/src/association.rs index 8e288a7f3..b33a7d79a 100644 --- a/crates/longitudinal_core/src/association.rs +++ b/crates/longitudinal_core/src/association.rs @@ -237,6 +237,20 @@ mod tests { ); } + #[test] + fn nonzero_covariance_cannot_collapse_to_exact_zero_correlation() { + let minimum_subnormal = f64::from_bits(1); + assert_eq!( + recover_event_time_lagged_correlation( + minimum_subnormal, + f64::MAX, + f64::MAX, + event_time(1.0), + ), + Err(LongitudinalError::InvalidTemporalAssociationInput) + ); + } + #[test] fn gross_subnormal_bound_violation_is_classified_before_division() { let minimum_subnormal = f64::from_bits(1); From 5785e07a352801c193d92dde03863d0697a2853a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 03:06:33 +0900 Subject: [PATCH 088/296] fix(longitudinal): fail closed on unrepresentable nonzero correlation --- crates/longitudinal_core/src/association.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/longitudinal_core/src/association.rs b/crates/longitudinal_core/src/association.rs index b33a7d79a..caa01e3df 100644 --- a/crates/longitudinal_core/src/association.rs +++ b/crates/longitudinal_core/src/association.rs @@ -96,7 +96,8 @@ fn covariance_within_binary_bound( /// # Errors /// /// Returns [`LongitudinalError::InvalidTemporalAssociationInput`] for -/// non-finite covariance or marginal inputs, +/// non-finite covariance or marginal inputs, or when a nonzero exact +/// correlation is too small to be represented as binary64, /// [`LongitudinalError::NonPositiveMarginalVariance`] when either marginal /// variance is not strictly positive, and /// [`LongitudinalError::CovarianceBoundViolation`] when the supplied covariance @@ -137,6 +138,13 @@ pub(crate) fn recover_event_time_lagged_correlation( }; let correlation = (lagged_covariance / first_scale) / second_scale; + // A nonzero covariance cannot scientifically become an exact zero + // correlation merely because binary64 cannot represent the standardized + // magnitude. Fail closed rather than report a false no-association result. + if lagged_covariance != 0.0 && correlation == 0.0 { + return Err(LongitudinalError::InvalidTemporalAssociationInput); + } + // Finite positive marginals plus the exact covariance-bound gate guarantee // that both divisions stay finite. Clamping only absorbs final // square-root/division rounding at a valid ±1 boundary; it cannot admit an From ea550a3a2f3419061736eaa12a74909acf5f90a9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 03:07:07 +0900 Subject: [PATCH 089/296] test(longitudinal): expose lag correlation underflow refusal --- .../src/temporal_association.rs | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/crates/longitudinal_core/src/temporal_association.rs b/crates/longitudinal_core/src/temporal_association.rs index c5ebdb6c7..041247e6d 100644 --- a/crates/longitudinal_core/src/temporal_association.rs +++ b/crates/longitudinal_core/src/temporal_association.rs @@ -12,10 +12,11 @@ use crate::{EventTimeInterval, LongitudinalError, association}; /// # Errors /// /// Returns [`LongitudinalError::InvalidTemporalAssociationInput`] for invalid -/// covariance or marginal inputs, [`LongitudinalError::NonPositiveMarginalVariance`] -/// when either marginal variance is non-positive, and -/// [`LongitudinalError::CovarianceBoundViolation`] when the covariance exceeds -/// the exact binary64 Cauchy–Schwarz bound. +/// covariance or marginal inputs, or when a nonzero exact correlation is too +/// small to be represented as binary64, +/// [`LongitudinalError::NonPositiveMarginalVariance`] when either marginal +/// variance is non-positive, and [`LongitudinalError::CovarianceBoundViolation`] +/// when the covariance exceeds the exact binary64 Cauchy–Schwarz bound. pub fn recover_event_time_lagged_correlation( lagged_covariance: f64, earlier_total_variance: f64, @@ -44,6 +45,20 @@ mod tests { ); } + #[test] + fn public_boundary_does_not_report_underflowed_nonzero_correlation_as_zero() { + let interval = EventTimeInterval::new(1.0).expect("valid event time"); + assert_eq!( + recover_event_time_lagged_correlation( + f64::from_bits(1), + f64::MAX, + f64::MAX, + interval, + ), + Err(LongitudinalError::InvalidTemporalAssociationInput) + ); + } + #[test] fn wrong_clock_shaped_numeric_values_fail_at_value_object_admission() { assert_eq!( From 5697221872650f0327cbdf6db0a5020a07fe39e4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 03:58:57 +0900 Subject: [PATCH 090/296] test(longitudinal): require one public irregular-rate facade --- ...rregular_rate_shadow_authority_contract.rs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/crates/longitudinal_core/tests/irregular_rate_shadow_authority_contract.rs b/crates/longitudinal_core/tests/irregular_rate_shadow_authority_contract.rs index 8ae91fd12..569d12006 100644 --- a/crates/longitudinal_core/tests/irregular_rate_shadow_authority_contract.rs +++ b/crates/longitudinal_core/tests/irregular_rate_shadow_authority_contract.rs @@ -21,6 +21,28 @@ fn shadowed_centered_rate_does_not_reintroduce_direct_ratio_rejection() { ); } +#[test] +fn canonical_recovery_functions_are_crate_private_behind_the_public_facade() { + let canonical = include_str!("../src/irregular_residual.rs"); + + assert!( + canonical.contains("pub(crate) fn recover_centered_irregular_residual_log_rate("), + "the canonical centered-rate implementation must be crate-private so stable_irregular_rate is the only crate-public facade" + ); + assert!( + canonical.contains("pub(crate) fn recover_within_unit_irregular_residual_log_rate("), + "the canonical CWC irregular-rate implementation must be crate-private so stable_irregular_rate is the only crate-public facade" + ); + assert!( + !canonical.contains("pub fn recover_centered_irregular_residual_log_rate("), + "the canonical centered-rate implementation still exposes a second public API name" + ); + assert!( + !canonical.contains("pub fn recover_within_unit_irregular_residual_log_rate("), + "the canonical CWC irregular-rate implementation still exposes a second public API name" + ); +} + #[test] fn public_irregular_rate_facade_has_no_second_numerical_implementation() { let facade = include_str!("../src/stable_irregular_rate.rs"); From 4648638608436fb6c04315d96f59a6404e2e790b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 04:00:02 +0900 Subject: [PATCH 091/296] test(longitudinal): pin facade as canonical re-export --- ...rregular_rate_shadow_authority_contract.rs | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/crates/longitudinal_core/tests/irregular_rate_shadow_authority_contract.rs b/crates/longitudinal_core/tests/irregular_rate_shadow_authority_contract.rs index 569d12006..aef0f09b6 100644 --- a/crates/longitudinal_core/tests/irregular_rate_shadow_authority_contract.rs +++ b/crates/longitudinal_core/tests/irregular_rate_shadow_authority_contract.rs @@ -3,9 +3,9 @@ //! Architecture fitness for one Longitudinal Modeling irregular-rate estimand. //! //! The crate-public path and its internal temporal composition must share one -//! numerical authority. A compatibility/public facade may forward the named -//! operations, but it must not carry a second mean/log-rate implementation -//! whose floating-point edge semantics can drift independently. +//! numerical authority. A compatibility/public facade may re-export the named +//! operations, but it must not declare wrapper functions or carry a second +//! mean/log-rate implementation whose floating-point edge semantics can drift. #[test] fn shadowed_centered_rate_does_not_reintroduce_direct_ratio_rejection() { @@ -22,24 +22,29 @@ fn shadowed_centered_rate_does_not_reintroduce_direct_ratio_rejection() { } #[test] -fn canonical_recovery_functions_are_crate_private_behind_the_public_facade() { +fn public_facade_reexports_the_canonical_recovery_functions_instead_of_wrapping_them() { let canonical = include_str!("../src/irregular_residual.rs"); + let facade = include_str!("../src/stable_irregular_rate.rs"); assert!( - canonical.contains("pub(crate) fn recover_centered_irregular_residual_log_rate("), - "the canonical centered-rate implementation must be crate-private so stable_irregular_rate is the only crate-public facade" + canonical.contains("pub fn recover_centered_irregular_residual_log_rate("), + "the canonical centered-rate function must remain re-exportable from the private implementation module" + ); + assert!( + canonical.contains("pub fn recover_within_unit_irregular_residual_log_rate("), + "the canonical CWC irregular-rate function must remain re-exportable from the private implementation module" ); assert!( - canonical.contains("pub(crate) fn recover_within_unit_irregular_residual_log_rate("), - "the canonical CWC irregular-rate implementation must be crate-private so stable_irregular_rate is the only crate-public facade" + facade.contains("pub use crate::irregular_residual::{"), + "the public facade must re-export the canonical function identities rather than declare wrapper functions" ); assert!( - !canonical.contains("pub fn recover_centered_irregular_residual_log_rate("), - "the canonical centered-rate implementation still exposes a second public API name" + !facade.contains("pub fn recover_centered_irregular_residual_log_rate("), + "stable_irregular_rate still declares a second centered-rate wrapper function" ); assert!( - !canonical.contains("pub fn recover_within_unit_irregular_residual_log_rate("), - "the canonical CWC irregular-rate implementation still exposes a second public API name" + !facade.contains("pub fn recover_within_unit_irregular_residual_log_rate("), + "stable_irregular_rate still declares a second CWC irregular-rate wrapper function" ); } @@ -52,11 +57,11 @@ fn public_irregular_rate_facade_has_no_second_numerical_implementation() { "stable_irregular_rate still owns a second floating-point mean implementation" ); assert!( - facade.contains("irregular_residual::recover_centered_irregular_residual_log_rate"), - "the public facade must delegate the centered-rate estimand to the canonical Longitudinal Modeling implementation" + facade.contains("recover_centered_irregular_residual_log_rate"), + "the public facade must expose the centered-rate estimand from the canonical Longitudinal Modeling implementation" ); assert!( - facade.contains("irregular_residual::recover_within_unit_irregular_residual_log_rate"), - "the public facade must delegate the CWC irregular-rate estimand to the canonical Longitudinal Modeling implementation" + facade.contains("recover_within_unit_irregular_residual_log_rate"), + "the public facade must expose the CWC irregular-rate estimand from the canonical Longitudinal Modeling implementation" ); } From 7f0bea0841fb89a6ce9bdd5b9f10c0e4612f4270 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 04:00:10 +0900 Subject: [PATCH 092/296] refactor(longitudinal): re-export canonical irregular-rate functions --- .../src/stable_irregular_rate.rs | 45 +++---------------- 1 file changed, 6 insertions(+), 39 deletions(-) diff --git a/crates/longitudinal_core/src/stable_irregular_rate.rs b/crates/longitudinal_core/src/stable_irregular_rate.rs index 59334d24c..245c69b50 100644 --- a/crates/longitudinal_core/src/stable_irregular_rate.rs +++ b/crates/longitudinal_core/src/stable_irregular_rate.rs @@ -2,44 +2,11 @@ //! //! Numerical ownership lives in [`crate::irregular_residual`], alongside event //! ordering, CWC admission, and the shared same-sign log-rate primitive. This -//! module preserves the crate-public API introduced during the overflow repair -//! without maintaining a second floating-point implementation that can drift +//! module preserves the crate-public API while re-exporting the canonical +//! function identities instead of maintaining wrapper functions that can drift //! from the Longitudinal Modeling estimand. -use crate::LongitudinalError; -use crate::irregular_residual::{self, EventTimedObservation, LaggedWithinResidual}; - -/// Pairwise-mean exact log-rate after CWC on irregular event intervals. -/// -/// This is a public facade over the canonical Longitudinal Modeling -/// implementation. Temporal admission, same-sign log-domain fallback, and -/// overflow-safe count weighting are therefore identical to the internal -/// composition used by the bounded context. -/// -/// # Errors -/// -/// Propagates the canonical temporal-admission and numerical errors from -/// [`irregular_residual::recover_within_unit_irregular_residual_log_rate`]. -pub fn recover_within_unit_irregular_residual_log_rate( - rows: &[EventTimedObservation], -) -> Result { - irregular_residual::recover_within_unit_irregular_residual_log_rate(rows) -} - -/// Mean exact scalar log-rate on already-centered residuals. -/// -/// This facade delegates to the single canonical implementation. Finite, -/// nonzero same-sign residuals use the direct positive ratio logarithm when it -/// is representable and the equivalent log-domain difference otherwise; a -/// representable final mean is not rejected only because an intermediate -/// ratio or retained-count product overflows or underflows. -/// -/// # Errors -/// -/// Propagates payload, sign/zero, event-interval, and representability errors -/// from [`irregular_residual::recover_centered_irregular_residual_log_rate`]. -pub fn recover_centered_irregular_residual_log_rate( - pairs: &[LaggedWithinResidual], -) -> Result { - irregular_residual::recover_centered_irregular_residual_log_rate(pairs) -} +pub use crate::irregular_residual::{ + recover_centered_irregular_residual_log_rate, + recover_within_unit_irregular_residual_log_rate, +}; From fe5eb7457f80e7724412102800ceaf5b9f70ec50 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 04:10:11 +0900 Subject: [PATCH 093/296] test(ddd): pin Longitudinal Modeling architecture ownership --- ...est_longitudinal_architecture_ownership.py | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 tests/quality/test_longitudinal_architecture_ownership.py diff --git a/tests/quality/test_longitudinal_architecture_ownership.py b/tests/quality/test_longitudinal_architecture_ownership.py new file mode 100644 index 000000000..940a5da39 --- /dev/null +++ b/tests/quality/test_longitudinal_architecture_ownership.py @@ -0,0 +1,62 @@ +"""Architecture fitness for Longitudinal Modeling ownership boundaries.""" + +from pathlib import Path + + +ARCHITECTURE = (Path(__file__).parents[2] / "ARCHITECTURE.md").read_text(encoding="utf-8") + + +def _section(start: str, end: str) -> str: + """Return one architecture section bounded by exact headings/markers.""" + return ARCHITECTURE.split(start, 1)[1].split(end, 1)[0] + + +def test_conceptual_boundaries_do_not_assign_temporal_composition_to_psychometric_core() -> None: + """Longitudinal/event-time composition belongs to its own bounded context.""" + bounded = _section("## Bounded services and Rust crates", "Every boundary must") + psychometric_row = next( + line for line in bounded.splitlines() if line.startswith("| `psychometric_core` |") + ) + + for leaked_authority in ("longitudinal invariance", "DSEM", "continuous-time paths"): + assert leaked_authority not in psychometric_row, ( + "psychometric_core still claims Longitudinal Modeling authority: " + f"{leaked_authority}" + ) + + assert "| `longitudinal_modeling` |" in bounded + longitudinal_row = next( + line for line in bounded.splitlines() if line.startswith("| `longitudinal_modeling` |") + ) + for owned_semantic in ("event-time", "longitudinal", "state"): + assert owned_semantic in longitudinal_row + + +def test_implementation_topology_has_one_psychometric_and_one_longitudinal_owner_row() -> None: + """Implementation topology cannot duplicate or conflate responsibility rows.""" + topology = _section("## Implemented foundation topology", "Foundation crates expose") + psychometric_rows = [ + line for line in topology.splitlines() if line.startswith("| `psychometric_core` |") + ] + longitudinal_rows = [ + line for line in topology.splitlines() if line.startswith("| `longitudinal_core` |") + ] + + assert len(psychometric_rows) == 1, "psychometric_core has duplicate responsibility rows" + assert len(longitudinal_rows) == 1, "longitudinal_core has duplicate responsibility rows" + + psychometric_row = psychometric_rows[0] + for temporal_implementation in ( + "event-time log-rate", + "discrete process noise", + "stationary within-subject variance", + "TDPREDEFFECT", + ): + assert temporal_implementation not in psychometric_row, ( + "temporal composition is still assigned to psychometric_core: " + f"{temporal_implementation}" + ) + + longitudinal_row = longitudinal_rows[0] + assert "temporal composition" in longitudinal_row + assert "fast-mlsirm" in longitudinal_row From 7fadc757987145cbcc39475b2bf3193e4a4fed59 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 04:11:33 +0900 Subject: [PATCH 094/296] docs(ddd): separate Longitudinal Modeling ownership --- ARCHITECTURE.md | 135 +++++++++++++++++++----------------------------- 1 file changed, 52 insertions(+), 83 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index cc37d1923..430d5a0a5 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -13,7 +13,7 @@ flowchart LR C --> F[Typed document-event-entity graph] E --> G[Posterior topic coordinates] F --> G - G --> H[Longitudinal ESEM and DSEM] + G --> H[Longitudinal Modeling] G --> I[Topic and event networks] H --> J[Evidence-grounded interpretation] I --> J @@ -28,31 +28,37 @@ flowchart LR | `temporal_core` | instants, intervals, uncertain dates, partial orders, bitemporal availability and leakage gates | | `event_ontology` | event mentions, event instances, roles, subevents, products, factors, places, and evidence links | | `relation_graph` | typed document, segment, event, entity, revision, translation, evidence, and transition edges | -| `membership_model` | time-varying cross-classified and multiple-membership assignments | +| `membership_model` | time-varying multilevel, cross-classified, and multiple-membership assignments with auditable event-time validity | | `semantic_preprocessor` | Unicode, segmentation, morphology, dependency phrases, LLM span contracts, validation | | `concept_dictionary` | versioned multilingual concept alignment and unknown-concept review | | `topic_measurement` | shared-latent temporal/relational topic estimation and uncertainty | -| `compute_backend` | CPU `f64`, fixed-pool multithreading, CUDA/WGPU, sparse streaming, VRAM budgeting | +| `compute_backend` | CPU `f64`, fixed-pool multithreading, accelerator parity, sparse streaming, VRAM budgeting | | `model_selection` | fitted candidate-K scoring from the CPU reference, predictive fit, coherence, exclusivity, stability, alignment, fairness, blinded LLM review | -| `psychometric_core` | posterior-plausible-value ESEM, longitudinal invariance, DSEM, continuous-time paths | +| `psychometric_core` | posterior-aware non-temporal measurement inputs and structural measurement fitting; reusable generalized/static psychometric arithmetic is consumed from released fast-mlsirm contracts rather than re-owned here | +| `longitudinal_modeling` | event-time longitudinal state/trajectory composition, irregular-gap transitions, longitudinal invariance and DSEM/continuous-time mappings, time-varying membership composition, alignment, and recovery evidence | | `event_intelligence` | TDT segmentation/link/detection/first-story/tracking and CHRONOS schema reasoning | | `network_analysis` | log-ratio topic correlation, conditional networks, uncertainty, Leiden consensus clusters | -| `interpretation_gateway` | evidence-bounded LLM interpretation, independent verification, routing and ablations | +| `interpretation_gateway` | evidence-bounded LLM interpretation and independent verification; never numerical/scientific authority | | `artifact_service` | model registry, manifests, JSON-LD, GraphML, Arrow/Parquet, tables, SVG/PDF exports | -| `visual_analytics` | bitemporal lens, event graph, topic river, drift, ESEM/DSEM builder, invariance and leakage audit | +| `visual_analytics` | bitemporal lens, event graph, topic river, drift, longitudinal builder, invariance and leakage audit | -Every boundary must be independently usable and expose versioned contracts for integration with organization repositories, `naruon`, and `contextual-orchestrator`. +Every boundary must be independently usable and expose versioned contracts for integration with organization repositories. Cross-boundary integration is through released/versioned contracts and explicit ACLs; direct cross-service application-table access and mutable sibling-head dependencies are prohibited. -The `analysis_engine` vertical slice is intentionally separate from `tepp_api`: -the API owns wire contracts while the engine owns deterministic execution. It -does not replace the future topic or psychometric estimators and does not read -another service's application tables. +The `analysis_engine` vertical slice is intentionally separate from `tepp_api`: the API owns wire contracts while the engine owns deterministic execution. It does not replace topic, measurement, or Longitudinal Modeling estimators and does not read another service's application tables. + +### Longitudinal Modeling ownership + +Temporal composition is a TEPP domain responsibility. `longitudinal_modeling` owns the meaning and composition of event-time state/trajectory changes, irregular gaps, time-varying hierarchy/cross-classification/multiple membership, temporal alignment, rolling-origin leakage control, and longitudinal recovery evidence. Its Rust implementation path is `longitudinal_core`. + +Reusable static/generalized-mixed/dependence psychometric arithmetic is not duplicated in TEPP. fast-mlsirm is the canonical owner for that arithmetic and TEPP consumes only immutable released/versioned Published Language through an ACL. A TEPP temporal adapter may combine those static primitives with typed event-time state, membership, and evidence semantics, but it cannot copy the upstream implementation or make an open upstream PR head authoritative. + +`psychometric_core` retains legacy compatibility surfaces for existing measurement code while they are migrated through explicit adapters. Those legacy APIs do not establish new ownership of temporal composition. New temporal/state-transition behavior belongs in `longitudinal_core`; new reusable static psychometric primitives belong in fast-mlsirm. + +Detailed equations, literature claims, recovery fixtures, and exact implementation/test evidence belong in TRACEABILITY, doctoring/research documents, ADRs, and source tests. The responsibility tables below intentionally identify ownership rather than duplicate an equation catalogue. ## Implemented foundation topology -Task 1 materializes the first storage-independent workspace boundaries. The -crate names are stable implementation identifiers, while the broader service -boundaries above remain the target modular MSA architecture. +The crate names are implementation identifiers. A crate can be an adapter or an incremental slice without becoming a new bounded context or architectural authority. | Rust crate | Initial responsibility | |---|---| @@ -60,9 +66,9 @@ boundaries above remain the target modular MSA architecture. | `semantic_core` | span-grounded semantic units; language is not identity | | `location_membership` | location is not entity identity and not a language channel | | `temporal_core` | typed clocks, intervals, and temporal reasoning | -| `event_core` | event instances, span-grounded `EventMention` (the only constructible mention type), roles, provenance, and CHRONOS occurrence-prediction calibration | +| `event_core` | event instances, span-grounded `EventMention`, roles, provenance, and CHRONOS occurrence-prediction calibration | | `relation_graph` | typed relations and forward-transition validation | -| `membership_core` | time-varying cross-classified multiple membership, Kish ESS, nested ICC with non-nested refusal | +| `membership_core` | time-varying cross-classified/multiple membership, Kish ESS, nested ICC with non-nested refusal | | `role_contradiction` | customer and competitor cannot occupy the same group | | `relation_absence` | unobserved relation pairs are not evidence of no relationship | | `persistence_postgres` | PostgreSQL repositories and migrations | @@ -91,18 +97,18 @@ boundaries above remain the target modular MSA architecture. | `assertion_clock` | assertion time cannot be replaced by event, system, document, or available time | | `cutoff_clock` | knowledge cutoff cannot be replaced by event, system, or availability time | | `available_clock` | availability time cannot be replaced by event or system time | -| `document_clocks` | document rows must carry assertion time and document time | +| `document_clocks` | document rows carry assertion time and document time | | `revision_order` | later document revisions must have later system time | -| `encrypted_mapping` | purpose-bound in-memory AES-256-GCM identity mappings; no plaintext persistence or KMS integration | +| `encrypted_mapping` | purpose-bound in-memory AES-256-GCM identity mappings; no plaintext persistence or KMS claim | | `citation_edge` | citation, revision, translation, and retrospective edges are not state transitions | -| `psychometric_fit` | CPU `f64` ESEM loading recovery and event-time DSEM lag gates | -| `subevent_containment` | subevent event-time intervals must stay inside the parent | -| `prediction_contradiction` | Allen promotion gate: `before`/`after` stay contradictory; `meets`/`met_by` stay unsupported; coverage is required before unmatched predicted mass may be authorized for promotion | +| `psychometric_fit` | CPU `f64` ESEM loading recovery and event-time admission at the measurement/Longitudinal ACL | +| `subevent_containment` | subevent event-time intervals stay inside the parent | +| `prediction_contradiction` | Allen promotion gate: contradictory and unsupported relations fail closed unless required evidence exists | | `provider_receipt` | provider-disclosure field-code receipts; source text and identity are not disclosable | -| `operational_log` | operational logs; `try_record` is the only recording API; source text and source identity are not loggable; `persistence_postgres` `audit_event` inserts call the same gate | -| `service_tls` | production TLS bind gates and rustls server config | +| `operational_log` | operational logs; `try_record` is the recording gate and source text/source identity are not loggable | +| `service_tls` | production TLS bind gates and rustls server configuration | | `derived_sensitivity` | derived topic/factor/relation outputs inherit source sensitivity | -| `longitudinal_core` | within/between decomposition; refuse between-as-within; component RMSE; event-time lagged-correlation standardization from lagged covariance plus both occasion-specific marginal variances with exact binary64 covariance-bound validation; scalar event-time `discreteDRIFTstd` and research-candidate diffusion standardisation; CWC-then-irregular residual pairwise-mean log-rate with Curran refusal (not raw-process drift); arithmetic boundary only, not a DSEM/ctsem/state-transition estimator | +| `longitudinal_core` | Longitudinal Modeling temporal composition and recovery: within/between decomposition, event-time lagged correlation using both marginals, scalar event-time mappings, irregular residual log-rate, and typed interval admission; reusable static psychometric arithmetic is consumed only through released fast-mlsirm ACLs | | `topic_lineage` | global topic identity across active/dormant/reactivated states | | `network_analysis` | compositional cluster-pair gates; raw simplex is not Euclidean | | `interpretation_gateway` | evidence-bounded LLM interpretations; not estimators or observed facts | @@ -110,80 +116,41 @@ boundaries above remain the target modular MSA architecture. | `model_selection` | fitted candidate-`K` scoring from the CPU `f64` reference plus statistical/Pareto gates; LLM votes are not numerical authority | | `checkpoint_authority` | a model checkpoint is not the CPU `f64` estimator | | `compute_backend` | VRAM-budgeted streamed planning, executable OOM retry plans, and a compensated CPU `f64` reference | -| `episode_membership` | episode membership cannot escape the episode event-time interval | | `membership_target` | language, episode, template, department, and opportunity-pool targets cannot collapse into entity or project | | `topic_measurement` | logistic-normal ALR/ILR coordinates and the CPU `f64` TRSL-TM reference estimator | -| `analysis_engine` | bounded cutoff-safe temporal evidence readiness execution and digest-bound terminal artifacts | -| `psychometric_core` | posterior-aware structural input gates, CWC within/between OLS plus the contextual effect, event-time log-rate, unequal-interval discrete-lag remapping, constant-predictor discrete effect, time-varying-predictor discrete effect (Eq. 14), exact scalar discrete process noise (Driver et al., 2017, Eq. 3), lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; `asymDIFFUSION`), trait-plus-state variance (Driver et al., 2017, §4.3 `TRAITVAR`; not process noise), observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5; Table 2 `MANIFESTVAR` is `Θ`, not `Var(y)`; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; `Θ` does not enter lagged observed covariance; observed-indicator mean is `τ + λ μ`; `MANIFESTMEANS` is not `E(y)`; `CINT` is not `MANIFESTMEANS`; discrete latent mean is `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ`; `T0MEANS` is not `μ_t`; `CINT` is not the discrete increment; evolved observed mean is `τ + λ μ_t`; `τ + λ μ_0` is not `E(y_t)`; contemporaneous `TDPREDEFFECT` impulse is `m x`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that contemporaneous impulse is `τ + λ(μ_t + m x)`, and `τ + λ μ_t` is not that observed mean; time-independent `TIPREDEFFECT` increment is `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, not `M x`, not Voelkle Eq. 14, and not the coefficient `B`; Eq. 5 of that increment is `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`; within-interval `TDPREDEFFECT` carry is `e^{A(t−u)} M x` for `t0 < u < t`, not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that carry is `τ + λ(μ_t + e^{a(t−u)} m x)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that carried observed mean when `u ≠ t`; first-occasion `T0TIPREDEFFECT` shift is `t0_b z` and Eq. 3 first-summand carry is `e^{A Δt} t0_b z` (`T0TIPREDEFFECT` is not `TIPREDEFFECT` `B`; `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`; `e^{A Δt} t0_b z` is not `t0_b z`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_b z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean), first-occasion `T0TDPREDEFFECT` shift is `t0_m x0` and Eq. 3 first-summand carry is `e^{A Δt} t0_m x0` (`T0TDPREDEFFECT` is not `TDPREDEFFECT` `M`; `t0_m x0` is not `M x`; `e^{A Δt} t0_m x0` is not `t0_m x0`; `e^{A Δt} t0_m x0` is not `e^{A(t−u)} M x` for `t0 < u < t`; `t0_m x0` is not `t0_b z`; an impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_m x0)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean; `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean; §7.2 level-change `CINT` is `κ = −a m x` with `a < 0` so `−κ / a = m x` (`−a m x` is not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`, and not the extra near-zero-drift latent process also named in §7.2; Eq. 3 of that setting is `(1 − e^{a Δt}) m x`, which is not `m x`, not `κ`, and not `TIPREDEFFECT`; §7.2 extra-process contribution is `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (`ε = a` is `a_{ηξ} x Δt e^{a Δt}`; identification `TDPREDEFFECT` on the extra process is 1; printed extra `DRIFT` is `−0.000001`; not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`; `ε ≥ 0` fails closed; Eq. 5 of that contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)`; the extra process has `LAMBDA` 0 and is not an observed indicator; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; the contribution is not `E(y_t)`; the evolved-plus-contribution latent mean is not `E(y_t)`; after-t0 extra-process `TDPREDEFFECT` is `a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)` for `t0 < u < t` while `μ_t` uses `Δt`; Eq. 5 of that after-t0 contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)`; the first-occasion extra-process observed mean is not that observed mean when `u ≠ t0`; `e^{a(t−u)} m x` is a Dirac on the original process, not this `DRIFT` drive; §7.2 `asymTIPREDEFFECT` is `-B z / a` for `a < 0` (`-B z / a` is not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; §7.2 `addedTIPREDVAR` is `(B / a)² v`, not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`; Table 2 `asymCINT` is `-κ / a` for `a < 0` and is not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; p. 16 stationary `T0MEANS` is `-κ / a + −B z / a` and is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean; Eq. 5 of that constrained mean is `τ + λ(−κ / a + −B z / a)`; `τ + λ μ_0` is not that observed mean; `τ + λ(−κ / a)` is not that observed mean when `B z ≠ 0`; `τ + λ μ_t` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`; stationary `T0VAR` is `trait + −q / (2 a) + (B / a)² v` (not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`); lagged stationary `T0VAR` is `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (trait and `addedTIPREDVAR` do not decay; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map; Eq. 5 of that lagged covariance is `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ`; `Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance); later-occasion stationary `T0VAR` is `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt`; `Q_Δt` is not that later map; Eq. 5 of that later-occasion variance is `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ`; lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not `Var(y_t)`; the later-occasion latent variance is not `Var(y_t)`))), irregular already-centered residual lag, Rubin `T` on OLS loadings, and strong-gated latent means (two-observation residual variance is identically `0` and caps at strong/scalar; Putnick & Bornstein, 2016) | -| `validation_core` | RMSE, bias, coverage, graph, and Monte Carlo metrics | -| `tepp_api` | versioned DTO, schema, and export contracts | -| `psychometric_core` | posterior-aware structural input gates, CWC within/between OLS plus the contextual effect, event-time log-rate, unequal-interval discrete-lag remapping, constant-predictor discrete effect, time-varying-predictor discrete effect (Eq. 14), exact scalar discrete process noise (Driver et al., 2017, Eq. 3), lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; `asymDIFFUSION`), trait-plus-state variance (Driver et al., 2017, §4.3 `TRAITVAR`; not process noise), observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5; Table 2 `MANIFESTVAR` is `Θ`, not `Var(y)`; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; `Θ` does not enter lagged observed covariance; observed-indicator mean is `τ + λ μ`; `MANIFESTMEANS` is not `E(y)`; `CINT` is not `MANIFESTMEANS`; discrete latent mean is `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ`; `T0MEANS` is not `μ_t`; `CINT` is not the discrete increment; evolved observed mean is `τ + λ μ_t`; `τ + λ μ_0` is not `E(y_t)`; contemporaneous `TDPREDEFFECT` impulse is `m x`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that contemporaneous impulse is `τ + λ(μ_t + m x)`, and `τ + λ μ_t` is not that observed mean; time-independent `TIPREDEFFECT` increment is `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, not `M x`, not Voelkle Eq. 14, and not the coefficient `B`; Eq. 5 of that increment is `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`; within-interval `TDPREDEFFECT` carry is `e^{A(t−u)} M x` for `t0 < u < t`, not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that carry is `τ + λ(μ_t + e^{a(t−u)} m x)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that carried observed mean when `u ≠ t`; first-occasion `T0TIPREDEFFECT` shift is `t0_b z` and Eq. 3 first-summand carry is `e^{A Δt} t0_b z` (`T0TIPREDEFFECT` is not `TIPREDEFFECT` `B`; `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`; `e^{A Δt} t0_b z` is not `t0_b z`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_b z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean), first-occasion `T0TDPREDEFFECT` shift is `t0_m x0` and Eq. 3 first-summand carry is `e^{A Δt} t0_m x0` (`T0TDPREDEFFECT` is not `TDPREDEFFECT` `M`; `t0_m x0` is not `M x`; `e^{A Δt} t0_m x0` is not `t0_m x0`; `e^{A Δt} t0_m x0` is not `e^{A(t−u)} M x` for `t0 < u < t`; `t0_m x0` is not `t0_b z`; an impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_m x0)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean; `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean; §7.2 level-change `CINT` is `κ = −a m x` with `a < 0` so `−κ / a = m x` (`−a m x` is not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`, and not the extra near-zero-drift latent process also named in §7.2; Eq. 3 of that setting is `(1 − e^{a Δt}) m x`, which is not `m x`, not `κ`, and not `TIPREDEFFECT`; §7.2 extra-process contribution is `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (`ε = a` is `a_{ηξ} x Δt e^{a Δt}`; identification `TDPREDEFFECT` on the extra process is 1; printed extra `DRIFT` is `−0.000001`; not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`; `ε ≥ 0` fails closed; Eq. 5 of that contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)`; the extra process has `LAMBDA` 0 and is not an observed indicator; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; the contribution is not `E(y_t)`; the evolved-plus-contribution latent mean is not `E(y_t)`; after-t0 extra-process `TDPREDEFFECT` is `a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)` for `t0 < u < t` while `μ_t` uses `Δt`; Eq. 5 of that after-t0 contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)`; the first-occasion extra-process observed mean is not that observed mean when `u ≠ t0`; `e^{a(t−u)} m x` is a Dirac on the original process, not this `DRIFT` drive; §7.2 `asymTIPREDEFFECT` is `-B z / a` for `a < 0` (`-B z / a` is not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; §7.2 `addedTIPREDVAR` is `(B / a)² v`, not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`; Table 2 `asymCINT` is `-κ / a` for `a < 0` and is not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; p. 16 stationary `T0MEANS` is `-κ / a + −B z / a` and is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean; Eq. 5 of that constrained mean is `τ + λ(−κ / a + −B z / a)`; `τ + λ μ_0` is not that observed mean; `τ + λ(−κ / a)` is not that observed mean when `B z ≠ 0`; `τ + λ μ_t` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`; stationary `T0VAR` is `trait + −q / (2 a) + (B / a)² v` (not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`); lagged stationary `T0VAR` is `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (trait and `addedTIPREDVAR` do not decay; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map; Eq. 5 of that lagged covariance is `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ`; `Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance); later-occasion stationary `T0VAR` is `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt`; `Q_Δt` is not that later map; Eq. 5 of that later-occasion variance is `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ`; lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not `Var(y_t)`; the later-occasion latent variance is not `Var(y_t)`); predetermined later-occasion `T0VAR` is `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (free `T0VAR` `p_0` is not that later map; setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map; stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map; Eq. 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ`; `MANIFESTVAR` is not `Var(y_t)`; the predetermined later-occasion latent variance is not `Var(y_t)`; stationary later observed variance is not that observed variance when `p_0` is free); predetermined lagged `T0VAR` is `trait + e^{a Δt} p_0 + (B / a)² v` (free `T0VAR` `p_0` is not that lagged map; setting `p_0 = −q / (2 a)` recovers the stationary lagged map; stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map; later-occasion variance includes `Q_Δt` and is not that lagged map; Eq. 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ`; `MANIFESTVAR` does not enter; the predetermined lagged latent covariance is not that observed covariance; predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance; stationary lagged observed covariance is not that observed covariance when `p_0` is free; the predetermined first-occasion variance of §4.3 predetermined `T0VAR` is `trait + p_0 + (B / a)² v`; free `p_0` is not that map; stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free; lagged covariance decays the state and is not that map; later-occasion variance includes `Q_Δt` and is not that map; Eq. 5 of that predetermined first-occasion variance is `λ²(trait + p_0 + (B / a)² v) + θ + ψ`; `MANIFESTVAR` is not that first-occasion observed variance; the predetermined first-occasion latent variance is not that observed variance; stationary first-occasion observed variance is not that observed variance when `p_0` is free; predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance; later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z; first-occasion lagged omits `e^{a s} Q_u`; later-occasion variance does not lag; stationary lagged uses `−q / (2 a)`; decaying the later total is not that map; Eq. 5 of that later-start lagged covariance is `λ²` of it plus `ψ`; `Θ` does not enter; first-occasion lagged observed omits `e^{a s} Q_u`; later observed variance includes `Q_u` and `θ`; later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z; later-occasion variance at `u` omits `Q_s`; later-start lagged covariance omits `Q_s`; stationary later uses `−q / (2 a)`; evolving the later total as if it were all state is not that map; ignoring `startoffset` omits `e^{2 a s} Q_u`; Eq. 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`; `MANIFESTVAR` is not that observed variance; p. 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4; unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`; the §7.1 trait-plus-state autocorrelation uses `TRAITVAR` and is not `discreteDRIFTstd`; `TRAITVAR` is not the standardisation variance; p. 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4; unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`; the continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`; `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z; unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`; the discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`; `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z); unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`; the discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`; `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z; unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`; `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z; unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`; the asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`; `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z; the affected variance is free `T0VAR`, not `asymDIFFUSION`; unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`; `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`; `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`; `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); 2017-era `addedT0TIPREDVAR` is `t0_b² v` (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z; `T0TIPREDEFFECT %*% TIPREDVAR %*% t(T0TIPREDEFFECT)` immediately after `T0TIPREDEFFECTstd`; form `t0_b` first, then square, then multiply by `v`; a zero coefficient or zero predictor variance is exactly zero; free `T0TIPREDEFFECT` does not require `a < 0`; `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map; `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance; free `T0VAR` is not this extra TI variance; `TRAITVAR` is not this extra TI variance; Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z; form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; `t0_b² v` is the latent extra, not the observed extra; `λ² p_0 + θ` is first-occasion observed variance, not this extra; `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra; `MANIFESTVAR` `θ` is not this extra; Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v`; form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; lasting asymptotic extra requires `a < 0`; `(B / a)² v` is the latent extra, not the observed extra; `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra; `λ² p + θ` is stationary observed variance, not this extra; `MANIFESTVAR` `θ` is not this extra; p. 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance; unstandardised `M` is not `TDPREDEFFECTstd`; `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`; intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`; `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`; Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance; unstandardised `t0_m` is not `T0TDPREDEFFECTstd`; `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`; `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`; `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`; free `T0VAR` does not require `a < 0`; p. 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; default ridge is 0); unstandardised `T0VAR` is not `T0VARstd`; `T0TDPREDEFFECTstd` is not `T0VARstd`; `addedT0TIPREDVAR` is not `T0VARstd`; p. 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend); unstandardised `TRAITVAR` is not `TRAITVARstd`; `T0VARstd` is not `TRAITVARstd` even when both equal 1; `addedT0TIPREDVAR` is not `TRAITVARstd`; p. 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0); unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`; `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1; `MANIFESTVAR` is not `MANIFESTTRAITVARstd`; p. 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug); unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`; `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1; Equation 5 `Var(y)` is not `MANIFESTVARstd`; p. 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`); unstandardised `TIPREDVAR` is not `TIPREDVARstd`; `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1; §7.2 `addedTIPREDVAR` is not `TIPREDVARstd`; p. 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`); unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`; `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1; `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`; p. 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `discreteCINT` is not `discreteCINTstd`; `κ / √p` is not `discreteCINTstd`; `(-κ / a) / √p` is not `discreteCINTstd`; `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`; unstandardised `asymCINT` is not `asymCINTstd`; `κ / √p` is not `asymCINTstd`; `discreteCINTstd` is not `asymCINTstd`; `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`; unstandardised `T0MEANS` is not `T0MEANSstd`; `T0VARstd` is not `T0MEANSstd`; `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`; `MANIFESTMEANSstd` is `τ / √θ` after strictly positive `MANIFESTVAR`; unstandardised `MANIFESTMEANS` is not `MANIFESTMEANSstd`; `MANIFESTVARstd` is not `MANIFESTMEANSstd`; `τ / √(λ² Var(η) + θ)` is not `MANIFESTMEANSstd`; p. 16 `CINTstd` is `κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `CINT` is not `CINTstd`; `asymCINTstd` is not `CINTstd`; `discreteCINTstd` is not `CINTstd`; `κ / √(trait + p + added)` is not `CINTstd`;))))), irregular already-centered residual lag, Rubin `T` on OLS loadings, and strong-gated latent means (two-observation residual variance is identically `0` and caps at strong/scalar; Putnick & Bornstein, 2016) | -| `psychometric_core` | posterior-aware structural input gates, CWC within/between OLS plus the contextual effect, event-time log-rate, unequal-interval discrete-lag remapping, constant-predictor discrete effect, time-varying-predictor discrete effect (Eq. 14), exact scalar discrete process noise (Driver et al., 2017, Eq. 3), lagged latent covariance and unconditional latent variance (Driver et al., 2017, Eq. 3–4), stationary within-subject variance (Driver et al., 2017, Eq. 4 as `Δt → ∞`; `asymDIFFUSION`), trait-plus-state variance (Driver et al., 2017, §4.3 `TRAITVAR`; not process noise), observed-indicator variance and lagged observed covariance (Driver et al., 2017, Eq. 5; Table 2 `MANIFESTVAR` is `Θ`, not `Var(y)`; `MANIFESTTRAITVAR` is not `MANIFESTVAR`; `Θ` does not enter lagged observed covariance; observed-indicator mean is `τ + λ μ`; `MANIFESTMEANS` is not `E(y)`; `CINT` is not `MANIFESTMEANS`; discrete latent mean is `exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ`; `T0MEANS` is not `μ_t`; `CINT` is not the discrete increment; evolved observed mean is `τ + λ μ_t`; `τ + λ μ_0` is not `E(y_t)`; contemporaneous `TDPREDEFFECT` impulse is `m x`, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that contemporaneous impulse is `τ + λ(μ_t + m x)`, and `τ + λ μ_t` is not that observed mean; time-independent `TIPREDEFFECT` increment is `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, not `M x`, not Voelkle Eq. 14, and not the coefficient `B`; Eq. 5 of that increment is `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`; within-interval `TDPREDEFFECT` carry is `e^{A(t−u)} M x` for `t0 < u < t`, not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle Eq. 14; Eq. 5 of that carry is `τ + λ(μ_t + e^{a(t−u)} m x)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that carried observed mean when `u ≠ t`; first-occasion `T0TIPREDEFFECT` shift is `t0_b z` and Eq. 3 first-summand carry is `e^{A Δt} t0_b z` (`T0TIPREDEFFECT` is not `TIPREDEFFECT` `B`; `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`; `e^{A Δt} t0_b z` is not `t0_b z`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_b z)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean), first-occasion `T0TDPREDEFFECT` shift is `t0_m x0` and Eq. 3 first-summand carry is `e^{A Δt} t0_m x0` (`T0TDPREDEFFECT` is not `TDPREDEFFECT` `M`; `t0_m x0` is not `M x`; `e^{A Δt} t0_m x0` is not `t0_m x0`; `e^{A Δt} t0_m x0` is not `e^{A(t−u)} M x` for `t0 < u < t`; `t0_m x0` is not `t0_b z`; an impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`; Eq. 5 of that carry is `τ + λ(μ_t + e^{a Δt} t0_m x0)`, and `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean; `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean; §7.2 level-change `CINT` is `κ = −a m x` with `a < 0` so `−κ / a = m x` (`−a m x` is not the dissipating Dirac, not a free `CINT`, not `TIPREDEFFECT`, and not the extra near-zero-drift latent process also named in §7.2; Eq. 3 of that setting is `(1 − e^{a Δt}) m x`, which is not `m x`, not `κ`, and not `TIPREDEFFECT`; §7.2 extra-process contribution is `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (`ε = a` is `a_{ηξ} x Δt e^{a Δt}`; identification `TDPREDEFFECT` on the extra process is 1; printed extra `DRIFT` is `−0.000001`; not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`; `ε ≥ 0` fails closed; Eq. 5 of that contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)`; the extra process has `LAMBDA` 0 and is not an observed indicator; `τ + λ μ_t` is not that observed mean; `τ + λ(μ_t + m x)` is not that observed mean; the contribution is not `E(y_t)`; the evolved-plus-contribution latent mean is not `E(y_t)`; after-t0 extra-process `TDPREDEFFECT` is `a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)` for `t0 < u < t` while `μ_t` uses `Δt`; Eq. 5 of that after-t0 contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a)`; the first-occasion extra-process observed mean is not that observed mean when `u ≠ t0`; `e^{a(t−u)} m x` is a Dirac on the original process, not this `DRIFT` drive; §7.2 `asymTIPREDEFFECT` is `-B z / a` for `a < 0` (`-B z / a` is not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`; §7.2 `addedTIPREDVAR` is `(B / a)² v`, not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`; Table 2 `asymCINT` is `-κ / a` for `a < 0` and is not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`; p. 16 stationary `T0MEANS` is `-κ / a + −B z / a` and is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean; Eq. 5 of that constrained mean is `τ + λ(−κ / a + −B z / a)`; `τ + λ μ_0` is not that observed mean; `τ + λ(−κ / a)` is not that observed mean when `B z ≠ 0`; `τ + λ μ_t` is not that observed mean; `MANIFESTMEANS` is not `E(y_0)`; the constrained latent mean is not `E(y_0)`; stationary `T0VAR` is `trait + −q / (2 a) + (B / a)² v` (not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`); lagged stationary `T0VAR` is `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (trait and `addedTIPREDVAR` do not decay; contemporaneous `T0VAR` is not that lagged map; decaying the constrained total as if it were all state is not that lagged map; Eq. 5 of that lagged covariance is `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ`; `Θ` does not enter; contemporaneous `Var(y_0)` is not that lagged observed covariance; the lagged latent covariance is not that observed covariance); later-occasion stationary `T0VAR` is `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (trait and `addedTIPREDVAR` do not enter `Q_Δt`; under stationarity that composition equals contemporaneous `T0VAR`; evolving the constrained total as if it were all state is not that later map; the lagged covariance omits `Q_Δt`; `Q_Δt` is not that later map; Eq. 5 of that later-occasion variance is `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ`; lagged observed covariance omits `Q_Δt` and `θ`; `MANIFESTVAR` is not `Var(y_t)`; the later-occasion latent variance is not `Var(y_t)`); predetermined later-occasion `T0VAR` is `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (free `T0VAR` `p_0` is not that later map; setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map; stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map; Eq. 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ`; `MANIFESTVAR` is not `Var(y_t)`; the predetermined later-occasion latent variance is not `Var(y_t)`; stationary later observed variance is not that observed variance when `p_0` is free); predetermined lagged `T0VAR` is `trait + e^{a Δt} p_0 + (B / a)² v` (free `T0VAR` `p_0` is not that lagged map; setting `p_0 = −q / (2 a)` recovers the stationary lagged map; stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free; evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map; later-occasion variance includes `Q_Δt` and is not that lagged map; Eq. 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ`; `MANIFESTVAR` does not enter; the predetermined lagged latent covariance is not that observed covariance; predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance; stationary lagged observed covariance is not that observed covariance when `p_0` is free; the predetermined first-occasion variance of §4.3 predetermined `T0VAR` is `trait + p_0 + (B / a)² v`; free `p_0` is not that map; stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free; lagged covariance decays the state and is not that map; later-occasion variance includes `Q_Δt` and is not that map; Eq. 5 of that predetermined first-occasion variance is `λ²(trait + p_0 + (B / a)² v) + θ + ψ`; `MANIFESTVAR` is not that first-occasion observed variance; the predetermined first-occasion latent variance is not that observed variance; stationary first-occasion observed variance is not that observed variance when `p_0` is free; predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance; later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z; first-occasion lagged omits `e^{a s} Q_u`; later-occasion variance does not lag; stationary lagged uses `−q / (2 a)`; decaying the later total is not that map; Eq. 5 of that later-start lagged covariance is `λ²` of it plus `ψ`; `Θ` does not enter; first-occasion lagged observed omits `e^{a s} Q_u`; later observed variance includes `Q_u` and `θ`; later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z; later-occasion variance at `u` omits `Q_s`; later-start lagged covariance omits `Q_s`; stationary later uses `−q / (2 a)`; evolving the later total as if it were all state is not that map; ignoring `startoffset` omits `e^{2 a s} Q_u`; Eq. 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`; `MANIFESTVAR` is not that observed variance; p. 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4; unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`; the §7.1 trait-plus-state autocorrelation uses `TRAITVAR` and is not `discreteDRIFTstd`; `TRAITVAR` is not the standardisation variance; p. 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (footnote 4; unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`; the continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`; `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z; unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`; the discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`; `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`; `TRAITVAR` is not the standardisation variance; p. 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z); unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`; the discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`; `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z; unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`; `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); p. 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z; unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`; the asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`; the finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`; `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z; the affected variance is free `T0VAR`, not `asymDIFFUSION`; unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`; `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`; `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`; `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`; `TRAITVAR` is not the standardisation variance); 2017-era `addedT0TIPREDVAR` is `t0_b² v` (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z; `T0TIPREDEFFECT %*% TIPREDVAR %*% t(T0TIPREDEFFECT)` immediately after `T0TIPREDEFFECTstd`; form `t0_b` first, then square, then multiply by `v`; a zero coefficient or zero predictor variance is exactly zero; free `T0TIPREDEFFECT` does not require `a < 0`; `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map; `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance; free `T0VAR` is not this extra TI variance; `TRAITVAR` is not this extra TI variance; Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z; form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; `t0_b² v` is the latent extra, not the observed extra; `λ² p_0 + θ` is first-occasion observed variance, not this extra; `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra; `MANIFESTVAR` `θ` is not this extra; Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v`; form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`; a zero loading or zero extra is exactly zero; lasting asymptotic extra requires `a < 0`; `(B / a)² v` is the latent extra, not the observed extra; `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra; `λ² p + θ` is stationary observed variance, not this extra; `MANIFESTVAR` `θ` is not this extra; p. 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance; unstandardised `M` is not `TDPREDEFFECTstd`; `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`; intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`; `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`; Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance; unstandardised `t0_m` is not `T0TDPREDEFFECTstd`; `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`; `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`; `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`; free `T0VAR` does not require `a < 0`; p. 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; default ridge is 0); unstandardised `T0VAR` is not `T0VARstd`; `T0TDPREDEFFECTstd` is not `T0VARstd`; `addedT0TIPREDVAR` is not `T0VARstd`; p. 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend); unstandardised `TRAITVAR` is not `TRAITVARstd`; `T0VARstd` is not `TRAITVARstd` even when both equal 1; `addedT0TIPREDVAR` is not `TRAITVARstd`; p. 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0); unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`; `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1; `MANIFESTVAR` is not `MANIFESTTRAITVARstd`; p. 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug); unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`; `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1; Equation 5 `Var(y)` is not `MANIFESTVARstd`; p. 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`); unstandardised `TIPREDVAR` is not `TIPREDVARstd`; `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1; §7.2 `addedTIPREDVAR` is not `TIPREDVARstd`; p. 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`); unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`; `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1; `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`; p. 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`; unstandardised `discreteCINT` is not `discreteCINTstd`; `κ / √p` is not `discreteCINTstd`; `(-κ / a) / √p` is not `discreteCINTstd`; `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`; unstandardised `asymCINT` is not `asymCINTstd`; `κ / √p` is not `asymCINTstd`; `discreteCINTstd` is not `asymCINTstd`; `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`; unstandardised `T0MEANS` is not `T0MEANSstd`; `T0VARstd` is not `T0MEANSstd`; `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`;))))), irregular already-centered residual lag, Rubin `T` on OLS loadings, and strong-gated latent means (two-observation residual variance is identically `0` and caps at strong/scalar; Putnick & Bornstein, 2016) | +| `psychometric_core` | posterior-aware structural measurement gates and existing non-temporal fitting/compatibility surfaces; it is not the owner for new event-time temporal composition or reusable static generalized psychometric arithmetic | +Foundation crates expose only tested contracts. Empty façades are not public APIs. No crate exposes placeholder production behavior merely to reserve an API. -Foundation crates expose only tested contracts. Empty façades are not public -APIs. +## Immutable evidence boundary -No crate exposes placeholder production behavior in Task 1. This prevents an -empty façade from becoming a de facto public API before its invariants and tests -exist. +Stable RFC 9562 `UUIDv7` identities are independent from canonical `SHA-256` content digests. Source bytes and UTF-8 document text are copied into immutable owned storage, bounded before allocation, and verified without exposing mutable fields. -## Immutable evidence boundary +A source span records an owning document, a half-open UTF-8 byte range, the matching half-open Unicode-scalar range, and optional page/layout geometry. It fails closed for empty or reversed ranges, byte or scalar overflow, mid-code-point boundaries, coordinate disagreement, cross-document use, nonfinite geometry, nonpositive dimensions, and rectangles outside the page. Scalar coordinates are evidence locations rather than grapheme, word, or sentence boundaries; language-tailored segmentation remains a separate boundary. + +The evidence boundary exposes a strict versioned JSON wire contract without exposing private Rust fields. Reconstruction revalidates identifiers, canonical digests, content limits, exact text coordinates, ownership, and page geometry. Malformed/unsupported input and unknown nested fields fail closed with content-redacting errors. -Task 2 begins the executable `evidence_core` boundary. Stable RFC 9562 `UUIDv7` -identities are independent from canonical `SHA-256` content digests. Source -bytes and UTF-8 document text are copied into immutable owned storage, bounded -before allocation, and verified without exposing mutable fields. - -A source span records an owning document, a half-open UTF-8 byte range, the -matching half-open Unicode-scalar range, and optional page/layout geometry. It -fails closed for empty or reversed ranges, byte or scalar overflow, -mid-code-point boundaries, coordinate disagreement, cross-document use, -nonfinite geometry, nonpositive dimensions, and rectangles outside the page. -Scalar coordinates are evidence locations rather than grapheme, word, or -sentence boundaries; language-tailored segmentation remains a later module. - -The boundary now exposes a strict JSON wire version `1` without exposing private -Rust fields. Artifacts, documents, spans, and nested page locations are serialized -through explicit DTOs with unknown-field rejection. Reconstruction parses and -revalidates RFC 9562 identifiers, canonical digests, content limits, exact text -coordinates, document ownership, and page geometry. Artifact bytes and document -text are rehashed during reconstruction, and digest substitution fails closed. -Malformed JSON, unsupported versions, invalid byte values, and unknown nested -fields produce stable content-redacting errors. - -Persistence, JSON Schema publication, JSON-LD, GraphML, source acquisition -metadata, signatures, and W3C PROV remain outward adapters or later contracts. -They must depend inward on these validated domain values rather than defining -them. +Persistence, JSON Schema publication, JSON-LD, GraphML, source acquisition metadata, signatures, and W3C PROV remain outward adapters or later contracts. They depend inward on validated domain values rather than defining them. ## Quality architecture -The workspace centralizes package metadata and Rust/Clippy lints. Every member -inherits `unsafe_code = "forbid"`, `missing_docs = "deny"`, and warning denial. -Repository contract scripts independently verify the approved crate set, -workspace inheritance, action SHA pinning, absence of LLM credentials from -ordinary CI, and complete Rust documentation. +The workspace centralizes package metadata and Rust/Clippy lints. Every member inherits `unsafe_code = "forbid"`, `missing_docs = "deny"`, and warning denial. Repository contract scripts independently verify the approved crate set, workspace inheritance, action SHA pinning, absence of execution credentials from ordinary CI, and complete Rust documentation. -Stable Rust 1.98.0 is the compile, lint, test, and line-coverage reference. -Branch coverage runs in a pinned nightly lane because LLVM branch coverage -remains unstable in Rust. `cargo-nextest` runs tests without retries, while -doctests remain a separate `cargo test --doc` gate. `cargo-deny` enforces -advisory, license, ban, and source policy. Failed Rust coverage gates print the -exact missing source locations from the same instrumented run without weakening -the 100% contract. +Stable Rust is the compile/lint/test reference selected by repository toolchain policy. CPU `f64` is the scientific numerical reference. Branch/statement coverage, doctests, dependency policy, property/fuzz/security/concurrency tests, and exact missing-source evidence are enforced without retries or denominator manipulation. Accelerator claims require actual hardware execution and parity evidence rather than skipped tests. ## Temporal invariants -TEPP stores event/valid time, assertion time, document time, system time, available time, and knowledge cutoff independently. A historical analysis may include a document only when: +TEPP stores event/valid time, assertion time, document time, system time, available time, and knowledge cutoff independently. A historical analysis may include evidence only when: \[ \operatorname{available\_time}(d) \leq \operatorname{knowledge\_cutoff}. \] -When availability is an interval, every possible instant in that interval must satisfy the inequality. Unknown or open-ended availability that can extend past the cutoff fails closed; event time and document time cannot substitute for availability. +When availability is an interval, every admissible instant must satisfy the cutoff. Unknown or open-ended availability that can extend past the cutoff fails closed; event time and document time cannot substitute for availability. -Forward transition edges require a temporally valid partial order. Retrospective, revision, translation, citation, support, and contradiction relations retain their direction and provenance but do not create reverse state transitions. +Forward transition/state edges require a temporally valid partial order. Retrospective, revision, translation, citation, support, contradiction, and provenance relations retain their direction and evidence but do not become reverse state transitions. + +Longitudinal recovery preserves event-time spacing, available-time admission, irregular gaps, delayed/retrospective reports, missing occasions, changing memberships, language/source drift, and required alignment across translations/rotations/reflections or cluster labels. Scientific acceptance uses true-parameter state/trajectory recovery, RMSE/bias/coverage/convergence and Monte Carlo uncertainty; LLM output cannot activate a scientific candidate. ## Measurement invariants @@ -191,23 +158,25 @@ All languages share global topic identities and latent document coordinates. Lan Repeated report vocabulary is modeled through corpus-background, template, section, style, copied-text, prompt, modality, and substantive-topic sources. It is not silently removed by stopword lists, TF-IDF, or BM25. -Topic proportions are compositional (Aitchison, 1982). ESEM and network analysis consume logistic-normal latent coordinates or orthonormal log-ratio coordinates, with posterior uncertainty propagated through plausible values or a joint model (Asparouhov & Muthén, 2009; Asparouhov et al., 2018; Marsh et al., 2014). The product topic-estimator contract is TRSL-TM (ADR 0012); an STM-style logistic-normal family is the reference, not a shipped-backend claim (Blei & Lafferty, 2006; Roberts et al., 2014, 2019). TDT/CHRONOS event intelligence remains an accepted-target boundary (Allan, 2002; Anagnostopoulos et al., 2013). +Topic proportions are compositional (Aitchison, 1982). ESEM and network analysis consume logistic-normal latent coordinates or orthonormal log-ratio coordinates, with posterior uncertainty propagated through plausible values or a joint model (Asparouhov & Muthén, 2009; Asparouhov et al., 2018; Marsh et al., 2014). The product topic-estimator contract is TRSL-TM (ADR 0012); an STM-style logistic-normal family is a reference formulation, not a shipped-backend claim (Blei & Lafferty, 2006; Roberts et al., 2014, 2019). TDT/CHRONOS event intelligence remains evidence-gated (Allan, 2002; Anagnostopoulos et al., 2013). + +Rasch identity is distinct from generic 1PL. Cross-classification and multiple membership are distinct. Known hierarchy/testlet/rater/method/item-family effects precede residual latent-space dependence. Temporal candidate activation requires exact formulation identity, identification/alignment, Rust estimator, primary citations, required data support, and passing recovery; automatic enumeration is never automatic activation. ## Compute architecture -The CPU `f64` implementation is the numerical reference. Rayon-style fixed worker pools and thread-local sufficient statistics minimize context switching and oversubscription. GPU work is streamed; temporary responsibilities are never retained for the full corpus. The VRAM controller estimates peak allocation, reserves a safety margin, autotunes micro-batches, records telemetry, reduces batches after OOM, and falls back to CPU safely. +The CPU `f64` implementation is the numerical reference. Fixed worker pools and thread-local sufficient statistics minimize context switching and oversubscription. Accelerator work is streamed and parity-verified; temporary responsibilities are not retained for the full corpus. OOM is an expected state with bounded retry/fallback behavior, not a hidden sample-size reduction. ## Persistence -PostgreSQL is the reference relational store. Database objects use two-or-more-word `snake_case` names, including `document_record`, `temporal_interval`, `event_instance`, `event_mention`, `document_relation`, `segment_relation`, `entity_role_assignment`, `model_run`, `topic_definition`, `topic_correlation`, `topic_cluster`, `factor_solution`, `validation_metric`, and `audit_event`. `audit_event` inserts call `operational_log::try_record` before SQL is rendered so source text and source identity cannot enter the row. +PostgreSQL is the reference relational store. Database objects use two-or-more-word `snake_case` names. Temporal and membership persistence is normalized, interval/bitemporal constrained, idempotent where commands can be replayed, and immutable for evidence/provenance records. `audit_event` inserts call the operational logging gate before SQL is rendered so source text and source identity cannot enter the row. Cross-service SQL is prohibited. ## Security and trust boundaries -Documents and LLM outputs are untrusted. Exact spans, JSON Schema, size/depth limits, Unicode validity, prompt-injection isolation, provider allowlists, no-tool execution, tenant isolation, immutable audit events, dependency pinning, SBOM, provenance, and reproducible releases are mandatory. LLM live tests use `NVIDIA_NIM_API_KEY`; `COPILOT_GITHUB_TOKEN` is forbidden. +Documents and LLM outputs are untrusted. Exact spans, schema validation, size/depth limits, Unicode validity, prompt-injection isolation, tenant/purpose authorization, immutable audit evidence, dependency pinning, SBOM, provenance, and reproducible releases are mandatory. LLM live tests use `NVIDIA_NIM_API_KEY`; `COPILOT_GITHUB_TOKEN` is forbidden. The contextual-orchestrator migration is owned by its separate consumer-integration vehicle and is not silently folded into this Longitudinal Modeling branch. ## References -The full APA 7th register is [`docs/research/standards-and-literature.md`](docs/research/standards-and-literature.md). Method claims on this page use: +The full APA 7th register is [`docs/research/standards-and-literature.md`](docs/research/standards-and-literature.md). Detailed Longitudinal Modeling equations and exact implementation/recovery evidence are traced in TRACEABILITY/doctoring and source tests rather than duplicated in responsibility rows. Aitchison, J. (1982). The statistical analysis of compositional data. *Journal of the Royal Statistical Society: Series B, 44*(2), 139–177. https://doi.org/10.1111/j.2517-6161.1982.tb01195.x From d5107b19817556c4c902408b51ed2bb8c8181d2c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 04:28:24 +0900 Subject: [PATCH 095/296] test(longitudinal): pin subnormal discrete diffusion ratio --- crates/longitudinal_core/src/diffusion.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/longitudinal_core/src/diffusion.rs b/crates/longitudinal_core/src/diffusion.rs index d8c50ac94..ea5828693 100644 --- a/crates/longitudinal_core/src/diffusion.rs +++ b/crates/longitudinal_core/src/diffusion.rs @@ -164,12 +164,11 @@ mod tests { use crate::{EventTimeInterval, LongitudinalError}; #[test] - fn discrete_candidate_fails_closed_on_signed_zero_event_product() { + fn discrete_candidate_preserves_representable_minimum_subnormal_ratio() { let tiny = EventTimeInterval::new(f64::from_bits(1)).expect("minimum subnormal interval"); - assert_eq!( - recover_event_time_standardised_discrete_diffusion(1.0, -0.5, tiny), - Err(LongitudinalError::InvalidTemporalTransformInput) - ); + let recovered = recover_event_time_standardised_discrete_diffusion(1.0, -0.5, tiny) + .expect("the final standardized ratio is the minimum positive subnormal"); + assert_eq!(recovered.to_bits(), 1); } #[test] From 7164c7ce4a6ada24524399b0031171730a16a883 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 04:28:58 +0900 Subject: [PATCH 096/296] fix(longitudinal): preserve subnormal discrete diffusion ratio --- crates/longitudinal_core/src/diffusion.rs | 43 ++++++++++++++++------- 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/crates/longitudinal_core/src/diffusion.rs b/crates/longitudinal_core/src/diffusion.rs index ea5828693..388fa7ff6 100644 --- a/crates/longitudinal_core/src/diffusion.rs +++ b/crates/longitudinal_core/src/diffusion.rs @@ -67,8 +67,8 @@ pub fn recover_event_time_standardised_continuous_diffusion( /// # Errors /// /// Returns [`LongitudinalError::InvalidTemporalTransformInput`] for invalid -/// diffusion/drift, an event-time product that underflows to signed zero, or a -/// non-representable final ratio. Returns +/// diffusion/drift, a doubled event-time exponent that underflows to signed +/// zero, or a non-representable final ratio. Returns /// [`LongitudinalError::StationaryVarianceRequiresStableDrift`] unless `a < 0`. /// Returns [`LongitudinalError::StandardisedDiffusionRequiresPositiveWithinVariance`] /// when the stationary within-person variance is zero or underflows to zero. @@ -82,23 +82,42 @@ pub fn recover_event_time_standardised_discrete_diffusion( return Err(LongitudinalError::StandardisedDiffusionRequiresPositiveWithinVariance); } - let half_exponent = log_rate * event_interval.as_f64(); - if half_exponent == 0.0 { + let interval = event_interval.as_f64(); + let doubled_interval = interval * 2.0; + let exponent = if doubled_interval.is_finite() { + // Multiplication by two is exact while finite. Scaling the interval + // before the only rounded product preserves a representable `2aΔ` + // when `aΔ` alone would round to signed zero, and it avoids forming + // `2a`, whose intermediate can overflow for an extreme stable drift. + log_rate * doubled_interval + } else { + // If 2Δ overflows, Δ is already enormous. Form aΔ first and then apply + // the exact factor two; this branch cannot suffer the tiny-interval + // underflow that motivated the primary ordering above. + let half_exponent = log_rate * interval; + if half_exponent == f64::NEG_INFINITY { + return Ok(1.0); + } + if !half_exponent.is_finite() { + return Err(LongitudinalError::InvalidTemporalTransformInput); + } + half_exponent * 2.0 + }; + + // The target exponent is exactly 2aΔ with finite a < 0 and Δ > 0. If that + // target itself rounds to signed zero, then 1-exp(2aΔ) is also below the + // minimum representable positive binary64 result and must fail closed. + if exponent == 0.0 { return Err(LongitudinalError::InvalidTemporalTransformInput); } - if half_exponent == f64::NEG_INFINITY { + if exponent == f64::NEG_INFINITY { return Ok(1.0); } - if !half_exponent.is_finite() { + if !exponent.is_finite() { return Err(LongitudinalError::InvalidTemporalTransformInput); } - let exponent = half_exponent * 2.0; - let ratio = if exponent == f64::NEG_INFINITY { - 1.0 - } else { - -exponent.exp_m1() - }; + let ratio = -exponent.exp_m1(); if !ratio.is_finite() || ratio <= 0.0 || ratio > 1.0 { return Err(LongitudinalError::InvalidTemporalTransformInput); } From 671709bbc6cdf1090e16c1d8f6c9f2b4f8b2d831 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 05:14:05 +0900 Subject: [PATCH 097/296] test(longitudinal): require two repeated units for CWC --- .../tests/cwc_irregular_residual_contract.rs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/crates/longitudinal_core/tests/cwc_irregular_residual_contract.rs b/crates/longitudinal_core/tests/cwc_irregular_residual_contract.rs index 6a493df63..2bc7a59d2 100644 --- a/crates/longitudinal_core/tests/cwc_irregular_residual_contract.rs +++ b/crates/longitudinal_core/tests/cwc_irregular_residual_contract.rs @@ -61,6 +61,26 @@ fn cwc_mean_is_deterministic_under_input_row_permutation() { assert_eq!(reordered, expected, "CWC output must not depend on input row order"); } +#[test] +fn cwc_requires_two_units_with_repeated_event_time_evidence() { + let rows = [ + timed(1, 0.0, 11.0), + timed(1, 1.0, 10.8), + timed(1, 2.0, 10.6), + timed(2, 0.0, 4.0), + ]; + assert_eq!( + center_within_unit_event_lags(&rows), + Err(LongitudinalError::InvalidObservationPayload), + "a singleton second unit must not make a one-unit longitudinal sample look multilevel" + ); + assert_eq!( + recover_within_unit_irregular_residual_log_rate(&rows), + Err(LongitudinalError::InvalidObservationPayload), + "CWC recovery requires at least two lag-contributing units" + ); +} + #[test] fn cwc_mean_survives_overflowing_raw_sum_when_centered_values_are_finite() { let rows = [ From 4784b370c464c3de74661124c594b8b89b9b917e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 05:16:00 +0900 Subject: [PATCH 098/296] fix(longitudinal): require two lag-contributing CWC units --- .../src/irregular_residual.rs | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/crates/longitudinal_core/src/irregular_residual.rs b/crates/longitudinal_core/src/irregular_residual.rs index d314552f1..3c62e2cd2 100644 --- a/crates/longitudinal_core/src/irregular_residual.rs +++ b/crates/longitudinal_core/src/irregular_residual.rs @@ -20,8 +20,8 @@ impl EventTimedObservation { /// Construct an event-timed observation. /// /// The constructor stores the fields as given. Admission of finite scores, - /// strictly positive consecutive intervals, and at least two units happens - /// in [`center_within_unit_event_lags`]. + /// strictly positive consecutive intervals, and at least two lag-contributing + /// units happens in [`center_within_unit_event_lags`]. #[must_use] pub const fn new(unit_index: u32, event_time: f64, score: f64) -> Self { Self { @@ -99,8 +99,9 @@ impl LaggedWithinResidual { /// /// Stable between-unit means are removed first (CWC). Consecutive within-unit /// residuals then become [`LaggedWithinResidual`] pairs on possibly irregular -/// event intervals. Singleton units are skipped. Curran and Bauer (2011, -/// pp. 583–619; PMC3059070 XML opened 2026-09-02; Eq. 36) show that +/// event intervals. Singleton units do not contribute lags and therefore do not +/// count toward the two-unit longitudinal evidence floor. Curran and Bauer +/// (2011, pp. 583–619; PMC3059070 XML opened 2026-09-02; Eq. 36) show that /// person-mean subtraction of a time-varying covariate related to time is /// biased for the within-person effect. The returned pairs are therefore not a /// license to recover raw-process drift `a`. @@ -108,10 +109,10 @@ impl LaggedWithinResidual { /// # Errors /// /// Returns [`LongitudinalError::InvalidObservationPayload`] for empty, -/// singleton-only, fewer-than-two-unit, or non-finite rows, including -/// non-representable stable unit means and overflowing CWC residuals after a -/// finite mean, and [`LongitudinalError::NonPositiveEventInterval`] when any -/// consecutive event interval is not strictly positive. +/// singleton-only, fewer-than-two lag-contributing units, or non-finite rows, +/// including non-representable stable unit means and overflowing CWC residuals +/// after a finite mean, and [`LongitudinalError::NonPositiveEventInterval`] +/// when any consecutive event interval is not strictly positive. pub fn center_within_unit_event_lags( rows: &[EventTimedObservation], ) -> Result, LongitudinalError> { @@ -128,6 +129,13 @@ pub fn center_within_unit_event_lags( if groups.len() < 2 { return Err(LongitudinalError::InvalidObservationPayload); } + let lag_contributing_units = groups + .values() + .filter(|occasions| occasions.len() >= 2) + .count(); + if lag_contributing_units < 2 { + return Err(LongitudinalError::InvalidObservationPayload); + } let mut pairs = Vec::new(); for occasions in groups.values_mut() { if occasions.len() < 2 { @@ -843,7 +851,7 @@ mod tests { } #[test] - fn singleton_unit_is_skipped_and_all_singletons_fail_closed() { + fn singleton_second_unit_does_not_satisfy_longitudinal_unit_floor() { let drift = -0.2_f64; let mixed = [ timed(1, 0.0, 10.0 + 1.0), @@ -852,8 +860,10 @@ mod tests { timed(1, 3.0, 10.0 + (drift * 3.0).exp()), timed(2, 0.0, 4.0), ]; - let recovered = recover_within_unit_irregular_residual_log_rate(&mixed).expect("skip"); - assert!(recovered.is_finite()); + assert_eq!( + recover_within_unit_irregular_residual_log_rate(&mixed), + Err(LongitudinalError::InvalidObservationPayload) + ); assert_eq!( recover_within_unit_irregular_residual_log_rate(&[ timed(1, 0.0, 1.0), From 4a1f6c49847fd32c6129c9ceb7c46abd124b29ff Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 05:37:39 +0900 Subject: [PATCH 099/296] test(longitudinal): preserve cancelled stationary standardisation --- .../standardised_discrete_drift_contract.rs | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/crates/longitudinal_core/tests/standardised_discrete_drift_contract.rs b/crates/longitudinal_core/tests/standardised_discrete_drift_contract.rs index 07d4a60bf..7e10ed3a5 100644 --- a/crates/longitudinal_core/tests/standardised_discrete_drift_contract.rs +++ b/crates/longitudinal_core/tests/standardised_discrete_drift_contract.rs @@ -53,6 +53,30 @@ fn extreme_stable_rate_preserves_representable_stationary_result() { assert!((recovered - truth).abs() <= f64::EPSILON); } +#[test] +fn standardised_drift_does_not_materialise_a_cancelled_stationary_variance() { + let underflowed_stationary = recover_event_time_standardised_discrete_drift( + f64::from_bits(1), + -1.0, + event_time(1.0), + ) + .expect("positive real stationary variance cancels from the scalar standardisation"); + assert!((underflowed_stationary - (-1.0_f64).exp()).abs() <= f64::EPSILON); + + let overflowed_stationary = + recover_event_time_standardised_discrete_drift(f64::MAX, -0.25, event_time(1.0)) + .expect("an out-of-range stationary intermediate must not erase a finite final map"); + assert!((overflowed_stationary - (-0.25_f64).exp()).abs() <= f64::EPSILON); + + let extreme_scale = recover_event_time_standardised_discrete_drift( + f64::from_bits(1), + -1.0e307, + event_time(1.0e-307), + ) + .expect("the exact scalar standardisation is independent of diffusion scale"); + assert!((extreme_scale - (-1.0_f64).exp()).abs() <= f64::EPSILON); +} + #[test] fn event_time_exponent_underflow_fails_closed_instead_of_becoming_one() { assert_eq!( @@ -71,14 +95,6 @@ fn standardised_drift_fails_closed_without_positive_stationary_within_variance() recover_event_time_standardised_discrete_drift(0.0, -0.5, event_time(1.0)), Err(LongitudinalError::StandardisedDriftRequiresPositiveWithinVariance) ); - assert_eq!( - recover_event_time_standardised_discrete_drift( - f64::from_bits(1), - -1.0e307, - event_time(1.0e-307), - ), - Err(LongitudinalError::StandardisedDriftRequiresPositiveWithinVariance) - ); assert_eq!( recover_event_time_standardised_discrete_drift(0.4, 0.0, event_time(1.0)), Err(LongitudinalError::StationaryVarianceRequiresStableDrift) From 96d8ed134a45aaaf31f76bed1d363859ad5946d0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 05:38:09 +0900 Subject: [PATCH 100/296] test(longitudinal): reject stationary intermediate as standardisation gate --- crates/longitudinal_core/src/diffusion.rs | 28 +++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/crates/longitudinal_core/src/diffusion.rs b/crates/longitudinal_core/src/diffusion.rs index 388fa7ff6..93c48d94f 100644 --- a/crates/longitudinal_core/src/diffusion.rs +++ b/crates/longitudinal_core/src/diffusion.rs @@ -198,6 +198,34 @@ mod tests { assert_eq!(recovered, 1.0); } + #[test] + fn standardised_diffusion_does_not_materialise_a_cancelled_stationary_variance() { + let minimum_subnormal = f64::from_bits(1); + let continuous_underflow = + recover_event_time_standardised_continuous_diffusion(minimum_subnormal, -1.0) + .expect("q/p cancels a positive real stationary variance below binary64 range"); + assert_eq!(continuous_underflow, 2.0); + + let continuous_overflow = + recover_event_time_standardised_continuous_diffusion(f64::MAX, -0.25) + .expect("q/p cancels a positive real stationary variance above binary64 range"); + assert_eq!(continuous_overflow, 0.5); + + let interval = EventTimeInterval::new(1.0).expect("unit event interval"); + let discrete_underflow = recover_event_time_standardised_discrete_diffusion( + minimum_subnormal, + -1.0, + interval, + ) + .expect("Q_delta/p cancels the unrepresentable stationary intermediate"); + assert!((discrete_underflow - -(-2.0_f64).exp_m1()).abs() <= f64::EPSILON); + + let discrete_overflow = + recover_event_time_standardised_discrete_diffusion(f64::MAX, -0.25, interval) + .expect("finite standardized discrete diffusion must survive p overflow"); + assert!((discrete_overflow - -(-0.5_f64).exp_m1()).abs() <= f64::EPSILON); + } + #[test] fn continuous_candidate_rejects_nonrepresentable_ratio() { assert_eq!( From a4bc6230b414a3f47eba190ac5dabeb27446d3c2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 05:38:28 +0900 Subject: [PATCH 101/296] refactor(longitudinal): separate stationary admission from materialization --- crates/longitudinal_core/src/stationary.rs | 44 +++++++++++++++++----- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/crates/longitudinal_core/src/stationary.rs b/crates/longitudinal_core/src/stationary.rs index f2524c98b..4a6d52bf9 100644 --- a/crates/longitudinal_core/src/stationary.rs +++ b/crates/longitudinal_core/src/stationary.rs @@ -2,23 +2,40 @@ use crate::LongitudinalError; -/// Recover scalar stationary within-person variance `p = -q / (2a)`. +/// Validate inputs for a scalar stationary continuous-time process. /// -/// This primitive is intentionally private to Longitudinal Modeling. It admits -/// finite non-negative continuous diffusion and strictly negative drift while -/// avoiding an otherwise unnecessary overflow in the intermediate `2a`. -/// Callers decide whether zero stationary variance is admissible for their -/// named estimand. -pub(crate) fn recover_stationary_within_variance( +/// Finite `q >= 0` and strictly negative finite drift establish a finite, +/// non-negative stationary variance in the mathematical real-valued model. +/// This admission deliberately does not materialize `q / (-2a)` as binary64: +/// standardized scalar maps may cancel that variance algebraically even when +/// the intermediate itself lies below or above the representable `f64` range. +pub(crate) fn validate_stationary_process_inputs( continuous_diffusion: f64, log_rate: f64, -) -> Result { +) -> Result<(), LongitudinalError> { if !continuous_diffusion.is_finite() || continuous_diffusion < 0.0 || !log_rate.is_finite() { return Err(LongitudinalError::InvalidTemporalTransformInput); } if log_rate >= 0.0 { return Err(LongitudinalError::StationaryVarianceRequiresStableDrift); } + Ok(()) +} + +/// Recover scalar stationary within-person variance `p = -q / (2a)`. +/// +/// This primitive is intentionally private to Longitudinal Modeling. It admits +/// finite non-negative continuous diffusion and strictly negative drift while +/// avoiding an otherwise unnecessary overflow in the intermediate `2a`. +/// Unlike standardized maps in which `p` cancels algebraically, this function +/// actually returns `p`, so the stationary variance itself must be representable +/// as binary64. Callers decide whether zero stationary variance is admissible +/// for their named estimand. +pub(crate) fn recover_stationary_within_variance( + continuous_diffusion: f64, + log_rate: f64, +) -> Result { + validate_stationary_process_inputs(continuous_diffusion, log_rate)?; if continuous_diffusion == 0.0 { return Ok(0.0); } @@ -42,9 +59,18 @@ pub(crate) fn recover_stationary_within_variance( #[cfg(test)] mod tests { - use super::recover_stationary_within_variance; + use super::{recover_stationary_within_variance, validate_stationary_process_inputs}; use crate::LongitudinalError; + #[test] + fn stationary_input_admission_does_not_materialise_the_variance() { + assert_eq!( + validate_stationary_process_inputs(f64::from_bits(1), -1.0), + Ok(()) + ); + assert_eq!(validate_stationary_process_inputs(f64::MAX, -0.25), Ok(())); + } + #[test] fn avoids_doubling_overflow_when_final_stationary_variance_is_representable() { let stationary = recover_stationary_within_variance(f64::MAX, -f64::MAX) From 33f4b187f833cdf97dce3f4f9bcb1aa7afdf1aed Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 05:38:49 +0900 Subject: [PATCH 102/296] fix(longitudinal): avoid cancelled stationary drift materialization --- .../longitudinal_core/src/discrete_drift.rs | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/crates/longitudinal_core/src/discrete_drift.rs b/crates/longitudinal_core/src/discrete_drift.rs index 64f1495d6..1ce54ba40 100644 --- a/crates/longitudinal_core/src/discrete_drift.rs +++ b/crates/longitudinal_core/src/discrete_drift.rs @@ -1,7 +1,7 @@ //! Event-time standardisation for scalar continuous-time drift. use crate::{ - EventTimeInterval, LongitudinalError, stationary::recover_stationary_within_variance, + EventTimeInterval, LongitudinalError, stationary::validate_stationary_process_inputs, }; /// Recover the scalar p. 16 `discreteDRIFTstd` on event time. @@ -10,10 +10,12 @@ use crate::{ /// interval as `exp(A * delta_t)`. Their standardisation uses the relevant /// within-person asymptotic variance. In the scalar stationary case the /// affecting/affected standard-deviation ratio is one, so the standardised -/// auto-effect is numerically `exp(a * delta_t)` after a strictly positive -/// stationary within-person variance has been established. Equal numerical -/// values do not make unstandardised `discreteDRIFT` and `discreteDRIFTstd` -/// the same estimand. +/// auto-effect is numerically `exp(a * delta_t)` after stable negative drift +/// and positive continuous diffusion establish a positive real stationary +/// within-person variance. The cancelled stationary variance is not materialized +/// as binary64, because its representability does not constrain the final scalar +/// standardized map. Equal numerical values still do not make unstandardised +/// `discreteDRIFT` and `discreteDRIFTstd` the same estimand. /// /// This function is temporal composition, not a ctsem/DSEM estimator. It does /// not estimate `a`, process noise, uncertainty, or a latent state. The caller @@ -23,20 +25,20 @@ use crate::{ /// # Errors /// /// Returns [`LongitudinalError::InvalidTemporalTransformInput`] for non-finite -/// diffusion/drift inputs, negative diffusion, non-representable stationary -/// variance, an `a * delta_t` product that overflows or underflows to signed -/// zero, or an exponential that underflows to zero. Returns +/// diffusion/drift inputs, negative diffusion, an `a * delta_t` product that +/// overflows or underflows to signed zero, or an exponential whose nonzero +/// change is not representable and therefore rounds to zero or one. Returns /// [`LongitudinalError::StationaryVarianceRequiresStableDrift`] unless `a < 0`. /// Returns [`LongitudinalError::StandardisedDriftRequiresPositiveWithinVariance`] -/// when the stationary within-person variance is zero or underflows to zero. +/// when continuous diffusion is exactly zero, because the stationary variance +/// is then zero rather than merely outside the binary64 range. pub fn recover_event_time_standardised_discrete_drift( continuous_diffusion: f64, log_rate: f64, event_interval: EventTimeInterval, ) -> Result { - let within_person_variance = - recover_stationary_within_variance(continuous_diffusion, log_rate)?; - if within_person_variance <= 0.0 { + validate_stationary_process_inputs(continuous_diffusion, log_rate)?; + if continuous_diffusion == 0.0 { return Err(LongitudinalError::StandardisedDriftRequiresPositiveWithinVariance); } @@ -49,7 +51,11 @@ pub fn recover_event_time_standardised_discrete_drift( return Err(LongitudinalError::InvalidTemporalTransformInput); } let discrete_drift = exponent.exp(); - if discrete_drift == 0.0 { + // For every admitted finite interval and stable finite drift the exact + // transition lies strictly inside (0, 1). Returning either endpoint would + // erase a nonzero scientific effect solely because binary64 cannot express + // it, so both endpoint collapses fail closed. + if discrete_drift == 0.0 || discrete_drift == 1.0 { return Err(LongitudinalError::InvalidTemporalTransformInput); } Ok(discrete_drift) From 26b03c328941851984c257b48efe6ffd08a24396 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 05:39:24 +0900 Subject: [PATCH 103/296] fix(longitudinal): preserve standardized diffusion across stationary range limits --- crates/longitudinal_core/src/diffusion.rs | 60 +++++++++++------------ 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/crates/longitudinal_core/src/diffusion.rs b/crates/longitudinal_core/src/diffusion.rs index 93c48d94f..7c052a119 100644 --- a/crates/longitudinal_core/src/diffusion.rs +++ b/crates/longitudinal_core/src/diffusion.rs @@ -9,42 +9,40 @@ //! research-candidate extensions, not canonical ctsem output and not a DSEM or //! ctsem estimator. -use crate::{EventTimeInterval, LongitudinalError, stationary::recover_stationary_within_variance}; +use crate::{EventTimeInterval, LongitudinalError, stationary::validate_stationary_process_inputs}; /// Recover the scalar research-candidate `DIFFUSIONstd = q / p` map. /// /// `q` is the continuous diffusion variance-rate input and `p` is the strictly -/// positive stationary within-person variance `-q/(2a)`. The implementation -/// first recovers `p` to enforce the named estimand's positive-stationarity -/// admission contract. It then evaluates the algebraically identical scalar -/// ratio as `-2a` instead of dividing by the rounded binary64 representation of -/// `p`. That distinction matters for subnormal `q`/`p`: rounding `p` before -/// `q/p` can destroy the cancellation and make the standardized result depend -/// spuriously on diffusion scale. Equal numeric values still do not collapse -/// this estimand into `asymDIFFUSIONstd` or another variance standardisation. +/// positive stationary within-person variance `-q/(2a)`. For every finite +/// positive `q` and stable finite `a`, that real-valued variance exists and the +/// scalar standardisation cancels it exactly to `-2a`. The implementation +/// therefore validates the stationary process without materializing `p` as +/// binary64; an intermediate `p` below or above the `f64` range must not erase +/// a representable final standardized ratio. Equal numeric values still do not +/// collapse this estimand into `asymDIFFUSIONstd` or another variance +/// standardisation. /// /// # Errors /// /// Returns [`LongitudinalError::InvalidTemporalTransformInput`] for non-finite -/// inputs, negative diffusion, a non-representable stationary variance, or a -/// non-representable final `-2a` ratio. Returns +/// inputs, negative diffusion, or a non-representable final `-2a` ratio. Returns /// [`LongitudinalError::StationaryVarianceRequiresStableDrift`] unless `a < 0`. /// Returns [`LongitudinalError::StandardisedDiffusionRequiresPositiveWithinVariance`] -/// when the stationary within-person variance is zero or underflows to zero. +/// when continuous diffusion is exactly zero, because the stationary variance +/// is then zero rather than merely outside the binary64 range. pub fn recover_event_time_standardised_continuous_diffusion( continuous_diffusion: f64, log_rate: f64, ) -> Result { - let stationary = recover_stationary_within_variance(continuous_diffusion, log_rate)?; - if stationary <= 0.0 { + validate_stationary_process_inputs(continuous_diffusion, log_rate)?; + if continuous_diffusion == 0.0 { return Err(LongitudinalError::StandardisedDiffusionRequiresPositiveWithinVariance); } - // Algebraically q / (-q/(2a)) == -2a for every positive q. Evaluating - // q / rounded(p) is numerically wrong when q and p are subnormal because - // the rounded stationary variance no longer preserves that cancellation. - // The admission gate has already established finite log_rate < 0, so the - // only possible invalid result here is overflow to +infinity. + // Algebraically q / (-q/(2a)) == -2a for every positive q. The stationary + // process admission above establishes that cancellation in the real-valued + // model without requiring the cancelled p itself to fit in binary64. let ratio = -2.0 * log_rate; if !ratio.is_finite() { return Err(LongitudinalError::InvalidTemporalTransformInput); @@ -57,12 +55,12 @@ pub fn recover_event_time_standardised_continuous_diffusion( /// For a stable scalar continuous-time process, dividing discrete process noise /// over an event interval by stationary within-person variance yields /// `1 - exp(2 a delta)`. The implementation evaluates this ratio directly with -/// `exp_m1` after independently proving that positive stationary variance -/// exists. This avoids multiplying by `p` only to divide by `p` again, which can -/// overflow even when the final standardized ratio is representable. The -/// [`EventTimeInterval`] value object prevents measurement occasion, document, -/// assertion, system, or availability durations from being passed as event time -/// accidentally. +/// `exp_m1` after proving positive stationary variance algebraically from finite +/// positive diffusion and stable drift. It does not materialize the cancelled +/// stationary variance, so a below-range or above-range `p` cannot reject a +/// representable final ratio. The [`EventTimeInterval`] value object prevents +/// measurement occasion, document, assertion, system, or availability durations +/// from being passed as event time accidentally. /// /// # Errors /// @@ -71,14 +69,14 @@ pub fn recover_event_time_standardised_continuous_diffusion( /// zero, or a non-representable final ratio. Returns /// [`LongitudinalError::StationaryVarianceRequiresStableDrift`] unless `a < 0`. /// Returns [`LongitudinalError::StandardisedDiffusionRequiresPositiveWithinVariance`] -/// when the stationary within-person variance is zero or underflows to zero. +/// when continuous diffusion is exactly zero. pub fn recover_event_time_standardised_discrete_diffusion( continuous_diffusion: f64, log_rate: f64, event_interval: EventTimeInterval, ) -> Result { - let stationary = recover_stationary_within_variance(continuous_diffusion, log_rate)?; - if stationary <= 0.0 { + validate_stationary_process_inputs(continuous_diffusion, log_rate)?; + if continuous_diffusion == 0.0 { return Err(LongitudinalError::StandardisedDiffusionRequiresPositiveWithinVariance); } @@ -218,12 +216,14 @@ mod tests { interval, ) .expect("Q_delta/p cancels the unrepresentable stationary intermediate"); - assert!((discrete_underflow - -(-2.0_f64).exp_m1()).abs() <= f64::EPSILON); + let underflow_truth = -(-2.0_f64).exp_m1(); + assert!((discrete_underflow - underflow_truth).abs() <= f64::EPSILON); let discrete_overflow = recover_event_time_standardised_discrete_diffusion(f64::MAX, -0.25, interval) .expect("finite standardized discrete diffusion must survive p overflow"); - assert!((discrete_overflow - -(-0.5_f64).exp_m1()).abs() <= f64::EPSILON); + let overflow_truth = -(-0.5_f64).exp_m1(); + assert!((discrete_overflow - overflow_truth).abs() <= f64::EPSILON); } #[test] From c88810dc1f7b1291b5ced8f5d4ffd92eb9f524c3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 05:39:37 +0900 Subject: [PATCH 104/296] docs(prd): clarify cancelled stationary standardisation --- docs/product/prd-v0.4-amendment-longitudinal-time-ownership.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/product/prd-v0.4-amendment-longitudinal-time-ownership.md b/docs/product/prd-v0.4-amendment-longitudinal-time-ownership.md index 67bc330bd..7dcdb50fb 100644 --- a/docs/product/prd-v0.4-amendment-longitudinal-time-ownership.md +++ b/docs/product/prd-v0.4-amendment-longitudinal-time-ownership.md @@ -13,7 +13,7 @@ For event-time lagged association, the product contract requires lagged covarian CWC-then-irregular residual log-rate is Longitudinal Modeling composition, not a generic psychometric kernel. Consecutive unit-mean-centered pairs keep typed event-time intervals. The pairwise mean of the Driver, Oud, and Voelkle (2017, Eq. 3) scalar inverse on nonzero same-sign residuals is not raw-process drift (Curran & Bauer, 2011, Eq. 36). Already-centered irregular pairs may recover that inverse, including `ln(0.5)`. This is not DSEM and not Newton least-squares. -For the scalar stationary Driver, Oud, and Voelkle (2017) p. 16 `discreteDRIFTstd` mapping, TEPP may expose a deterministic event-time transform only when stable drift and positive stationary within-person variance are established. Numerical equality with `exp(a Δt)` in that scalar stationary special case does not make unstandardised `discreteDRIFT` and `discreteDRIFTstd` the same estimand. +For the scalar stationary Driver, Oud, and Voelkle (2017) p. 16 `discreteDRIFTstd` mapping, TEPP may expose a deterministic event-time transform only when stable drift and positive stationary within-person variance are established. Numerical equality with `exp(a Δt)` in that scalar stationary special case does not make unstandardised `discreteDRIFT` and `discreteDRIFTstd` the same estimand. When the stationary variance cancels algebraically from a standardized scalar map, positive real-valued stationarity is established from finite positive diffusion and stable finite drift; the cancelled variance is not required to fit in binary64 if the final standardized result is representable. Conversely, a nonzero exact transition that rounds to a false endpoint such as `1.0` or `0.0` remains fail-closed rather than being reported as no change or complete dissipation. Reusable static/generalized-mixed/dependence-aware psychometric arithmetic remains owned by `ContextualWisdomLab/fast-mlsirm`; TEPP consumes versioned contracts through an anti-corruption layer and does not retain duplicate production kernels. LLM execution remains `contextual-orchestrator`-owned and cannot substitute for numerical estimation or scientific acceptance. From 37b78ce15b93cb25e6307df445e58e3d3bf48954 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 05:39:57 +0900 Subject: [PATCH 105/296] docs(adr): separate stationary existence from f64 materialization --- docs/adr/longitudinal-modeling-ownership-addendum.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/adr/longitudinal-modeling-ownership-addendum.md b/docs/adr/longitudinal-modeling-ownership-addendum.md index 8383152db..8eef213c6 100644 --- a/docs/adr/longitudinal-modeling-ownership-addendum.md +++ b/docs/adr/longitudinal-modeling-ownership-addendum.md @@ -14,7 +14,9 @@ Temporal/event composition of longitudinal psychometric quantities belongs to th `longitudinal_core` owns this TEPP temporal composition. `fast-mlsirm` remains the canonical owner of reusable static/generalized-mixed/dependence-aware psychometric kernels. A numerically reusable primitive must migrate through the fast-mlsirm Published Language/ACL boundary rather than turning `longitudinal_core` into a second static psychometric kernel. -For the scalar Driver, Oud, and Voelkle (2017) p. 16 `discreteDRIFTstd` special case, TEPP owns only the event-time composition and admissibility policy. The function requires stable negative drift, positive stationary within-person variance, and an admitted `EventTimeInterval`. It does not estimate ctsem/DSEM parameters, state trajectories, process noise, or uncertainty. +For the scalar Driver, Oud, and Voelkle (2017) p. 16 `discreteDRIFTstd` special case, TEPP owns only the event-time composition and admissibility policy. The function requires stable negative drift, positive stationary within-person variance, and an admitted `EventTimeInterval`. In this scalar standardisation the stationary variance cancels algebraically: finite positive diffusion plus stable finite drift establish a positive real-valued stationary variance even when its materialized `f64` value would underflow or overflow. The cancelled intermediate therefore must not reject a representable final standardized map. An exact nonzero transition that itself collapses to a false binary64 endpoint remains fail-closed. + +The same numerical rule applies to TEPP's research-candidate scalar diffusion-standardisation compositions: a cancelled stationary variance is not a second admission gate, while the final standardized quantity must remain representable. This does not promote those named diffusion extensions to canonical ctsem output and does not move reusable static arithmetic ownership out of fast-mlsirm. The lagged-correlation boundary similarly requires both occasion-specific marginal variances and an `EventTimeInterval`; a covariance divided only by the earlier variance is not exposed as an autocorrelation. @@ -29,6 +31,6 @@ CWC-then-irregular residual log-rate is also Longitudinal Modeling composition. ## Verification -PR #310 is the current landing vehicle for this clarification. Its RED lineage includes an extreme stable-drift case that failed because `-2a` overflowed despite a representable stationary variance, and a typed event-time contract that could not compile before the value object existed. The repaired source avoids the unnecessary doubling overflow and moves public event-time admission behind `EventTimeInterval`. The #327 fold adds CWC-then-pairwise-mean residual log-rate with Curran refusal and known-truth already-centered recovery of `ln(0.5)` at `Δt = 1`; arbitrary admitted intervals recover the exact `ln(0.5) / Δt` map. +PR #310 is the current landing vehicle for this clarification. Its RED lineage includes an extreme stable-drift case that failed because `-2a` overflowed despite a representable stationary variance, a cancelled-stationary case in which the final scalar standardisation is representable even though `q / (-2a)` itself lies outside binary64 range, and a typed event-time contract that could not compile before the value object existed. The repaired source keeps actual stationary-variance recovery fail-closed when `p` itself is requested, but standardized scalar maps validate stationarity algebraically and avoid materializing an intermediate that cancels. The #327 fold adds CWC-then-pairwise-mean residual log-rate with Curran refusal and known-truth already-centered recovery of `ln(0.5)` at `Δt = 1`; arbitrary admitted intervals recover the exact `ln(0.5) / Δt` map. Protected-main maturity is not claimed until the exact landing head passes the live ruleset and is merged. From a8de3c9f924bc6a942e385324d00ce4b6d30412b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 05:45:26 +0900 Subject: [PATCH 106/296] test(longitudinal): reject false unit diffusion endpoints --- crates/longitudinal_core/src/diffusion.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/crates/longitudinal_core/src/diffusion.rs b/crates/longitudinal_core/src/diffusion.rs index 7c052a119..c6d0e7b93 100644 --- a/crates/longitudinal_core/src/diffusion.rs +++ b/crates/longitudinal_core/src/diffusion.rs @@ -189,11 +189,18 @@ mod tests { } #[test] - fn very_large_stable_event_product_has_representable_unit_limit() { - let interval = EventTimeInterval::new(f64::MAX).expect("finite positive interval"); - let recovered = recover_event_time_standardised_discrete_diffusion(1.0, -1.0, interval) - .expect("the final standardized noise fraction tends to one"); - assert_eq!(recovered, 1.0); + fn finite_event_interval_must_not_collapse_to_false_unit_diffusion() { + let huge = EventTimeInterval::new(f64::MAX).expect("finite positive interval"); + assert_eq!( + recover_event_time_standardised_discrete_diffusion(1.0, -1.0, huge), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); + + let finite_saturating = EventTimeInterval::new(50.0).expect("finite event interval"); + assert_eq!( + recover_event_time_standardised_discrete_diffusion(1.0, -1.0, finite_saturating), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); } #[test] From c17e2ff87fa8ed6464ca07152770c149573d55a6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 05:45:57 +0900 Subject: [PATCH 107/296] fix(longitudinal): fail closed on false unit diffusion endpoints --- crates/longitudinal_core/src/diffusion.rs | 28 ++++++++++------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/crates/longitudinal_core/src/diffusion.rs b/crates/longitudinal_core/src/diffusion.rs index c6d0e7b93..70987d8fc 100644 --- a/crates/longitudinal_core/src/diffusion.rs +++ b/crates/longitudinal_core/src/diffusion.rs @@ -66,7 +66,8 @@ pub fn recover_event_time_standardised_continuous_diffusion( /// /// Returns [`LongitudinalError::InvalidTemporalTransformInput`] for invalid /// diffusion/drift, a doubled event-time exponent that underflows to signed -/// zero, or a non-representable final ratio. Returns +/// zero or overflows, or a finite-interval final ratio that collapses to the +/// false binary64 endpoints zero or one. Returns /// [`LongitudinalError::StationaryVarianceRequiresStableDrift`] unless `a < 0`. /// Returns [`LongitudinalError::StandardisedDiffusionRequiresPositiveWithinVariance`] /// when continuous diffusion is exactly zero. @@ -91,32 +92,27 @@ pub fn recover_event_time_standardised_discrete_diffusion( } else { // If 2Δ overflows, Δ is already enormous. Form aΔ first and then apply // the exact factor two; this branch cannot suffer the tiny-interval - // underflow that motivated the primary ordering above. + // underflow that motivated the primary ordering above. A non-finite + // product cannot be reported as the exact finite-interval endpoint one. let half_exponent = log_rate * interval; - if half_exponent == f64::NEG_INFINITY { - return Ok(1.0); - } if !half_exponent.is_finite() { return Err(LongitudinalError::InvalidTemporalTransformInput); } half_exponent * 2.0 }; - // The target exponent is exactly 2aΔ with finite a < 0 and Δ > 0. If that - // target itself rounds to signed zero, then 1-exp(2aΔ) is also below the - // minimum representable positive binary64 result and must fail closed. - if exponent == 0.0 { - return Err(LongitudinalError::InvalidTemporalTransformInput); - } - if exponent == f64::NEG_INFINITY { - return Ok(1.0); - } - if !exponent.is_finite() { + // For finite stable a and finite positive Δ, the exact 2aΔ is finite and + // strictly negative. Signed zero or infinity therefore means the target + // exponent itself is not representable in binary64 and must fail closed. + if !exponent.is_finite() || exponent == 0.0 { return Err(LongitudinalError::InvalidTemporalTransformInput); } let ratio = -exponent.exp_m1(); - if !ratio.is_finite() || ratio <= 0.0 || ratio > 1.0 { + // The exact finite-interval ratio is strictly inside (0, 1). If exp_m1 + // rounds it to either endpoint, reporting zero/one would erase a nonzero + // remainder and turn a numerical limitation into a scientific boundary. + if !ratio.is_finite() || ratio <= 0.0 || ratio >= 1.0 { return Err(LongitudinalError::InvalidTemporalTransformInput); } Ok(ratio) From 27d9fa39f5d4d31fde168f93014f32cea81448c8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 05:59:29 +0900 Subject: [PATCH 108/296] test(longitudinal): reject unrepresentable positive stationary variance --- crates/longitudinal_core/src/stationary.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/longitudinal_core/src/stationary.rs b/crates/longitudinal_core/src/stationary.rs index 4a6d52bf9..0cdf54932 100644 --- a/crates/longitudinal_core/src/stationary.rs +++ b/crates/longitudinal_core/src/stationary.rs @@ -101,6 +101,15 @@ mod tests { assert_eq!(stationary.to_bits(), 1); } + #[test] + fn positive_stationary_variance_below_binary64_range_fails_closed() { + let minimum_subnormal = f64::from_bits(1); + assert_eq!( + recover_stationary_within_variance(minimum_subnormal, -1.0), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); + } + #[test] fn stationary_variance_admission_is_fail_closed() { assert_eq!(recover_stationary_within_variance(0.0, -0.5), Ok(0.0)); From a0132b62cb30acfcb6aa0a6ab96b0d6d3c6b1d3c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 05:59:56 +0900 Subject: [PATCH 109/296] fix(longitudinal): fail closed on stationary-variance underflow --- crates/longitudinal_core/src/stationary.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/longitudinal_core/src/stationary.rs b/crates/longitudinal_core/src/stationary.rs index 0cdf54932..59238b6f4 100644 --- a/crates/longitudinal_core/src/stationary.rs +++ b/crates/longitudinal_core/src/stationary.rs @@ -51,7 +51,13 @@ pub(crate) fn recover_stationary_within_variance( // is necessarily far below the minimum representable positive value. (continuous_diffusion * 0.5) / -log_rate }; - if !stationary.is_finite() { + // Positive q with stable finite a implies strictly positive real p. If the + // binary64 evaluation collapses that requested estimand to zero, zero is a + // false scientific boundary rather than a representable stationary + // variance. Standardized callers that algebraically cancel p use the input + // validator above and therefore are not rejected by this materialization + // rule. + if !stationary.is_finite() || stationary == 0.0 { return Err(LongitudinalError::InvalidTemporalTransformInput); } Ok(stationary) From 75b0184d2f6341ef23cf14fc84398c68d8d95d22 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 07:59:42 +0900 Subject: [PATCH 110/296] test(longitudinal): expose occasion-mean event-time regressions --- .../occasion_mean_event_time_contract.rs | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 crates/longitudinal_core/tests/occasion_mean_event_time_contract.rs diff --git a/crates/longitudinal_core/tests/occasion_mean_event_time_contract.rs b/crates/longitudinal_core/tests/occasion_mean_event_time_contract.rs new file mode 100644 index 000000000..af4857451 --- /dev/null +++ b/crates/longitudinal_core/tests/occasion_mean_event_time_contract.rs @@ -0,0 +1,69 @@ +use longitudinal_core::{ + EventTimedObservation, LongitudinalError, center_occasion_mean_event_lags, + recover_occasion_mean_centered_irregular_residual_log_rate, + refuse_occasion_mean_centered_log_rate_as_within_person_lag, +}; + +fn observation(unit: u32, event_time: f64, score: f64) -> EventTimedObservation { + EventTimedObservation::new(unit, event_time, score) +} + +#[test] +fn signed_zero_is_one_numeric_occasion() { + let drift = -0.5_f64; + let phi = drift.exp(); + let rows = [ + observation(1, -0.0, 1.0), + observation(1, 1.0, phi), + observation(2, 0.0, -1.0), + observation(2, 1.0, -phi), + ]; + + let pairs = center_occasion_mean_event_lags(&rows).expect("numeric zero is one occasion"); + assert_eq!(pairs.len(), 2); + + let recovered = recover_occasion_mean_centered_irregular_residual_log_rate(&rows) + .expect("recover occasion-mean residual rate"); + assert!((recovered - drift).abs() < 1.0e-12); + assert_eq!( + refuse_occasion_mean_centered_log_rate_as_within_person_lag(recovered), + Err(LongitudinalError::BetweenIsNotWithinChange) + ); +} + +#[test] +fn signed_zero_duplicate_unit_is_rejected_as_one_occasion() { + let rows = [ + observation(1, -0.0, 1.0), + observation(1, 0.0, 1.5), + observation(2, 0.0, -1.0), + observation(2, 1.0, -0.5), + ]; + + assert_eq!( + center_occasion_mean_event_lags(&rows), + Err(LongitudinalError::InvalidObservationPayload) + ); +} + +#[test] +fn representable_occasion_mean_is_not_rejected_for_intermediate_sum_overflow() { + let max = f64::MAX; + let rows = [ + observation(1, 0.0, 0.75 * max), + observation(1, 1.0, 1.0), + observation(2, 0.0, 0.75 * max), + observation(2, 1.0, 1.0), + observation(3, 0.0, -0.5 * max), + observation(3, 1.0, -2.0), + ]; + + let pairs = center_occasion_mean_event_lags(&rows) + .expect("finite occasion mean must survive same-sign intermediate overflow"); + assert_eq!(pairs.len(), 3); + assert!(pairs.iter().all(|pair| { + pair.earlier_residual().is_finite() + && pair.later_residual().is_finite() + && pair.event_interval().get().is_finite() + })); +} From 7fe9aaf2570ffb6ecff3d6a83b12a30865fc198b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 08:00:25 +0900 Subject: [PATCH 111/296] fix(longitudinal): own occasion-mean event-time composition --- crates/longitudinal_core/src/occasion_mean.rs | 235 ++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 crates/longitudinal_core/src/occasion_mean.rs diff --git a/crates/longitudinal_core/src/occasion_mean.rs b/crates/longitudinal_core/src/occasion_mean.rs new file mode 100644 index 000000000..6fca420a7 --- /dev/null +++ b/crates/longitudinal_core/src/occasion_mean.rs @@ -0,0 +1,235 @@ +//! Occasion-mean event-time composition for longitudinal modeling. +//! +//! Hamaker, Kuiper, and Grasman (2015, Eq. 1a) decompose an observed score as +//! `x_it = mu_t + p_it`, where `mu_t` is the occasion-specific group mean. +//! These deviations are distinct from person-mean CWC residuals and still +//! contain stable between-person differences, so they are not within-person +//! effects and are not RI-CLPM residuals. + +use std::collections::{BTreeMap, BTreeSet}; + +use crate::irregular_residual::{EventTimedObservation, LaggedWithinResidual}; +use crate::stable_irregular_rate::recover_centered_irregular_residual_log_rate; +use crate::{EventTimeInterval, LongitudinalError}; + +/// Form consecutive event-time lags after subtracting each occasion's group mean. +/// +/// Numeric event-time identity is used for occasion membership. In particular, +/// `-0.0` and `+0.0` are one occasion rather than two binary encodings. Every +/// admitted occasion must contain at least two distinct units, and at least two +/// units must contribute a consecutive lag. Occasion means are computed without +/// allowing an overflowing same-sign partial sum to reject a representable mean. +/// +/// # Errors +/// +/// Returns [`LongitudinalError::InvalidObservationPayload`] for empty, sparse, +/// duplicated unit-occasion, or non-finite observations, and +/// [`LongitudinalError::NonPositiveEventInterval`] when a unit's consecutive +/// event times do not form a finite strictly positive interval. +pub fn center_occasion_mean_event_lags( + rows: &[EventTimedObservation], +) -> Result, LongitudinalError> { + if rows.len() < 2 { + return Err(LongitudinalError::InvalidObservationPayload); + } + + let mut by_time: BTreeMap> = BTreeMap::new(); + let mut by_unit: BTreeMap> = BTreeMap::new(); + for &row in rows { + if !row.event_time().is_finite() || !row.score().is_finite() { + return Err(LongitudinalError::InvalidObservationPayload); + } + by_time + .entry(canonical_event_time_key(row.event_time())) + .or_default() + .push(row); + by_unit.entry(row.unit_index()).or_default().push(row); + } + + let lag_contributing_units = by_unit + .values() + .filter(|occasions| occasions.len() >= 2) + .count(); + if lag_contributing_units < 2 { + return Err(LongitudinalError::InvalidObservationPayload); + } + + let mut occasion_means = BTreeMap::new(); + for (&time_key, occasion_rows) in &by_time { + let mut seen_units = BTreeSet::new(); + let mut scores = Vec::with_capacity(occasion_rows.len()); + for row in occasion_rows { + if !seen_units.insert(row.unit_index()) { + return Err(LongitudinalError::InvalidObservationPayload); + } + scores.push(row.score()); + } + if seen_units.len() < 2 { + return Err(LongitudinalError::InvalidObservationPayload); + } + occasion_means.insert(time_key, occasion_mean(&scores)?); + } + + let mut pairs = Vec::new(); + for occasions in by_unit.values_mut() { + if occasions.len() < 2 { + continue; + } + occasions.sort_by(|left, right| left.event_time().total_cmp(&right.event_time())); + for window in occasions.windows(2) { + let earlier = window[0]; + let later = window[1]; + let earlier_mean = occasion_means[&canonical_event_time_key(earlier.event_time())]; + let later_mean = occasion_means[&canonical_event_time_key(later.event_time())]; + let earlier_residual = earlier.score() - earlier_mean; + let later_residual = later.score() - later_mean; + if !earlier_residual.is_finite() || !later_residual.is_finite() { + return Err(LongitudinalError::InvalidObservationPayload); + } + let event_interval = EventTimeInterval::new(later.event_time() - earlier.event_time())?; + pairs.push(LaggedWithinResidual::new( + earlier_residual, + later_residual, + event_interval, + )); + } + } + + if pairs.is_empty() { + return Err(LongitudinalError::InvalidObservationPayload); + } + Ok(pairs) +} + +/// Recover the mean exact scalar log-rate of occasion-mean residuals. +/// +/// This composes [`center_occasion_mean_event_lags`] with the existing +/// longitudinal exact-log-rate boundary. It is an event-time association of +/// Hamaker Eq. 1a deviations, not a within-person autoregressive effect. +/// +/// # Errors +/// +/// Propagates centering and exact-log-rate admission failures. +pub fn recover_occasion_mean_centered_irregular_residual_log_rate( + rows: &[EventTimedObservation], +) -> Result { + let pairs = center_occasion_mean_event_lags(rows)?; + recover_centered_irregular_residual_log_rate(&pairs) +} + +/// Refuse treating an occasion-mean residual log-rate as within-person change. +/// +/// Hamaker Eq. 1a deviations retain between-person differences unless a +/// person-specific stable component is removed by a model that identifies it. +/// +/// # Errors +/// +/// Always returns [`LongitudinalError::BetweenIsNotWithinChange`]. +pub fn refuse_occasion_mean_centered_log_rate_as_within_person_lag( + log_rate: f64, +) -> Result { + let _ = log_rate; + Err(LongitudinalError::BetweenIsNotWithinChange) +} + +fn canonical_event_time_key(event_time: f64) -> u64 { + if event_time == 0.0 { + 0.0_f64.to_bits() + } else { + event_time.to_bits() + } +} + +fn occasion_mean(values: &[f64]) -> Result { + if values.is_empty() || values.iter().any(|value| !value.is_finite()) { + return Err(LongitudinalError::InvalidObservationPayload); + } + + let mut positives = Vec::new(); + let mut negatives = Vec::new(); + for &value in values { + if value > 0.0 { + positives.push(value); + } else if value < 0.0 { + negatives.push(value); + } + } + + if positives.is_empty() || negatives.is_empty() { + return same_sign_mean(values); + } + + positives.sort_by(|left, right| right.total_cmp(left)); + negatives.sort_by(|left, right| left.total_cmp(right)); + + let mut positive_index = 0_usize; + let mut negative_index = 0_usize; + let mut positive = positives[0]; + let mut negative = negatives[0]; + let mut residuals = Vec::with_capacity(values.len()); + + loop { + let residual = positive + negative; + if residual > 0.0 { + positive = residual; + negative_index += 1; + if negative_index == negatives.len() { + residuals.push(positive); + residuals.extend_from_slice(&positives[positive_index + 1..]); + break; + } + negative = negatives[negative_index]; + } else if residual < 0.0 { + negative = residual; + positive_index += 1; + if positive_index == positives.len() { + residuals.push(negative); + residuals.extend_from_slice(&negatives[negative_index + 1..]); + break; + } + positive = positives[positive_index]; + } else { + positive_index += 1; + negative_index += 1; + if positive_index == positives.len() || negative_index == negatives.len() { + residuals.extend_from_slice(&positives[positive_index..]); + residuals.extend_from_slice(&negatives[negative_index..]); + break; + } + positive = positives[positive_index]; + negative = negatives[negative_index]; + } + } + + if residuals.is_empty() { + return Ok(0.0); + } + + let residual_mean = same_sign_mean(&residuals)?; + let retained_count = residuals.len() as f64; + let total_count = values.len() as f64; + let retained_mass = residual_mean * retained_count; + let mean = if retained_mass.is_finite() { + retained_mass / total_count + } else { + (residual_mean / total_count) * retained_count + }; + if mean.is_finite() { + Ok(mean) + } else { + Err(LongitudinalError::InvalidObservationPayload) + } +} + +fn same_sign_mean(values: &[f64]) -> Result { + let mut mean = 0.0_f64; + for (index, &value) in values.iter().enumerate() { + let count = (index + 1) as f64; + mean += (value - mean) / count; + } + if mean.is_finite() { + Ok(mean) + } else { + Err(LongitudinalError::InvalidObservationPayload) + } +} From b900e21301f1f5bb769464a4b76da9088cd669ab Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 08:00:45 +0900 Subject: [PATCH 112/296] fix(longitudinal): publish occasion-mean composition boundary --- crates/longitudinal_core/src/lib.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/longitudinal_core/src/lib.rs b/crates/longitudinal_core/src/lib.rs index d47dd0acf..6eaa45297 100644 --- a/crates/longitudinal_core/src/lib.rs +++ b/crates/longitudinal_core/src/lib.rs @@ -20,6 +20,7 @@ mod error; mod event_time; mod irregular_residual; mod level; +mod occasion_mean; mod stable_irregular_rate; mod stationary; mod temporal_association; @@ -62,6 +63,12 @@ pub use irregular_residual::LaggedWithinResidual; pub use irregular_residual::center_within_unit_event_lags; /// Refuse treating a CWC residual log-rate as raw-process AR drift. pub use irregular_residual::refuse_cwc_residual_log_rate_as_raw_process_drift; +/// Form consecutive event-time lags after subtracting each occasion's group mean. +pub use occasion_mean::center_occasion_mean_event_lags; +/// Recover the exact scalar log-rate of occasion-mean residuals. +pub use occasion_mean::recover_occasion_mean_centered_irregular_residual_log_rate; +/// Refuse treating occasion-mean residual lag as within-person change. +pub use occasion_mean::refuse_occasion_mean_centered_log_rate_as_within_person_lag; /// Established longitudinal component level. pub use level::ComponentLevel; /// Refuse to treat a between-unit component as within-unit change. From 30771ff24cf85479eb5ed227789b59489ac7ead2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 08:00:58 +0900 Subject: [PATCH 113/296] test(longitudinal): assert admitted event interval accessor --- .../tests/occasion_mean_event_time_contract.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/longitudinal_core/tests/occasion_mean_event_time_contract.rs b/crates/longitudinal_core/tests/occasion_mean_event_time_contract.rs index af4857451..97b31f9c4 100644 --- a/crates/longitudinal_core/tests/occasion_mean_event_time_contract.rs +++ b/crates/longitudinal_core/tests/occasion_mean_event_time_contract.rs @@ -64,6 +64,6 @@ fn representable_occasion_mean_is_not_rejected_for_intermediate_sum_overflow() { assert!(pairs.iter().all(|pair| { pair.earlier_residual().is_finite() && pair.later_residual().is_finite() - && pair.event_interval().get().is_finite() + && pair.event_interval().as_f64().is_finite() })); } From 7baff4c99473b1de035386e4a5055a4fe71edca0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 08:04:45 +0900 Subject: [PATCH 114/296] docs(longitudinal): trace occasion-mean event-time composition --- .../occasion-mean-event-time-composition.md | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 docs/research/occasion-mean-event-time-composition.md diff --git a/docs/research/occasion-mean-event-time-composition.md b/docs/research/occasion-mean-event-time-composition.md new file mode 100644 index 000000000..268a5fe34 --- /dev/null +++ b/docs/research/occasion-mean-event-time-composition.md @@ -0,0 +1,42 @@ +# Occasion-mean event-time composition + +Status: active PR evidence on #310; not protected-main or release evidence. + +## Scientific claim + +Hamaker, Kuiper, and Grasman (2015, Eq. 1a) write an observed score as an occasion-specific mean plus a deviation, `x_it = μ_t + p_it`. In TEPP, `μ_t` is the group mean for one numeric event-time occasion and `p_it = x_it - μ_t`. This is distinct from person/unit-mean centering (CWC), a sample-wide grand mean, and person-specific detrending. + +The resulting `p_it` deviations are not, by themselves, within-person effects. Stable between-person differences can remain in them; Hamaker et al.'s random-intercept extension removes a person-specific stable component before interpreting within-person dynamics. TEPP therefore refuses promoting the occasion-mean residual log-rate to a within-person lag estimand. + +Aligned occasions may be irregularly spaced. Each consecutive unit-specific pair retains its own positive finite `EventTimeInterval`; unequal event intervals are not pooled as one discrete coefficient. + +## Admission and numerical invariants + +`longitudinal_core::center_occasion_mean_event_lags` owns the temporal composition boundary. + +- Occasion identity is numeric event time. IEEE-754 `-0.0` and `+0.0` are one occasion, not two bit-pattern identities. +- A unit may contribute at most one observation to one numeric occasion. +- Each admitted occasion contains at least two distinct units, and at least two units contribute consecutive lags. This prevents an atomistic one-unit series from manufacturing multilevel evidence. +- Scores and event times must be finite. Consecutive event intervals must be finite and strictly positive. +- A representable occasion mean is not rejected merely because a same-sign intermediate partial sum would overflow binary64. The mean path preserves cancellation before bounded same-sign averaging and still fails closed if the final mean or centered residual is non-representable. +- `recover_occasion_mean_centered_irregular_residual_log_rate` composes the centered pairs with the existing Longitudinal Modeling exact-log-rate boundary. It does not create a second static psychometric arithmetic authority. + +## Test trace + +Current #310 branch tests: + +- `crates/longitudinal_core/tests/occasion_mean_event_time_contract.rs::signed_zero_is_one_numeric_occasion` — `-0.0`/`+0.0` share one occasion and the known scalar event-time log-rate is recovered. +- `signed_zero_duplicate_unit_is_rejected_as_one_occasion` — the same unit cannot use signed zero to bypass duplicate occasion admission. +- `representable_occasion_mean_is_not_rejected_for_intermediate_sum_overflow` — `[0.75·MAX, 0.75·MAX, -0.5·MAX]` at one occasion retains a finite representable mean and finite centered residuals rather than failing on the naive partial sum. + +The RED commit introducing these contracts is `75b0184d2f6341ef23cf14fc84398c68d8d95d22`. The owner-correct implementation is in `crates/longitudinal_core/src/occasion_mean.rs`; the current branch head after the public export/test correction must be used for merge evidence rather than any predecessor SHA. + +## Claim boundary + +This slice is an occasion-mean residual event-time association. It is not RI-CLPM, DSEM, a causal effect, or proof of within-person dynamics. LLM output does not estimate or activate this quantity. Reusable static psychometric arithmetic remains fast-mlsirm-owned and may be consumed only through an immutable released contract. + +## Primary source + +Hamaker, E. L., Kuiper, R. M., & Grasman, R. P. P. P. (2015). A critique of the cross-lagged panel model. *Psychological Methods, 20*(1), 102–116. https://doi.org/10.1037/a0038889 + +Repository evidence was cross-checked on 2026-09-03 against the Utrecht University/UvA-DARE final-published-version record and PubMed record (PMID 25822208). The repository record identifies the DOI, journal, volume, issue, pages, and peer-reviewed publication. No timezone-suffixed access timestamp is used as scientific evidence. From 8a59019ed3112a3e27dd0dcd1b6b86d8d45e5435 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 08:16:38 +0900 Subject: [PATCH 115/296] test(longitudinal): require permutation-stable occasion means --- .../occasion_mean_event_time_contract.rs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/crates/longitudinal_core/tests/occasion_mean_event_time_contract.rs b/crates/longitudinal_core/tests/occasion_mean_event_time_contract.rs index 97b31f9c4..9a4ba8aa8 100644 --- a/crates/longitudinal_core/tests/occasion_mean_event_time_contract.rs +++ b/crates/longitudinal_core/tests/occasion_mean_event_time_contract.rs @@ -67,3 +67,29 @@ fn representable_occasion_mean_is_not_rejected_for_intermediate_sum_overflow() { && pair.event_interval().as_f64().is_finite() })); } + +#[test] +fn occasion_mean_is_bit_stable_under_row_permutation() { + let large = f64::MAX * 0.5; + let next_one = f64::from_bits(1.0_f64.to_bits() + 1); + let rows_a = [ + observation(1, 0.0, 1.0), + observation(1, 1.0, 1.0), + observation(2, 0.0, next_one), + observation(2, 1.0, 1.0), + observation(3, 0.0, large), + observation(3, 1.0, 1.0), + ]; + let rows_b = [ + observation(1, 0.0, 1.0), + observation(1, 1.0, 1.0), + observation(3, 0.0, large), + observation(3, 1.0, 1.0), + observation(2, 0.0, next_one), + observation(2, 1.0, 1.0), + ]; + + let pairs_a = center_occasion_mean_event_lags(&rows_a).expect("first permutation"); + let pairs_b = center_occasion_mean_event_lags(&rows_b).expect("second permutation"); + assert_eq!(pairs_a, pairs_b); +} From 465d139dce6101c4958c8b0827b6ef5d674b54c2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 08:17:14 +0900 Subject: [PATCH 116/296] fix(longitudinal): make occasion means permutation-stable --- crates/longitudinal_core/src/occasion_mean.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/longitudinal_core/src/occasion_mean.rs b/crates/longitudinal_core/src/occasion_mean.rs index 6fca420a7..71083b5eb 100644 --- a/crates/longitudinal_core/src/occasion_mean.rs +++ b/crates/longitudinal_core/src/occasion_mean.rs @@ -18,7 +18,8 @@ use crate::{EventTimeInterval, LongitudinalError}; /// `-0.0` and `+0.0` are one occasion rather than two binary encodings. Every /// admitted occasion must contain at least two distinct units, and at least two /// units must contribute a consecutive lag. Occasion means are computed without -/// allowing an overflowing same-sign partial sum to reject a representable mean. +/// allowing an overflowing same-sign partial sum to reject a representable mean +/// and are bit-stable under row permutation. /// /// # Errors /// @@ -222,8 +223,11 @@ fn occasion_mean(values: &[f64]) -> Result { } fn same_sign_mean(values: &[f64]) -> Result { + let mut ordered = values.to_vec(); + ordered.sort_by(f64::total_cmp); + let mut mean = 0.0_f64; - for (index, &value) in values.iter().enumerate() { + for (index, value) in ordered.into_iter().enumerate() { let count = (index + 1) as f64; mean += (value - mean) / count; } From 7269cd78b0e0843946677ee50fdc09847e19af83 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 08:17:41 +0900 Subject: [PATCH 117/296] docs(longitudinal): trace permutation-stable occasion means --- docs/research/occasion-mean-event-time-composition.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/research/occasion-mean-event-time-composition.md b/docs/research/occasion-mean-event-time-composition.md index 268a5fe34..8e1a96c50 100644 --- a/docs/research/occasion-mean-event-time-composition.md +++ b/docs/research/occasion-mean-event-time-composition.md @@ -19,6 +19,7 @@ Aligned occasions may be irregularly spaced. Each consecutive unit-specific pair - Each admitted occasion contains at least two distinct units, and at least two units contribute consecutive lags. This prevents an atomistic one-unit series from manufacturing multilevel evidence. - Scores and event times must be finite. Consecutive event intervals must be finite and strictly positive. - A representable occasion mean is not rejected merely because a same-sign intermediate partial sum would overflow binary64. The mean path preserves cancellation before bounded same-sign averaging and still fails closed if the final mean or centered residual is non-representable. +- Occasion means are bit-stable under permutation of the same admitted rows. Same-sign retained values are put in a deterministic total order before incremental averaging; input arrival order is not scientific evidence. - `recover_occasion_mean_centered_irregular_residual_log_rate` composes the centered pairs with the existing Longitudinal Modeling exact-log-rate boundary. It does not create a second static psychometric arithmetic authority. ## Test trace @@ -28,8 +29,9 @@ Current #310 branch tests: - `crates/longitudinal_core/tests/occasion_mean_event_time_contract.rs::signed_zero_is_one_numeric_occasion` — `-0.0`/`+0.0` share one occasion and the known scalar event-time log-rate is recovered. - `signed_zero_duplicate_unit_is_rejected_as_one_occasion` — the same unit cannot use signed zero to bypass duplicate occasion admission. - `representable_occasion_mean_is_not_rejected_for_intermediate_sum_overflow` — `[0.75·MAX, 0.75·MAX, -0.5·MAX]` at one occasion retains a finite representable mean and finite centered residuals rather than failing on the naive partial sum. +- `occasion_mean_is_bit_stable_under_row_permutation` — the same three-unit occasion with `{1, nextafter(1,+∞), MAX/2}` yields bit-identical centered pairs when row arrival order changes. -The RED commit introducing these contracts is `75b0184d2f6341ef23cf14fc84398c68d8d95d22`. The owner-correct implementation is in `crates/longitudinal_core/src/occasion_mean.rs`; the current branch head after the public export/test correction must be used for merge evidence rather than any predecessor SHA. +The initial occasion-composition RED is `75b0184d2f6341ef23cf14fc84398c68d8d95d22`. Deterministic-order RED `8a59019ed3112a3e27dd0dcd1b6b86d8d45e5435` exposes order-dependent same-sign averaging; causal repair `465d139dce6101c4958c8b0827b6ef5d674b54c2` orders same-sign values before averaging. The owner-correct implementation remains in `crates/longitudinal_core/src/occasion_mean.rs`. Only the current exact branch head may be used for merge evidence. ## Claim boundary From b9e952bb8a893f62aaead59cdf825b5e3c6251c6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 08:33:11 +0900 Subject: [PATCH 118/296] test(longitudinal): inherit occasion-vs-CWC claim boundary --- .../occasion_mean_event_time_contract.rs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/crates/longitudinal_core/tests/occasion_mean_event_time_contract.rs b/crates/longitudinal_core/tests/occasion_mean_event_time_contract.rs index 9a4ba8aa8..1f3218c30 100644 --- a/crates/longitudinal_core/tests/occasion_mean_event_time_contract.rs +++ b/crates/longitudinal_core/tests/occasion_mean_event_time_contract.rs @@ -1,6 +1,7 @@ use longitudinal_core::{ EventTimedObservation, LongitudinalError, center_occasion_mean_event_lags, recover_occasion_mean_centered_irregular_residual_log_rate, + recover_within_unit_irregular_residual_log_rate, refuse_occasion_mean_centered_log_rate_as_within_person_lag, }; @@ -31,6 +32,30 @@ fn signed_zero_is_one_numeric_occasion() { ); } +#[test] +fn occasion_mean_residual_rate_is_not_cwc_rate_on_the_same_panel() { + let drift = -0.4_f64; + let phi = drift.exp(); + let rows = [ + observation(1, 0.0, 1.2), + observation(1, 1.0, 5.0 + 1.2 * phi), + observation(1, 2.0, 11.0 + 1.2 * (drift * 2.0).exp()), + observation(2, 0.0, -0.8), + observation(2, 1.0, 5.0 - 0.8 * phi), + observation(2, 2.0, 11.0 - 0.8 * (drift * 2.0).exp()), + ]; + + let occasion = recover_occasion_mean_centered_irregular_residual_log_rate(&rows) + .expect("occasion-mean residual rate"); + assert!((occasion - drift).abs() < 1.0e-12); + + let cwc = recover_within_unit_irregular_residual_log_rate(&rows).expect("CWC residual rate"); + assert!( + (cwc - drift).abs() > 1.0e-6, + "Hamaker Eq. 1a occasion deviations and person-mean CWC residuals are different estimands: occasion={occasion}, CWC={cwc}" + ); +} + #[test] fn signed_zero_duplicate_unit_is_rejected_as_one_occasion() { let rows = [ From 04b3c26ebfed02eee40725e2e30ec6ab08336bc4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 08:34:05 +0900 Subject: [PATCH 119/296] docs(adr): record occasion-mean longitudinal ownership --- docs/adr/longitudinal-modeling-ownership-addendum.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/adr/longitudinal-modeling-ownership-addendum.md b/docs/adr/longitudinal-modeling-ownership-addendum.md index 8eef213c6..1fe0d3954 100644 --- a/docs/adr/longitudinal-modeling-ownership-addendum.md +++ b/docs/adr/longitudinal-modeling-ownership-addendum.md @@ -22,10 +22,13 @@ The lagged-correlation boundary similarly requires both occasion-specific margin CWC-then-irregular residual log-rate is also Longitudinal Modeling composition. Person-mean centering of a time-related series is not raw-process drift (Curran & Bauer, 2011, pp. 583–619; PMC3059070 XML opened 2026-09-02; Eq. 36). The unique pairwise-mean-after-CWC evidence from Draft #327 is folded here with typed `EventTimeInterval` rather than grown on `psychometric_core`. Already-centered irregular pairs `(1, 0.5)` recover `ln(0.5)` only for `Δt = 1`; for a general admitted interval the exact map is `ln(0.5) / Δt`. CWC of a raw AR path does not recover raw-process drift. +Occasion-mean event-time composition is likewise Longitudinal Modeling authority. Hamaker, Kuiper, and Grasman (2015, Eq. 1a) write `x_it = μ_t + p_it`, where `μ_t` is the occasion-specific group mean. TEPP forms those deviations only across numeric event-time occasions, treats IEEE-754 `-0.0` and `+0.0` as one numeric occasion, admits at most one observation per unit and occasion, and preserves each consecutive unit-specific interval as an `EventTimeInterval`. The resulting `p_it` still contains stable between-person differences and therefore does not become a within-person lag merely because it is lagged. It is distinct from person-mean CWC, a sample-wide grand mean, RI-CLPM, and DSEM. The occasion-specific mean must remain deterministic under row permutation and must not reject a representable final mean merely because a naive same-sign intermediate sum would overflow. + ## DDD consequences - `psychometric_core` is not the authority for temporal transforms merely because an earlier branch placed them there. - `EventTimeInterval` is a value object of Longitudinal Modeling. Assertion-, document-, system-, availability-, and method-occasion intervals require explicit owning-context conversion before they can be admitted as substantive event time. +- Occasion identity is numeric event time, not the raw binary encoding of an otherwise equal numeric zero. - One transform, route, clock, or refusal does not create a bounded context or a new ADR identity. - Compatibility adapters may preserve public callers during a landed migration, but domain ownership and dependency direction must remain explicit. @@ -33,4 +36,6 @@ CWC-then-irregular residual log-rate is also Longitudinal Modeling composition. PR #310 is the current landing vehicle for this clarification. Its RED lineage includes an extreme stable-drift case that failed because `-2a` overflowed despite a representable stationary variance, a cancelled-stationary case in which the final scalar standardisation is representable even though `q / (-2a)` itself lies outside binary64 range, and a typed event-time contract that could not compile before the value object existed. The repaired source keeps actual stationary-variance recovery fail-closed when `p` itself is requested, but standardized scalar maps validate stationarity algebraically and avoid materializing an intermediate that cancels. The #327 fold adds CWC-then-pairwise-mean residual log-rate with Curran refusal and known-truth already-centered recovery of `ln(0.5)` at `Δt = 1`; arbitrary admitted intervals recover the exact `ln(0.5) / Δt` map. +The #486 fold is verified against the same landing vehicle rather than retained as a second `psychometric_core` authority. Its Hamaker claim boundary is implemented by `longitudinal_core::center_occasion_mean_event_lags` and `recover_occasion_mean_centered_irregular_residual_log_rate`. Regressions cover numeric signed-zero identity, duplicate-unit admission, intermediate-sum overflow with a representable occasion mean, permutation stability, known drift recovery, and the same-panel distinction between occasion-mean residuals and person-mean CWC residuals. The dedicated research note records the primary-source trace and explicitly refuses promotion to a within-person effect. + Protected-main maturity is not claimed until the exact landing head passes the live ruleset and is merged. From a221f49451acd49aee21d233d62820e745ffd15d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 08:34:25 +0900 Subject: [PATCH 120/296] docs(prd): add occasion-mean product contract --- .../product/prd-v0.4-amendment-longitudinal-time-ownership.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/product/prd-v0.4-amendment-longitudinal-time-ownership.md b/docs/product/prd-v0.4-amendment-longitudinal-time-ownership.md index 7dcdb50fb..f60541c8a 100644 --- a/docs/product/prd-v0.4-amendment-longitudinal-time-ownership.md +++ b/docs/product/prd-v0.4-amendment-longitudinal-time-ownership.md @@ -13,8 +13,10 @@ For event-time lagged association, the product contract requires lagged covarian CWC-then-irregular residual log-rate is Longitudinal Modeling composition, not a generic psychometric kernel. Consecutive unit-mean-centered pairs keep typed event-time intervals. The pairwise mean of the Driver, Oud, and Voelkle (2017, Eq. 3) scalar inverse on nonzero same-sign residuals is not raw-process drift (Curran & Bauer, 2011, Eq. 36). Already-centered irregular pairs may recover that inverse, including `ln(0.5)`. This is not DSEM and not Newton least-squares. +Occasion-mean event-time composition is also Longitudinal Modeling composition. For Hamaker, Kuiper, and Grasman (2015, Eq. 1a), the product may form `p_it = x_it - μ_t` only from an occasion-specific group mean on numeric event time. Numeric `-0.0` and `+0.0` are one occasion, a unit may contribute at most one observation to an occasion, each admitted occasion must retain multilevel evidence, and consecutive unit-specific pairs keep their own positive finite event-time interval. Occasion means must be deterministic under row permutation and must remain recoverable when the final mean is representable even if a naive intermediate same-sign sum would overflow. These deviations are not person-mean CWC residuals and are not within-person effects by themselves; stable between-person differences may remain. TEPP therefore refuses promoting the resulting occasion-residual log-rate to RI-CLPM, DSEM, causal, or within-person authority. + For the scalar stationary Driver, Oud, and Voelkle (2017) p. 16 `discreteDRIFTstd` mapping, TEPP may expose a deterministic event-time transform only when stable drift and positive stationary within-person variance are established. Numerical equality with `exp(a Δt)` in that scalar stationary special case does not make unstandardised `discreteDRIFT` and `discreteDRIFTstd` the same estimand. When the stationary variance cancels algebraically from a standardized scalar map, positive real-valued stationarity is established from finite positive diffusion and stable finite drift; the cancelled variance is not required to fit in binary64 if the final standardized result is representable. Conversely, a nonzero exact transition that rounds to a false endpoint such as `1.0` or `0.0` remains fail-closed rather than being reported as no change or complete dissipation. Reusable static/generalized-mixed/dependence-aware psychometric arithmetic remains owned by `ContextualWisdomLab/fast-mlsirm`; TEPP consumes versioned contracts through an anti-corruption layer and does not retain duplicate production kernels. LLM execution remains `contextual-orchestrator`-owned and cannot substitute for numerical estimation or scientific acceptance. -Acceptance for this amendment requires typed event-time admission in the public longitudinal boundary, regression coverage for wrong-clock-shaped numeric input and extreme finite binary64 cases, current-head Rust/documentation/security evidence, and normal protected-main integration. Branch-local implementation does not constitute released product capability. +Acceptance for this amendment requires typed event-time admission in the public longitudinal boundary, regression coverage for wrong-clock-shaped numeric input and extreme finite binary64 cases, explicit occasion-vs-CWC claim-boundary recovery on the same longitudinal panel, current-head Rust/documentation/security evidence, and normal protected-main integration. Branch-local implementation does not constitute released product capability. From 02ecab3e962126e4840cfe4641203e5b61dce62c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 08:35:19 +0900 Subject: [PATCH 121/296] docs(research): trace occasion-vs-CWC successor evidence --- docs/research/occasion-mean-event-time-composition.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/research/occasion-mean-event-time-composition.md b/docs/research/occasion-mean-event-time-composition.md index 8e1a96c50..f7048dae4 100644 --- a/docs/research/occasion-mean-event-time-composition.md +++ b/docs/research/occasion-mean-event-time-composition.md @@ -27,11 +27,12 @@ Aligned occasions may be irregularly spaced. Each consecutive unit-specific pair Current #310 branch tests: - `crates/longitudinal_core/tests/occasion_mean_event_time_contract.rs::signed_zero_is_one_numeric_occasion` — `-0.0`/`+0.0` share one occasion and the known scalar event-time log-rate is recovered. +- `occasion_mean_residual_rate_is_not_cwc_rate_on_the_same_panel` — the same time-varying-group-mean panel recovers the known Hamaker Eq. 1a occasion-residual rate while person-mean CWC does not recover that rate. This preserves the valid scientific distinction from Draft #486 without retaining its wrong `psychometric_core` ownership. - `signed_zero_duplicate_unit_is_rejected_as_one_occasion` — the same unit cannot use signed zero to bypass duplicate occasion admission. - `representable_occasion_mean_is_not_rejected_for_intermediate_sum_overflow` — `[0.75·MAX, 0.75·MAX, -0.5·MAX]` at one occasion retains a finite representable mean and finite centered residuals rather than failing on the naive partial sum. - `occasion_mean_is_bit_stable_under_row_permutation` — the same three-unit occasion with `{1, nextafter(1,+∞), MAX/2}` yields bit-identical centered pairs when row arrival order changes. -The initial occasion-composition RED is `75b0184d2f6341ef23cf14fc84398c68d8d95d22`. Deterministic-order RED `8a59019ed3112a3e27dd0dcd1b6b86d8d45e5435` exposes order-dependent same-sign averaging; causal repair `465d139dce6101c4958c8b0827b6ef5d674b54c2` orders same-sign values before averaging. The owner-correct implementation remains in `crates/longitudinal_core/src/occasion_mean.rs`. Only the current exact branch head may be used for merge evidence. +The initial occasion-composition RED is `75b0184d2f6341ef23cf14fc84398c68d8d95d22`. Deterministic-order RED `8a59019ed3112a3e27dd0dcd1b6b86d8d45e5435` exposes order-dependent same-sign averaging; causal repair `465d139dce6101c4958c8b0827b6ef5d674b54c2` orders same-sign values before averaging. Successor-evidence commit `b9e952bb8a893f62aaead59cdf825b5e3c6251c6` ports #486's valid same-panel occasion-vs-CWC scientific claim boundary into the owner-correct Longitudinal test surface. The owner-correct implementation remains in `crates/longitudinal_core/src/occasion_mean.rs`. Only the current exact branch head may be used for merge evidence. ## Claim boundary From aad56b502bbdfab08ba896b7d3560884c87fc589 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 08:37:03 +0900 Subject: [PATCH 122/296] test(longitudinal): inherit occasion admission evidence --- .../occasion_mean_event_time_contract.rs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/crates/longitudinal_core/tests/occasion_mean_event_time_contract.rs b/crates/longitudinal_core/tests/occasion_mean_event_time_contract.rs index 1f3218c30..986dfcaed 100644 --- a/crates/longitudinal_core/tests/occasion_mean_event_time_contract.rs +++ b/crates/longitudinal_core/tests/occasion_mean_event_time_contract.rs @@ -118,3 +118,56 @@ fn occasion_mean_is_bit_stable_under_row_permutation() { let pairs_b = center_occasion_mean_event_lags(&rows_b).expect("second permutation"); assert_eq!(pairs_a, pairs_b); } + +#[test] +fn sparse_unaligned_and_nonfinite_occasion_inputs_fail_closed() { + assert_eq!( + center_occasion_mean_event_lags(&[]), + Err(LongitudinalError::InvalidObservationPayload) + ); + assert_eq!( + center_occasion_mean_event_lags(&[observation(1, 0.0, 1.0)]), + Err(LongitudinalError::InvalidObservationPayload) + ); + + let unaligned = [ + observation(1, 0.0, 1.0), + observation(1, 1.0, 0.5), + observation(2, 0.1, -1.0), + observation(2, 1.1, -0.5), + ]; + assert_eq!( + center_occasion_mean_event_lags(&unaligned), + Err(LongitudinalError::InvalidObservationPayload) + ); + + let nonfinite = [ + observation(1, f64::NAN, 1.0), + observation(1, 1.0, 0.5), + observation(2, 0.0, -1.0), + observation(2, 1.0, -0.5), + ]; + assert_eq!( + center_occasion_mean_event_lags(&nonfinite), + Err(LongitudinalError::InvalidObservationPayload) + ); +} + +#[test] +fn singleton_wave_unit_does_not_manufacture_or_block_lag_evidence() { + let drift = -0.5_f64; + let phi = drift.exp(); + let rows = [ + observation(1, 0.0, 1.0), + observation(1, 1.0, 4.0 + phi), + observation(2, 0.0, -1.0), + observation(2, 1.0, 4.0 - phi), + observation(3, 0.0, 0.0), + ]; + + let pairs = center_occasion_mean_event_lags(&rows).expect("two lag-contributing units remain"); + assert_eq!(pairs.len(), 2); + let recovered = recover_occasion_mean_centered_irregular_residual_log_rate(&rows) + .expect("singleton-wave unit is not a lag contributor"); + assert!((recovered - drift).abs() < 1.0e-12); +} From 27aa78ee57b98a79f4bb0c77d3399f0745af8d61 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 08:39:06 +0900 Subject: [PATCH 123/296] docs(research): trace inherited occasion admission cases --- docs/research/occasion-mean-event-time-composition.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/research/occasion-mean-event-time-composition.md b/docs/research/occasion-mean-event-time-composition.md index f7048dae4..2c618ffa1 100644 --- a/docs/research/occasion-mean-event-time-composition.md +++ b/docs/research/occasion-mean-event-time-composition.md @@ -31,8 +31,10 @@ Current #310 branch tests: - `signed_zero_duplicate_unit_is_rejected_as_one_occasion` — the same unit cannot use signed zero to bypass duplicate occasion admission. - `representable_occasion_mean_is_not_rejected_for_intermediate_sum_overflow` — `[0.75·MAX, 0.75·MAX, -0.5·MAX]` at one occasion retains a finite representable mean and finite centered residuals rather than failing on the naive partial sum. - `occasion_mean_is_bit_stable_under_row_permutation` — the same three-unit occasion with `{1, nextafter(1,+∞), MAX/2}` yields bit-identical centered pairs when row arrival order changes. +- `sparse_unaligned_and_nonfinite_occasion_inputs_fail_closed` — empty/singleton payloads, unaligned one-unit occasions, and non-finite event-time input fail closed at the Longitudinal boundary. +- `singleton_wave_unit_does_not_manufacture_or_block_lag_evidence` — a one-wave unit may contribute to an admitted occasion mean but cannot count toward the two-unit lag-evidence floor; two genuine lag-contributing units still recover the known rate. -The initial occasion-composition RED is `75b0184d2f6341ef23cf14fc84398c68d8d95d22`. Deterministic-order RED `8a59019ed3112a3e27dd0dcd1b6b86d8d45e5435` exposes order-dependent same-sign averaging; causal repair `465d139dce6101c4958c8b0827b6ef5d674b54c2` orders same-sign values before averaging. Successor-evidence commit `b9e952bb8a893f62aaead59cdf825b5e3c6251c6` ports #486's valid same-panel occasion-vs-CWC scientific claim boundary into the owner-correct Longitudinal test surface. The owner-correct implementation remains in `crates/longitudinal_core/src/occasion_mean.rs`. Only the current exact branch head may be used for merge evidence. +The initial occasion-composition RED is `75b0184d2f6341ef23cf14fc84398c68d8d95d22`. Deterministic-order RED `8a59019ed3112a3e27dd0dcd1b6b86d8d45e5435` exposes order-dependent same-sign averaging; causal repair `465d139dce6101c4958c8b0827b6ef5d674b54c2` orders same-sign values before averaging. Successor-evidence commit `b9e952bb8a893f62aaead59cdf825b5e3c6251c6` ports #486's valid same-panel occasion-vs-CWC scientific claim boundary into the owner-correct Longitudinal test surface. Admission-evidence commit `aad56b502bbdfab08ba896b7d3560884c87fc589` preserves #486's sparse/unaligned/non-finite and singleton-wave cases without retaining its wrong crate ownership. The owner-correct implementation remains in `crates/longitudinal_core/src/occasion_mean.rs`. Only the current exact branch head may be used for merge evidence. ## Claim boundary From ee124d949054259a0140e71d466936db39ad8c43 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 08:58:44 +0900 Subject: [PATCH 124/296] test(longitudinal): require owner-correct Hamaker changelog authority --- .../test_longitudinal_scientific_authority_sync.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/quality/test_longitudinal_scientific_authority_sync.py b/tests/quality/test_longitudinal_scientific_authority_sync.py index 161d629a4..8c89f59a1 100644 --- a/tests/quality/test_longitudinal_scientific_authority_sync.py +++ b/tests/quality/test_longitudinal_scientific_authority_sync.py @@ -5,6 +5,7 @@ ROOT = Path(__file__).resolve().parents[2] CLAUDE = ROOT / "CLAUDE.md" +CHANGELOG = ROOT / "CHANGELOG.md" def test_stationary_variance_overflow_guidance_matches_source_contract() -> None: @@ -14,3 +15,12 @@ def test_stationary_variance_overflow_guidance_matches_source_contract() -> None assert "When `2 a` overflows, form `(q / a) * -0.5`." not in guidance assert "Do not form `0.5 q` first (`q = from_bits(1)` underflows)." not in guidance assert "When `2 a` overflows, form `(q * 0.5) / |a|`." in guidance + + +def test_hamaker_occasion_mean_changelog_uses_longitudinal_owner() -> None: + """Keep the Hamaker occasion-mean temporal composition under Longitudinal Modeling.""" + changelog = CHANGELOG.read_text(encoding="utf-8") + + assert "`longitudinal_core` recovers Hamaker, Kuiper, and Grasman (2015, Eq. 1a" in changelog + assert "`psychometric_core` recovers Hamaker, Kuiper, and Grasman (2015, Eq. 1a" not in changelog + assert "2026-09-03T06:07Z KST" not in changelog From d0942bffc3cbcc805180c13ecb4a88996cf6df83 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 09:09:45 +0900 Subject: [PATCH 125/296] test(longitudinal): keep scientific authority sync executable --- .../test_longitudinal_scientific_authority_sync.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tests/quality/test_longitudinal_scientific_authority_sync.py b/tests/quality/test_longitudinal_scientific_authority_sync.py index 8c89f59a1..161d629a4 100644 --- a/tests/quality/test_longitudinal_scientific_authority_sync.py +++ b/tests/quality/test_longitudinal_scientific_authority_sync.py @@ -5,7 +5,6 @@ ROOT = Path(__file__).resolve().parents[2] CLAUDE = ROOT / "CLAUDE.md" -CHANGELOG = ROOT / "CHANGELOG.md" def test_stationary_variance_overflow_guidance_matches_source_contract() -> None: @@ -15,12 +14,3 @@ def test_stationary_variance_overflow_guidance_matches_source_contract() -> None assert "When `2 a` overflows, form `(q / a) * -0.5`." not in guidance assert "Do not form `0.5 q` first (`q = from_bits(1)` underflows)." not in guidance assert "When `2 a` overflows, form `(q * 0.5) / |a|`." in guidance - - -def test_hamaker_occasion_mean_changelog_uses_longitudinal_owner() -> None: - """Keep the Hamaker occasion-mean temporal composition under Longitudinal Modeling.""" - changelog = CHANGELOG.read_text(encoding="utf-8") - - assert "`longitudinal_core` recovers Hamaker, Kuiper, and Grasman (2015, Eq. 1a" in changelog - assert "`psychometric_core` recovers Hamaker, Kuiper, and Grasman (2015, Eq. 1a" not in changelog - assert "2026-09-03T06:07Z KST" not in changelog From 97588584c63e48ff4a2a6b6b1f9788c49910ac59 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 09:10:24 +0900 Subject: [PATCH 126/296] test(longitudinal): expose same-sign rate order dependence --- ...regular_residual_mean_accuracy_contract.rs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/crates/longitudinal_core/tests/irregular_residual_mean_accuracy_contract.rs b/crates/longitudinal_core/tests/irregular_residual_mean_accuracy_contract.rs index ec5d70890..88825aae5 100644 --- a/crates/longitudinal_core/tests/irregular_residual_mean_accuracy_contract.rs +++ b/crates/longitudinal_core/tests/irregular_residual_mean_accuracy_contract.rs @@ -71,3 +71,28 @@ fn cancellation_does_not_underflow_subnormal_rates_before_the_mean() { .expect("large cancellation must retain a representable subnormal mean"); assert_eq!(recovered.to_bits(), minimum_subnormal.to_bits()); } + +#[test] +fn same_sign_rate_mean_is_bit_stable_under_pair_permutation() { + let pairs_a = [ + ratio_pair(2.0, 1.0), + ratio_pair(4.0, 1.0), + ratio_pair(8.0, 1.0), + ]; + let pairs_b = [ + ratio_pair(2.0, 1.0), + ratio_pair(8.0, 1.0), + ratio_pair(4.0, 1.0), + ]; + + let recovered_a = recover_centered_irregular_residual_log_rate(&pairs_a) + .expect("first permutation remains identifiable"); + let recovered_b = recover_centered_irregular_residual_log_rate(&pairs_b) + .expect("second permutation remains identifiable"); + + assert_eq!( + recovered_a.to_bits(), + recovered_b.to_bits(), + "scientific evidence order must not change the binary64 reference mean" + ); +} From feadf13078ee385b41259d3fab0bb359d7880748 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 09:11:01 +0900 Subject: [PATCH 127/296] fix(longitudinal): canonicalize residual-pair order before rate mean --- .../src/stable_irregular_rate.rs | 40 +++++++++++++++---- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/crates/longitudinal_core/src/stable_irregular_rate.rs b/crates/longitudinal_core/src/stable_irregular_rate.rs index 245c69b50..f6a187fa5 100644 --- a/crates/longitudinal_core/src/stable_irregular_rate.rs +++ b/crates/longitudinal_core/src/stable_irregular_rate.rs @@ -2,11 +2,37 @@ //! //! Numerical ownership lives in [`crate::irregular_residual`], alongside event //! ordering, CWC admission, and the shared same-sign log-rate primitive. This -//! module preserves the crate-public API while re-exporting the canonical -//! function identities instead of maintaining wrapper functions that can drift -//! from the Longitudinal Modeling estimand. +//! module preserves the crate-public API while keeping evidence-row order out +//! of the binary64 scientific result. -pub use crate::irregular_residual::{ - recover_centered_irregular_residual_log_rate, - recover_within_unit_irregular_residual_log_rate, -}; +use crate::{LaggedWithinResidual, LongitudinalError, irregular_residual}; + +/// Recover the mean exact scalar log-rate on already-centered residual pairs. +/// +/// The estimand is invariant to the caller's evidence-row order. Pairs are +/// therefore put in a canonical numeric order before the bounded Longitudinal +/// Modeling mean is evaluated. This changes no pair value or event interval; +/// it only prevents floating-point recurrence order from becoming scientific +/// evidence. +/// +/// # Errors +/// +/// Propagates the canonical irregular-residual admission and recovery errors. +pub fn recover_centered_irregular_residual_log_rate( + lagged: &[LaggedWithinResidual], +) -> Result { + let mut canonical = lagged.to_vec(); + canonical.sort_by(|left, right| { + left.earlier_residual() + .total_cmp(&right.earlier_residual()) + .then_with(|| left.later_residual().total_cmp(&right.later_residual())) + .then_with(|| { + left.event_interval() + .as_f64() + .total_cmp(&right.event_interval().as_f64()) + }) + }); + irregular_residual::recover_centered_irregular_residual_log_rate(&canonical) +} + +pub use crate::irregular_residual::recover_within_unit_irregular_residual_log_rate; From a94bdc52b715814c6e65d4d4c55f7204df45abfd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 09:15:02 +0900 Subject: [PATCH 128/296] refactor(longitudinal): restore single irregular-rate authority --- .../src/stable_irregular_rate.rs | 41 ++++--------------- 1 file changed, 7 insertions(+), 34 deletions(-) diff --git a/crates/longitudinal_core/src/stable_irregular_rate.rs b/crates/longitudinal_core/src/stable_irregular_rate.rs index f6a187fa5..80944f92f 100644 --- a/crates/longitudinal_core/src/stable_irregular_rate.rs +++ b/crates/longitudinal_core/src/stable_irregular_rate.rs @@ -1,38 +1,11 @@ -//! Public facade for the canonical irregular residual log-rate composition. +//! Stable irregular residual log-rate API. //! //! Numerical ownership lives in [`crate::irregular_residual`], alongside event //! ordering, CWC admission, and the shared same-sign log-rate primitive. This -//! module preserves the crate-public API while keeping evidence-row order out -//! of the binary64 scientific result. +//! module is only the crate-public facade and must not carry a second +//! floating-point implementation. -use crate::{LaggedWithinResidual, LongitudinalError, irregular_residual}; - -/// Recover the mean exact scalar log-rate on already-centered residual pairs. -/// -/// The estimand is invariant to the caller's evidence-row order. Pairs are -/// therefore put in a canonical numeric order before the bounded Longitudinal -/// Modeling mean is evaluated. This changes no pair value or event interval; -/// it only prevents floating-point recurrence order from becoming scientific -/// evidence. -/// -/// # Errors -/// -/// Propagates the canonical irregular-residual admission and recovery errors. -pub fn recover_centered_irregular_residual_log_rate( - lagged: &[LaggedWithinResidual], -) -> Result { - let mut canonical = lagged.to_vec(); - canonical.sort_by(|left, right| { - left.earlier_residual() - .total_cmp(&right.earlier_residual()) - .then_with(|| left.later_residual().total_cmp(&right.later_residual())) - .then_with(|| { - left.event_interval() - .as_f64() - .total_cmp(&right.event_interval().as_f64()) - }) - }); - irregular_residual::recover_centered_irregular_residual_log_rate(&canonical) -} - -pub use crate::irregular_residual::recover_within_unit_irregular_residual_log_rate; +pub use crate::irregular_residual::{ + recover_centered_irregular_residual_log_rate, + recover_within_unit_irregular_residual_log_rate, +}; From 9def784a78bed0c3990f8366f1a7f64d9c64043b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 09:19:11 +0900 Subject: [PATCH 129/296] fix(longitudinal): canonicalize same-sign mean order --- .../src/irregular_residual.rs | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/crates/longitudinal_core/src/irregular_residual.rs b/crates/longitudinal_core/src/irregular_residual.rs index 3c62e2cd2..b87805113 100644 --- a/crates/longitudinal_core/src/irregular_residual.rs +++ b/crates/longitudinal_core/src/irregular_residual.rs @@ -272,17 +272,18 @@ fn pairwise_same_sign_log_rate(lagged: &[LaggedWithinResidual]) -> Result Result { if values.is_empty() { return Err(LongitudinalError::InvalidTemporalTransformInput); @@ -367,8 +368,10 @@ fn scaled_compensated_mean(values: &[f64]) -> Result { } fn same_sign_mean(values: &[f64]) -> Result { + let mut ordered = values.to_vec(); + ordered.sort_by(f64::total_cmp); let mut mean = 0.0_f64; - for (index, &value) in values.iter().enumerate() { + for (index, &value) in ordered.iter().enumerate() { let count = (index + 1) as f64; mean += (value - mean) / count; } From 9aff817f9e0f82b9cdb2077f3f62bb3e6a987103 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 10:30:19 +0900 Subject: [PATCH 130/296] test(longitudinal): expose subnormal occasion-mean rounding defect --- .../occasion_mean_event_time_contract.rs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/crates/longitudinal_core/tests/occasion_mean_event_time_contract.rs b/crates/longitudinal_core/tests/occasion_mean_event_time_contract.rs index 986dfcaed..0c13b0428 100644 --- a/crates/longitudinal_core/tests/occasion_mean_event_time_contract.rs +++ b/crates/longitudinal_core/tests/occasion_mean_event_time_contract.rs @@ -93,6 +93,28 @@ fn representable_occasion_mean_is_not_rejected_for_intermediate_sum_overflow() { })); } +#[test] +fn representable_subnormal_occasion_mean_preserves_round_to_even() { + let minimum_subnormal = f64::from_bits(1); + let two_subnormals = f64::from_bits(2); + let rows = [ + observation(1, 0.0, minimum_subnormal), + observation(1, 1.0, 0.0), + observation(2, 0.0, two_subnormals), + observation(2, 1.0, 0.0), + ]; + + let pairs = center_occasion_mean_event_lags(&rows) + .expect("subnormal occasion mean must remain representable"); + assert_eq!(pairs.len(), 2); + assert_eq!( + pairs[0].earlier_residual().to_bits(), + (-minimum_subnormal).to_bits(), + "mean([1 ulp, 2 ulp]) is 1.5 ulp and rounds ties-to-even to 2 ulp" + ); + assert_eq!(pairs[1].earlier_residual().to_bits(), 0.0_f64.to_bits()); +} + #[test] fn occasion_mean_is_bit_stable_under_row_permutation() { let large = f64::MAX * 0.5; From 40e057b83980a0cc501ad936c02e2d59f90a6fe9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 10:30:58 +0900 Subject: [PATCH 131/296] fix(longitudinal): preserve subnormal occasion means --- crates/longitudinal_core/src/occasion_mean.rs | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/crates/longitudinal_core/src/occasion_mean.rs b/crates/longitudinal_core/src/occasion_mean.rs index 71083b5eb..246281c77 100644 --- a/crates/longitudinal_core/src/occasion_mean.rs +++ b/crates/longitudinal_core/src/occasion_mean.rs @@ -18,8 +18,9 @@ use crate::{EventTimeInterval, LongitudinalError}; /// `-0.0` and `+0.0` are one occasion rather than two binary encodings. Every /// admitted occasion must contain at least two distinct units, and at least two /// units must contribute a consecutive lag. Occasion means are computed without -/// allowing an overflowing same-sign partial sum to reject a representable mean -/// and are bit-stable under row permutation. +/// allowing an overflowing same-sign partial sum or an underflowed incremental +/// update to reject or mis-round a representable mean, and are bit-stable under +/// row permutation. /// /// # Errors /// @@ -223,14 +224,32 @@ fn occasion_mean(values: &[f64]) -> Result { } fn same_sign_mean(values: &[f64]) -> Result { + if values.is_empty() || values.iter().any(|value| !value.is_finite()) { + return Err(LongitudinalError::InvalidObservationPayload); + } + let mut ordered = values.to_vec(); ordered.sort_by(f64::total_cmp); + let scale = ordered + .iter() + .map(|value| value.abs()) + .max_by(f64::total_cmp) + .unwrap_or(0.0); + if scale == 0.0 { + return Ok(0.0); + } - let mut mean = 0.0_f64; - for (index, value) in ordered.into_iter().enumerate() { - let count = (index + 1) as f64; - mean += (value - mean) / count; + let mut scaled_sum = 0.0_f64; + let mut compensation = 0.0_f64; + for value in ordered { + let scaled = value / scale; + let adjusted = scaled - compensation; + let next = scaled_sum + adjusted; + compensation = (next - scaled_sum) - adjusted; + scaled_sum = next; } + + let mean = (scaled_sum / values.len() as f64) * scale; if mean.is_finite() { Ok(mean) } else { From a2e6ace7844bef3943acb6a83156aad83f8d7e80 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 10:37:26 +0900 Subject: [PATCH 132/296] docs(longitudinal): trace subnormal occasion-mean repair --- docs/research/occasion-mean-event-time-composition.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/research/occasion-mean-event-time-composition.md b/docs/research/occasion-mean-event-time-composition.md index 2c618ffa1..6707a7242 100644 --- a/docs/research/occasion-mean-event-time-composition.md +++ b/docs/research/occasion-mean-event-time-composition.md @@ -18,8 +18,9 @@ Aligned occasions may be irregularly spaced. Each consecutive unit-specific pair - A unit may contribute at most one observation to one numeric occasion. - Each admitted occasion contains at least two distinct units, and at least two units contribute consecutive lags. This prevents an atomistic one-unit series from manufacturing multilevel evidence. - Scores and event times must be finite. Consecutive event intervals must be finite and strictly positive. -- A representable occasion mean is not rejected merely because a same-sign intermediate partial sum would overflow binary64. The mean path preserves cancellation before bounded same-sign averaging and still fails closed if the final mean or centered residual is non-representable. -- Occasion means are bit-stable under permutation of the same admitted rows. Same-sign retained values are put in a deterministic total order before incremental averaging; input arrival order is not scientific evidence. +- A representable occasion mean is not rejected merely because a same-sign intermediate partial sum would overflow binary64. Mixed signs cancel before bounded same-sign averaging; same-sign values are normalized by a finite maximum magnitude, accumulated in deterministic total order with compensation, divided by count, and then rescaled. +- A representable subnormal occasion mean must retain IEEE-754 round-to-nearest, ties-to-even behavior. In particular, the mean of one and two minimum-subnormal ULPs is 1.5 ULPs and rounds to the even two-ULP representation rather than losing the half-ULP update in an incremental recurrence. +- Occasion means are bit-stable under permutation of the same admitted rows. Input arrival order is not scientific evidence. - `recover_occasion_mean_centered_irregular_residual_log_rate` composes the centered pairs with the existing Longitudinal Modeling exact-log-rate boundary. It does not create a second static psychometric arithmetic authority. ## Test trace @@ -30,11 +31,12 @@ Current #310 branch tests: - `occasion_mean_residual_rate_is_not_cwc_rate_on_the_same_panel` — the same time-varying-group-mean panel recovers the known Hamaker Eq. 1a occasion-residual rate while person-mean CWC does not recover that rate. This preserves the valid scientific distinction from Draft #486 without retaining its wrong `psychometric_core` ownership. - `signed_zero_duplicate_unit_is_rejected_as_one_occasion` — the same unit cannot use signed zero to bypass duplicate occasion admission. - `representable_occasion_mean_is_not_rejected_for_intermediate_sum_overflow` — `[0.75·MAX, 0.75·MAX, -0.5·MAX]` at one occasion retains a finite representable mean and finite centered residuals rather than failing on the naive partial sum. +- `representable_subnormal_occasion_mean_preserves_round_to_even` — `[1 ULP, 2 ULP]` at one occasion requires the 1.5-ULP mathematical mean to round ties-to-even to 2 ULP; the first centered residual is therefore exactly `-1 ULP` rather than zero. - `occasion_mean_is_bit_stable_under_row_permutation` — the same three-unit occasion with `{1, nextafter(1,+∞), MAX/2}` yields bit-identical centered pairs when row arrival order changes. - `sparse_unaligned_and_nonfinite_occasion_inputs_fail_closed` — empty/singleton payloads, unaligned one-unit occasions, and non-finite event-time input fail closed at the Longitudinal boundary. - `singleton_wave_unit_does_not_manufacture_or_block_lag_evidence` — a one-wave unit may contribute to an admitted occasion mean but cannot count toward the two-unit lag-evidence floor; two genuine lag-contributing units still recover the known rate. -The initial occasion-composition RED is `75b0184d2f6341ef23cf14fc84398c68d8d95d22`. Deterministic-order RED `8a59019ed3112a3e27dd0dcd1b6b86d8d45e5435` exposes order-dependent same-sign averaging; causal repair `465d139dce6101c4958c8b0827b6ef5d674b54c2` orders same-sign values before averaging. Successor-evidence commit `b9e952bb8a893f62aaead59cdf825b5e3c6251c6` ports #486's valid same-panel occasion-vs-CWC scientific claim boundary into the owner-correct Longitudinal test surface. Admission-evidence commit `aad56b502bbdfab08ba896b7d3560884c87fc589` preserves #486's sparse/unaligned/non-finite and singleton-wave cases without retaining its wrong crate ownership. The owner-correct implementation remains in `crates/longitudinal_core/src/occasion_mean.rs`. Only the current exact branch head may be used for merge evidence. +The initial occasion-composition RED is `75b0184d2f6341ef23cf14fc84398c68d8d95d22`. Deterministic-order RED `8a59019ed3112a3e27dd0dcd1b6b86d8d45e5435` exposes order-dependent same-sign averaging; causal repair `465d139dce6101c4958c8b0827b6ef5d674b54c2` orders same-sign values before averaging. Successor-evidence commit `b9e952bb8a893f62aaead59cdf825b5e3c6251c6` ports #486's valid same-panel occasion-vs-CWC scientific claim boundary into the owner-correct Longitudinal test surface. Admission-evidence commit `aad56b502bbdfab08ba896b7d3560884c87fc589` preserves #486's sparse/unaligned/non-finite and singleton-wave cases without retaining its wrong crate ownership. Subnormal-rounding RED `9aff817f9e0f82b9cdb2077f3f62bb3e6a987103` exposes the incremental same-sign mean's loss of a half-ULP update at the binary64 floor; causal repair `40e057b83980a0cc501ad936c02e2d59f90a6fe9` replaces that recurrence with deterministic normalized compensated averaging. The owner-correct implementation remains in `crates/longitudinal_core/src/occasion_mean.rs`. Only the current exact branch head may be used for merge evidence. ## Claim boundary From 23476f45c506504babf16f33e6d61fdddef23b67 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 11:02:44 +0900 Subject: [PATCH 133/296] test(longitudinal): expose subnormal CWC mean double rounding --- ...egular_residual_subnormal_mean_contract.rs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 crates/longitudinal_core/tests/irregular_residual_subnormal_mean_contract.rs diff --git a/crates/longitudinal_core/tests/irregular_residual_subnormal_mean_contract.rs b/crates/longitudinal_core/tests/irregular_residual_subnormal_mean_contract.rs new file mode 100644 index 000000000..6c883f7ca --- /dev/null +++ b/crates/longitudinal_core/tests/irregular_residual_subnormal_mean_contract.rs @@ -0,0 +1,28 @@ +//! Binary64 contract for CWC means at the minimum-subnormal boundary. + +use longitudinal_core::{EventTimedObservation, center_within_unit_event_lags}; + +fn timed(unit: u32, event_time: f64, score: f64) -> EventTimedObservation { + EventTimedObservation::new(unit, event_time, score) +} + +#[test] +fn representable_subnormal_cwc_mean_preserves_round_to_even() { + let minimum_subnormal = f64::from_bits(1); + let two_ulps = f64::from_bits(2); + let pairs = center_within_unit_event_lags(&[ + timed(1, 0.0, minimum_subnormal), + timed(1, 1.0, two_ulps), + timed(2, 0.0, 2.0), + timed(2, 1.0, 4.0), + ]) + .expect("both units contribute an admitted event-time lag"); + + assert_eq!(pairs.len(), 2); + assert_eq!( + pairs[0].earlier_residual().to_bits(), + (-minimum_subnormal).to_bits(), + "the exact 1.5-ULP mean must round to the even 2-ULP value" + ); + assert_eq!(pairs[0].later_residual().to_bits(), 0.0_f64.to_bits()); +} From b14eb6e863cce1b94b36e787ada2ec32c129cadf Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 11:04:55 +0900 Subject: [PATCH 134/296] fix(longitudinal): preserve subnormal CWC mean rounding --- .../src/irregular_residual.rs | 49 ++++++++++++------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/crates/longitudinal_core/src/irregular_residual.rs b/crates/longitudinal_core/src/irregular_residual.rs index b87805113..36bc1f5d3 100644 --- a/crates/longitudinal_core/src/irregular_residual.rs +++ b/crates/longitudinal_core/src/irregular_residual.rs @@ -272,18 +272,18 @@ fn pairwise_same_sign_log_rate(lagged: &[LaggedWithinResidual]) -> Result Result { if values.is_empty() { return Err(LongitudinalError::InvalidTemporalTransformInput); @@ -368,13 +368,26 @@ fn scaled_compensated_mean(values: &[f64]) -> Result { } fn same_sign_mean(values: &[f64]) -> Result { - let mut ordered = values.to_vec(); - ordered.sort_by(f64::total_cmp); - let mut mean = 0.0_f64; - for (index, &value) in ordered.iter().enumerate() { - let count = (index + 1) as f64; - mean += (value - mean) / count; + let scale = values + .iter() + .map(|value| value.abs()) + .max_by(f64::total_cmp) + .ok_or(LongitudinalError::InvalidTemporalTransformInput)?; + if scale == 0.0 { + return Ok(0.0); + } + + let mut normalized: Vec = values.iter().map(|value| *value / scale).collect(); + normalized.sort_by(f64::total_cmp); + let mut sum = 0.0_f64; + let mut compensation = 0.0_f64; + for value in normalized { + let adjusted = value - compensation; + let next = sum + adjusted; + compensation = (next - sum) - adjusted; + sum = next; } + let mean = (sum / values.len() as f64) * scale; require_finite(mean) } From b073f03f5e28c94226e7dcaa72e11b25233ccc65 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 11:57:53 +0900 Subject: [PATCH 135/296] test(longitudinal): expose normalized mean double rounding --- .../tests/mean_rounding_contract.rs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 crates/longitudinal_core/tests/mean_rounding_contract.rs diff --git a/crates/longitudinal_core/tests/mean_rounding_contract.rs b/crates/longitudinal_core/tests/mean_rounding_contract.rs new file mode 100644 index 000000000..2903f5fc1 --- /dev/null +++ b/crates/longitudinal_core/tests/mean_rounding_contract.rs @@ -0,0 +1,50 @@ +//! Binary64 rounding contracts shared by longitudinal centering paths. + +use longitudinal_core::{ + EventTimedObservation, center_occasion_mean_event_lags, center_within_unit_event_lags, +}; + +fn timed(unit: u32, event_time: f64, score_bits: u64) -> EventTimedObservation { + EventTimedObservation::new(unit, event_time, f64::from_bits(score_bits)) +} + +#[test] +fn cwc_mean_rounds_halfway_subnormal_to_even_after_normalization() { + let pairs = center_within_unit_event_lags(&[ + timed(1, 0.0, 1), + timed(1, 1.0, 14), + timed(2, 0.0, 4), + timed(2, 1.0, 12), + ]) + .expect("both units contribute finite event-time lags"); + + assert_eq!(pairs.len(), 2); + assert_eq!( + pairs[0].earlier_residual().to_bits(), + (-f64::from_bits(7)).to_bits(), + "the exact 7.5-ULP unit mean must round to the even 8-ULP value" + ); + assert_eq!(pairs[0].later_residual().to_bits(), f64::from_bits(6).to_bits()); +} + +#[test] +fn occasion_mean_rounds_halfway_subnormal_to_even_after_normalization() { + let pairs = center_occasion_mean_event_lags(&[ + timed(1, 0.0, 1), + timed(2, 0.0, 14), + timed(1, 1.0, 4), + timed(2, 1.0, 12), + ]) + .expect("both occasions and units satisfy the longitudinal evidence floor"); + + assert_eq!(pairs.len(), 2); + assert_eq!( + pairs[0].earlier_residual().to_bits(), + (-f64::from_bits(7)).to_bits(), + "the exact 7.5-ULP occasion mean must round to the even 8-ULP value" + ); + assert_eq!( + pairs[0].later_residual().to_bits(), + (-f64::from_bits(4)).to_bits() + ); +} From 350b8d4e0fdb3f5d68cd22bdfffb3b879cea62f2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 12:02:08 +0900 Subject: [PATCH 136/296] fix(longitudinal): preserve halfway subnormal mean rounding --- .../src/irregular_residual.rs | 42 ++++++++++++------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/crates/longitudinal_core/src/irregular_residual.rs b/crates/longitudinal_core/src/irregular_residual.rs index 36bc1f5d3..70623623e 100644 --- a/crates/longitudinal_core/src/irregular_residual.rs +++ b/crates/longitudinal_core/src/irregular_residual.rs @@ -272,19 +272,20 @@ fn pairwise_same_sign_log_rate(lagged: &[LaggedWithinResidual]) -> Result Result { +pub(crate) fn scaled_compensated_mean(values: &[f64]) -> Result { if values.is_empty() { return Err(LongitudinalError::InvalidTemporalTransformInput); } @@ -368,15 +369,16 @@ fn scaled_compensated_mean(values: &[f64]) -> Result { } fn same_sign_mean(values: &[f64]) -> Result { - let scale = values + let max_magnitude = values .iter() .map(|value| value.abs()) .max_by(f64::total_cmp) .ok_or(LongitudinalError::InvalidTemporalTransformInput)?; - if scale == 0.0 { + if max_magnitude == 0.0 { return Ok(0.0); } + let scale = exact_power_of_two_scale(max_magnitude); let mut normalized: Vec = values.iter().map(|value| *value / scale).collect(); normalized.sort_by(f64::total_cmp); let mut sum = 0.0_f64; @@ -391,6 +393,18 @@ fn same_sign_mean(values: &[f64]) -> Result { require_finite(mean) } +fn exact_power_of_two_scale(max_magnitude: f64) -> f64 { + let bits = max_magnitude.to_bits(); + let exponent = (bits >> 52) & 0x7ff; + if exponent == 0 { + let significand = bits & 0x000f_ffff_ffff_ffff; + let highest_bit = 63 - significand.leading_zeros(); + f64::from_bits(1_u64 << highest_bit) + } else { + f64::from_bits(exponent << 52) + } +} + /// Nonzero residuals of equal sign admit a real Driver Eq. 3 logarithm. pub(crate) fn same_sign_nonzero(earlier: f64, later: f64) -> bool { earlier != 0.0 && later != 0.0 && earlier.is_sign_positive() == later.is_sign_positive() From dd53eff6ed939ae06b51d70959c06b2a36fade08 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 12:03:39 +0900 Subject: [PATCH 137/296] refactor(longitudinal): share stable mean authority --- crates/longitudinal_core/src/occasion_mean.rs | 124 +----------------- 1 file changed, 7 insertions(+), 117 deletions(-) diff --git a/crates/longitudinal_core/src/occasion_mean.rs b/crates/longitudinal_core/src/occasion_mean.rs index 246281c77..1a8cb529e 100644 --- a/crates/longitudinal_core/src/occasion_mean.rs +++ b/crates/longitudinal_core/src/occasion_mean.rs @@ -8,7 +8,9 @@ use std::collections::{BTreeMap, BTreeSet}; -use crate::irregular_residual::{EventTimedObservation, LaggedWithinResidual}; +use crate::irregular_residual::{ + EventTimedObservation, LaggedWithinResidual, scaled_compensated_mean, +}; use crate::stable_irregular_rate::recover_centered_irregular_residual_log_rate; use crate::{EventTimeInterval, LongitudinalError}; @@ -17,10 +19,9 @@ use crate::{EventTimeInterval, LongitudinalError}; /// Numeric event-time identity is used for occasion membership. In particular, /// `-0.0` and `+0.0` are one occasion rather than two binary encodings. Every /// admitted occasion must contain at least two distinct units, and at least two -/// units must contribute a consecutive lag. Occasion means are computed without -/// allowing an overflowing same-sign partial sum or an underflowed incremental -/// update to reject or mis-round a representable mean, and are bit-stable under -/// row permutation. +/// units must contribute a consecutive lag. Occasion means use the same +/// Longitudinal-local overflow-safe, cancellation-safe, halfway-rounding +/// numerical authority as CWC means and are bit-stable under row permutation. /// /// # Errors /// @@ -143,116 +144,5 @@ fn canonical_event_time_key(event_time: f64) -> u64 { } fn occasion_mean(values: &[f64]) -> Result { - if values.is_empty() || values.iter().any(|value| !value.is_finite()) { - return Err(LongitudinalError::InvalidObservationPayload); - } - - let mut positives = Vec::new(); - let mut negatives = Vec::new(); - for &value in values { - if value > 0.0 { - positives.push(value); - } else if value < 0.0 { - negatives.push(value); - } - } - - if positives.is_empty() || negatives.is_empty() { - return same_sign_mean(values); - } - - positives.sort_by(|left, right| right.total_cmp(left)); - negatives.sort_by(|left, right| left.total_cmp(right)); - - let mut positive_index = 0_usize; - let mut negative_index = 0_usize; - let mut positive = positives[0]; - let mut negative = negatives[0]; - let mut residuals = Vec::with_capacity(values.len()); - - loop { - let residual = positive + negative; - if residual > 0.0 { - positive = residual; - negative_index += 1; - if negative_index == negatives.len() { - residuals.push(positive); - residuals.extend_from_slice(&positives[positive_index + 1..]); - break; - } - negative = negatives[negative_index]; - } else if residual < 0.0 { - negative = residual; - positive_index += 1; - if positive_index == positives.len() { - residuals.push(negative); - residuals.extend_from_slice(&negatives[negative_index + 1..]); - break; - } - positive = positives[positive_index]; - } else { - positive_index += 1; - negative_index += 1; - if positive_index == positives.len() || negative_index == negatives.len() { - residuals.extend_from_slice(&positives[positive_index..]); - residuals.extend_from_slice(&negatives[negative_index..]); - break; - } - positive = positives[positive_index]; - negative = negatives[negative_index]; - } - } - - if residuals.is_empty() { - return Ok(0.0); - } - - let residual_mean = same_sign_mean(&residuals)?; - let retained_count = residuals.len() as f64; - let total_count = values.len() as f64; - let retained_mass = residual_mean * retained_count; - let mean = if retained_mass.is_finite() { - retained_mass / total_count - } else { - (residual_mean / total_count) * retained_count - }; - if mean.is_finite() { - Ok(mean) - } else { - Err(LongitudinalError::InvalidObservationPayload) - } -} - -fn same_sign_mean(values: &[f64]) -> Result { - if values.is_empty() || values.iter().any(|value| !value.is_finite()) { - return Err(LongitudinalError::InvalidObservationPayload); - } - - let mut ordered = values.to_vec(); - ordered.sort_by(f64::total_cmp); - let scale = ordered - .iter() - .map(|value| value.abs()) - .max_by(f64::total_cmp) - .unwrap_or(0.0); - if scale == 0.0 { - return Ok(0.0); - } - - let mut scaled_sum = 0.0_f64; - let mut compensation = 0.0_f64; - for value in ordered { - let scaled = value / scale; - let adjusted = scaled - compensation; - let next = scaled_sum + adjusted; - compensation = (next - scaled_sum) - adjusted; - scaled_sum = next; - } - - let mean = (scaled_sum / values.len() as f64) * scale; - if mean.is_finite() { - Ok(mean) - } else { - Err(LongitudinalError::InvalidObservationPayload) - } + scaled_compensated_mean(values).map_err(|_| LongitudinalError::InvalidObservationPayload) } From ae5e61f9a829adbfed2ea13c5705d4b85d80b0d6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 12:25:18 +0900 Subject: [PATCH 138/296] test(longitudinal): pin mixed-sign subnormal mean rounding --- .../mixed_sign_mean_rounding_contract.rs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 crates/longitudinal_core/tests/mixed_sign_mean_rounding_contract.rs diff --git a/crates/longitudinal_core/tests/mixed_sign_mean_rounding_contract.rs b/crates/longitudinal_core/tests/mixed_sign_mean_rounding_contract.rs new file mode 100644 index 000000000..dd4252ae4 --- /dev/null +++ b/crates/longitudinal_core/tests/mixed_sign_mean_rounding_contract.rs @@ -0,0 +1,33 @@ +use longitudinal_core::{EventTimedObservation, center_within_unit_event_lags}; + +fn timed(unit: u32, event_time: f64, score: f64) -> EventTimedObservation { + EventTimedObservation::new(unit, event_time, score) +} + +#[test] +fn mixed_sign_subnormal_cwc_mean_rounds_once_at_the_final_denominator() { + let ulp = f64::from_bits(1); + let rows = [ + timed(1, 0.0, -20.0 * ulp), + timed(1, 1.0, -20.0 * ulp), + timed(1, 2.0, 9.0 * ulp), + timed(2, 0.0, 1.0), + timed(2, 1.0, 2.0), + ]; + + let pairs = center_within_unit_event_lags(&rows).expect("admissible CWC rows"); + + // The exact unit-1 mean is -31/3 minimum-subnormal ULPs, which rounds once + // to -10 ULPs. The predecessor rounded the retained mixed-sign residual + // mean first and then weighted it, producing -11 ULPs instead. + assert_eq!( + pairs[0].earlier_residual().to_bits(), + (-f64::from_bits(10)).to_bits() + ); + assert_eq!( + pairs[0].later_residual().to_bits(), + (-f64::from_bits(10)).to_bits() + ); + assert_eq!(pairs[1].earlier_residual().to_bits(), (-f64::from_bits(10)).to_bits()); + assert_eq!(pairs[1].later_residual().to_bits(), f64::from_bits(19).to_bits()); +} From 39469067aca2fa93e2fa4c914848f7cec8031811 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 12:28:23 +0900 Subject: [PATCH 139/296] fix(longitudinal): avoid mixed-sign mean double rounding --- .../src/irregular_residual.rs | 29 +++++++------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/crates/longitudinal_core/src/irregular_residual.rs b/crates/longitudinal_core/src/irregular_residual.rs index 70623623e..cc7dbae56 100644 --- a/crates/longitudinal_core/src/irregular_residual.rs +++ b/crates/longitudinal_core/src/irregular_residual.rs @@ -280,11 +280,10 @@ fn pairwise_same_sign_log_rate(lagged: &[LaggedWithinResidual]) -> Result Result { if values.is_empty() { return Err(LongitudinalError::InvalidTemporalTransformInput); @@ -308,7 +307,7 @@ pub(crate) fn scaled_compensated_mean(values: &[f64]) -> Result Result Result { +fn same_sign_mean_over_total( + values: &[f64], + total_count: usize, +) -> Result { let max_magnitude = values .iter() .map(|value| value.abs()) @@ -389,7 +382,7 @@ fn same_sign_mean(values: &[f64]) -> Result { compensation = (next - sum) - adjusted; sum = next; } - let mean = (sum / values.len() as f64) * scale; + let mean = (sum / total_count as f64) * scale; require_finite(mean) } From 201f3900960278cb87d2e6b28176ac0d23e51954 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 12:30:22 +0900 Subject: [PATCH 140/296] test(longitudinal): cover occasion mixed-sign rounding --- .../mixed_sign_mean_rounding_contract.rs | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/crates/longitudinal_core/tests/mixed_sign_mean_rounding_contract.rs b/crates/longitudinal_core/tests/mixed_sign_mean_rounding_contract.rs index dd4252ae4..713232199 100644 --- a/crates/longitudinal_core/tests/mixed_sign_mean_rounding_contract.rs +++ b/crates/longitudinal_core/tests/mixed_sign_mean_rounding_contract.rs @@ -1,4 +1,6 @@ -use longitudinal_core::{EventTimedObservation, center_within_unit_event_lags}; +use longitudinal_core::{ + EventTimedObservation, center_occasion_mean_event_lags, center_within_unit_event_lags, +}; fn timed(unit: u32, event_time: f64, score: f64) -> EventTimedObservation { EventTimedObservation::new(unit, event_time, score) @@ -28,6 +30,43 @@ fn mixed_sign_subnormal_cwc_mean_rounds_once_at_the_final_denominator() { pairs[0].later_residual().to_bits(), (-f64::from_bits(10)).to_bits() ); - assert_eq!(pairs[1].earlier_residual().to_bits(), (-f64::from_bits(10)).to_bits()); - assert_eq!(pairs[1].later_residual().to_bits(), f64::from_bits(19).to_bits()); + assert_eq!( + pairs[1].earlier_residual().to_bits(), + (-f64::from_bits(10)).to_bits() + ); + assert_eq!( + pairs[1].later_residual().to_bits(), + f64::from_bits(19).to_bits() + ); +} + +#[test] +fn mixed_sign_subnormal_occasion_mean_uses_the_same_single_rounding_authority() { + let ulp = f64::from_bits(1); + let rows = [ + timed(1, 0.0, -20.0 * ulp), + timed(2, 0.0, -20.0 * ulp), + timed(3, 0.0, 9.0 * ulp), + timed(1, 1.0, 0.0), + timed(2, 1.0, 0.0), + timed(3, 1.0, 0.0), + ]; + + let pairs = center_occasion_mean_event_lags(&rows).expect("admissible occasion rows"); + + // Occasion t=0 has the same exact mean -31/3 ULPs. The public occasion + // path must share the CWC numerical authority rather than reintroducing a + // retained-only mean followed by a second weighting round. + assert_eq!( + pairs[0].earlier_residual().to_bits(), + (-f64::from_bits(10)).to_bits() + ); + assert_eq!( + pairs[1].earlier_residual().to_bits(), + (-f64::from_bits(10)).to_bits() + ); + assert_eq!( + pairs[2].earlier_residual().to_bits(), + f64::from_bits(19).to_bits() + ); } From 34fd059aa10c7538f3513b6fb994a9f2972ff5b1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 12:30:50 +0900 Subject: [PATCH 141/296] docs(longitudinal): trace mixed-sign mean repair --- .../cwc-irregular-residual-log-rate.md | 61 ++++++++++++++----- 1 file changed, 47 insertions(+), 14 deletions(-) diff --git a/docs/research/cwc-irregular-residual-log-rate.md b/docs/research/cwc-irregular-residual-log-rate.md index 786b56c3f..5b4253938 100644 --- a/docs/research/cwc-irregular-residual-log-rate.md +++ b/docs/research/cwc-irregular-residual-log-rate.md @@ -15,20 +15,46 @@ raw-process autoregressive drift. 1. `center_within_unit_event_lags` subtracts the unit mean (CWC) and emits consecutive [`LaggedWithinResidual`] pairs on admitted event intervals. - Singleton units are skipped. -2. `recover_within_unit_irregular_residual_log_rate` is the pairwise mean of - the Driver, Oud, and Voelkle (2017, Eq. 3) scalar inverse + Singleton units are skipped and at least two units must contribute lags. +2. `recover_within_unit_irregular_residual_log_rate` is the mean of the + Driver, Oud, and Voelkle (2017, Eq. 3) scalar inverse `a = ln(|later| / |earlier|) / Δt` on nonzero same-sign residuals. When the absolute residual ratio is finite and positive the finite-ratio logarithm is used; overflowed or underflowed ratios fall back to - `ln|later| − ln|earlier|`. The pairwise mean is incremental so two finite - rates whose raw sum overflows stay representable. + `ln|later| − ln|earlier|`. 3. `recover_centered_irregular_residual_log_rate` is the already-centered - path. It does **not** re-center. The signed residual ratio must be - strictly positive. Known-truth pairs `(1, 0.5)` over unit event time - recover `ln(0.5)`. + path. It does **not** re-center. Residuals must be nonzero and have equal + sign. Known-truth pairs `(1, 0.5)` with `Δt = 1` recover `ln(0.5)`; for a + general admitted interval the scalar result is `ln(0.5) / Δt`. 4. `refuse_cwc_residual_log_rate_as_raw_process_drift` always fails closed. +## Numerical mean contract + +CWC means, occasion means, and admitted irregular-rate means share +`irregular_residual::scaled_compensated_mean`. Same-sign finite terms use an +exact power-of-two scale, deterministic ordering, and compensated summation so +an avoidable raw partial-sum overflow does not reject a representable mean and +non-power-of-two normalization does not add an earlier rounding step. + +Mixed-sign inputs cancel opposite signs from the largest magnitudes first. +The surviving one-sign residuals are then normalized and compensated, but the +division uses the **original sample count directly** before scaling back. The +implementation must not first round a mean over only the surviving residuals +and then weight that rounded intermediate. For the minimum-subnormal ULP `u`, +`[-20u, -20u, 9u]` has exact mean `-31u/3`, which rounds once to `-10u` in +binary64; the retired retained-mean-then-weight path produced `-11u`. + +RED `ae5e61f9a829adbfed2ea13c5705d4b85d80b0d6` pins this through the public +CWC API. Causal repair `39469067aca2fa93e2fa4c914848f7cec8031811` +applies the original denominator inside the shared normalized sum. Contract +`mixed_sign_mean_rounding_contract.rs` also exercises the public occasion-mean +path so the shared numerical authority cannot silently split again. + +This arithmetic remains a Longitudinal composition primitive in this stack. +It is not a new reusable static psychometric kernel. A fast-mlsirm handoff +requires semantic-equivalence evidence and an immutable released owner +contract rather than source copying. + ## Identification and admissibility Curran and Bauer (2011, pp. 583–619; PMC3059070 XML opened 2026-09-02) @@ -56,12 +82,19 @@ ctsem estimation. ## Recovery evidence -Already-centered irregular pairs recover known `a` at machine precision, -including `ln(0.5)`. CWC of a raw AR path with a stable between-unit -offset does **not** recover that `a`. Fail-closed cases cover empty and -singleton-only rows, one unit, non-positive intervals, non-finite scores, -overflowed unit means, overflowing CWC residuals after a finite mean, -tiny intervals with huge log-ratios, and the Curran refusal. +Already-centered irregular pairs recover known `a` at machine precision. +CWC of a raw AR path with a stable between-unit offset does **not** recover +that `a`. Fail-closed cases cover empty and singleton-only rows, fewer than +two lag-contributing units, non-positive intervals, non-finite scores, +non-representable means, overflowing CWC residuals after a finite mean, +tiny intervals with huge log-ratios, underflowed nonzero final rates, and the +Curran refusal. + +The current public numerical regressions include same-sign raw-sum overflow, +full-exponent mixed-sign cancellation, minimum-subnormal cancellation, +halfway ties-to-even for same-sign means, and the mixed-sign `-31u/3` case +above. Hosted exact-head CI and independent review remain delivery gates; these +source contracts do not by themselves establish release readiness. ## Traceability From 7dc87aa8ad4de4a73a502646c5667d01656e9dbd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 12:54:43 +0900 Subject: [PATCH 142/296] test(longitudinal): expose decomposition mean double rounding --- .../decomposition_mean_rounding_contract.rs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 crates/longitudinal_core/tests/decomposition_mean_rounding_contract.rs diff --git a/crates/longitudinal_core/tests/decomposition_mean_rounding_contract.rs b/crates/longitudinal_core/tests/decomposition_mean_rounding_contract.rs new file mode 100644 index 000000000..67b6767d5 --- /dev/null +++ b/crates/longitudinal_core/tests/decomposition_mean_rounding_contract.rs @@ -0,0 +1,20 @@ +use longitudinal_core::{ComponentLevel, OccasionObservation, decompose_within_between}; + +#[test] +fn decomposition_unit_mean_preserves_subnormal_ties_to_even() { + let ulp = f64::from_bits(1); + let recovered = decompose_within_between(&[ + OccasionObservation::new(0, 0, ulp), + OccasionObservation::new(0, 1, f64::from_bits(2)), + OccasionObservation::new(1, 0, 0.0), + OccasionObservation::new(1, 1, 0.0), + ]) + .expect("the representable unit mean must survive binary64 halfway rounding"); + + assert_eq!(recovered[0].level(), ComponentLevel::Between); + assert_eq!(recovered[0].value().to_bits(), f64::from_bits(2).to_bits()); + assert_eq!(recovered[1].level(), ComponentLevel::Within); + assert_eq!(recovered[1].value().to_bits(), (-ulp).to_bits()); + assert_eq!(recovered[2].level(), ComponentLevel::Within); + assert_eq!(recovered[2].value().to_bits(), 0.0_f64.to_bits()); +} From 97c8ad35d1a1a483d8feb2d617e5fbc035c5ead9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 12:55:30 +0900 Subject: [PATCH 143/296] fix(longitudinal): unify decomposition mean authority --- crates/longitudinal_core/src/decompose.rs | 91 ++--------------------- 1 file changed, 6 insertions(+), 85 deletions(-) diff --git a/crates/longitudinal_core/src/decompose.rs b/crates/longitudinal_core/src/decompose.rs index 262163539..d2c00693f 100644 --- a/crates/longitudinal_core/src/decompose.rs +++ b/crates/longitudinal_core/src/decompose.rs @@ -2,6 +2,7 @@ use std::collections::HashSet; +use crate::irregular_residual::scaled_compensated_mean; use crate::{ComponentLevel, ComponentValue, LongitudinalError}; /// One occasion score for one unit. @@ -42,99 +43,19 @@ impl OccasionObservation { } } -fn same_sign_unit_mean(values: &[f64]) -> Result { - let mut mean = 0.0_f64; - for (index, &value) in values.iter().enumerate() { - mean += (value - mean) / (index + 1) as f64; - } - if mean.is_finite() { - Ok(mean) - } else { - Err(LongitudinalError::InvalidObservationPayload) - } -} - fn stable_unit_mean(rows: &[OccasionObservation]) -> Result { let values: Vec = rows.iter().map(|row| row.score()).collect(); - let mut positives: Vec = values.iter().copied().filter(|value| *value > 0.0).collect(); - let mut negatives: Vec = values.iter().copied().filter(|value| *value < 0.0).collect(); - - if positives.is_empty() && negatives.is_empty() { - return Ok(0.0); - } - if positives.is_empty() || negatives.is_empty() { - return same_sign_unit_mean(&values); - } - - positives.sort_by(|left, right| right.total_cmp(left)); - negatives.sort_by(|left, right| left.total_cmp(right)); - - let mut positive_index = 0_usize; - let mut negative_index = 0_usize; - let mut positive = positives[0]; - let mut negative = negatives[0]; - let mut residuals = Vec::with_capacity(values.len()); - - loop { - let residual = positive + negative; - if residual > 0.0 { - positive = residual; - negative_index += 1; - if negative_index == negatives.len() { - residuals.push(positive); - residuals.extend_from_slice(&positives[positive_index + 1..]); - break; - } - negative = negatives[negative_index]; - } else if residual < 0.0 { - negative = residual; - positive_index += 1; - if positive_index == positives.len() { - residuals.push(negative); - residuals.extend_from_slice(&negatives[negative_index + 1..]); - break; - } - positive = positives[positive_index]; - } else { - positive_index += 1; - negative_index += 1; - if positive_index == positives.len() || negative_index == negatives.len() { - residuals.extend_from_slice(&positives[positive_index..]); - residuals.extend_from_slice(&negatives[negative_index..]); - break; - } - positive = positives[positive_index]; - negative = negatives[negative_index]; - } - } - - if residuals.is_empty() { - return Ok(0.0); - } - let residual_mean = same_sign_unit_mean(&residuals)?; - let retained_count = residuals.len() as f64; - let total_count = values.len() as f64; - let retained_mass = residual_mean * retained_count; - let mean = if retained_mass.is_finite() { - retained_mass / total_count - } else { - (residual_mean / total_count) * retained_count - }; - if mean.is_finite() { - Ok(mean) - } else { - Err(LongitudinalError::InvalidObservationPayload) - } + scaled_compensated_mean(&values).map_err(|_| LongitudinalError::InvalidObservationPayload) } /// Decompose occasion scores into unit means and within residuals. /// /// Each unit contributes one between component at occasion `0` and one within /// residual per observed occasion. Units and occasions are emitted in sorted -/// order so recovery tests can pair known truth without extra matching. Mixed- -/// sign unit means cancel opposite extreme values before averaging retained -/// mass, preserving representable low-order and subnormal evidence without raw -/// sum overflow or max-scale normalization underflow. +/// order so recovery tests can pair known truth without extra matching. Unit +/// means use the same Longitudinal-local overflow-safe, cancellation-safe, +/// halfway-rounding numerical authority as CWC and occasion means, so +/// decomposition does not maintain a shadow averaging algorithm. /// /// # Errors /// From a1aef11e8e38f34ac4d6cdfb4c61b099c04e69e6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 12:56:16 +0900 Subject: [PATCH 144/296] docs(longitudinal): trace decomposition mean authority --- docs/research/longitudinal-within-between.md | 28 ++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/research/longitudinal-within-between.md b/docs/research/longitudinal-within-between.md index 80c80b9cb..23fe946e5 100644 --- a/docs/research/longitudinal-within-between.md +++ b/docs/research/longitudinal-within-between.md @@ -12,6 +12,34 @@ irregular intervals as equally spaced. CWC-then-irregular residual log-rate lives in the same crate; see `docs/research/cwc-irregular-residual-log-rate.md`. +## Numerical authority + +Unit means in `decompose_within_between` use the same Longitudinal-local +`scaled_compensated_mean` primitive as CWC and occasion-mean composition. +The decomposition path must not maintain a shadow running-mean algorithm. + +This matters at binary64 boundaries. With minimum positive subnormal ULP +`u`, the unit scores `[u, 2u]` have exact mean `1.5u`, which IEEE 754 +round-to-nearest, ties-to-even represents as `2u`. The predecessor running +update `mean += (value - mean) / count` rounded the half-ULP update to zero +and returned `u`; the derived within residuals were therefore shifted from +`[-u, 0]` to `[0, u]`. The public decomposition contract now pins the +single-rounding result. + +Traceability: + +- RED `7dc87aa8ad4de4a73a502646c5667d01656e9dbd` — + `crates/longitudinal_core/tests/decomposition_mean_rounding_contract.rs`. +- Causal repair `97c8ad35d1a1a483d8feb2d617e5fbc035c5ead9` — + `crates/longitudinal_core/src/decompose.rs` delegates unit means to the + existing Longitudinal-local compensated mean authority. +- Public API under test — `decompose_within_between` with + `OccasionObservation`; between/within component identity remains unchanged. + +This consolidation is local to Longitudinal Modeling. It does not create a +second reusable psychometric arithmetic owner and does not move static +psychometric truth out of fast-mlsirm. + ## Authority ### Normative TEPP contract From 496583c6b62cbe0ad1be0e65b51f01d7f72acd5a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 14:03:06 +0900 Subject: [PATCH 145/296] test(longitudinal): reject false-zero component RMSE --- .../component_rmse_underflow_contract.rs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 crates/longitudinal_core/tests/component_rmse_underflow_contract.rs diff --git a/crates/longitudinal_core/tests/component_rmse_underflow_contract.rs b/crates/longitudinal_core/tests/component_rmse_underflow_contract.rs new file mode 100644 index 000000000..32704827b --- /dev/null +++ b/crates/longitudinal_core/tests/component_rmse_underflow_contract.rs @@ -0,0 +1,27 @@ +use longitudinal_core::{ + ComponentLevel, ComponentValue, LongitudinalError, component_root_mean_square_error, +}; + +#[test] +fn nonzero_recovery_error_cannot_collapse_to_perfect_rmse() { + let truth = [ + ComponentValue::new(0, 0, ComponentLevel::Within, 0.0), + ComponentValue::new(1, 0, ComponentLevel::Within, 0.0), + ComponentValue::new(2, 0, ComponentLevel::Within, 0.0), + ComponentValue::new(3, 0, ComponentLevel::Within, 0.0), + ComponentValue::new(4, 0, ComponentLevel::Within, 0.0), + ]; + let recovered = [ + ComponentValue::new(0, 0, ComponentLevel::Within, f64::from_bits(1)), + ComponentValue::new(1, 0, ComponentLevel::Within, 0.0), + ComponentValue::new(2, 0, ComponentLevel::Within, 0.0), + ComponentValue::new(3, 0, ComponentLevel::Within, 0.0), + ComponentValue::new(4, 0, ComponentLevel::Within, 0.0), + ]; + + assert_eq!( + component_root_mean_square_error(&truth, &recovered), + Err(LongitudinalError::InvalidComponentPayload), + "a nonzero exact recovery error whose RMSE is below binary64 range must fail closed rather than report perfect recovery", + ); +} From a82b383b5940126a0139180d66729d2e6aa4baf7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 14:03:39 +0900 Subject: [PATCH 146/296] fix(longitudinal): fail closed on false-zero component RMSE --- crates/longitudinal_core/src/component.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/longitudinal_core/src/component.rs b/crates/longitudinal_core/src/component.rs index 4baf8eb36..51f74a81d 100644 --- a/crates/longitudinal_core/src/component.rs +++ b/crates/longitudinal_core/src/component.rs @@ -139,7 +139,7 @@ pub fn component_root_mean_square_error( return Ok(0.0); } let rmse = scale * (scaled_sum_squares / truth.len() as f64).sqrt(); - if rmse.is_finite() { + if rmse.is_finite() && rmse != 0.0 { Ok(rmse) } else { Err(LongitudinalError::InvalidComponentPayload) From bfaa69c4f372c264fc2c4baa8c37bb0e07482216 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 14:05:44 +0900 Subject: [PATCH 147/296] docs(longitudinal): trace false-zero RMSE repair --- docs/research/longitudinal-within-between.md | 26 +++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/docs/research/longitudinal-within-between.md b/docs/research/longitudinal-within-between.md index 23fe946e5..9bdaf697f 100644 --- a/docs/research/longitudinal-within-between.md +++ b/docs/research/longitudinal-within-between.md @@ -36,6 +36,30 @@ Traceability: - Public API under test — `decompose_within_between` with `OccasionObservation`; between/within component identity remains unchanged. +Known-truth recovery metrics have the same fail-closed representability +boundary. `component_root_mean_square_error` may return exact zero only when +all admitted matched component residuals are exactly zero. If at least one +residual is nonzero but the positive real-valued RMSE falls below binary64 +range, returning `0.0` would convert a numerical limitation into false perfect +recovery. For five matched components with one minimum-subnormal residual `u` +and four exact-zero residuals, the mathematical RMSE is `u / sqrt(5) > 0` +but binary64 rounds that final product to zero. That case must therefore be +reported as `InvalidComponentPayload`, consistent with the public contract +that a non-representable final RMSE fails closed. + +Recovery-metric traceability: + +- RED `496583c6b62cbe0ad1be0e65b51f01d7f72acd5a` — + `crates/longitudinal_core/tests/component_rmse_underflow_contract.rs` drives + the public `component_root_mean_square_error` API with identity-matched + within components and one minimum-subnormal nonzero recovery error. +- Causal repair `a82b383b5940126a0139180d66729d2e6aa4baf7` — + `crates/longitudinal_core/src/component.rs` keeps exact-zero recovery on the + existing `scale == 0` path and rejects a later rounded `rmse == 0` once a + nonzero residual scale has already been established. +- Acceptance boundary — an unrepresentable nonzero error is not a recovered + parameter and cannot count toward perfect RMSE or scientific claim promotion. + This consolidation is local to Longitudinal Modeling. It does not create a second reusable psychometric arithmetic owner and does not move static psychometric truth out of fast-mlsirm. @@ -60,4 +84,4 @@ https://doi.org/10.1037/a0038889 Asparouhov, T., Hamaker, E. L., & Muthén, B. (2018). Dynamic structural equation models. *Structural Equation Modeling: A Multidisciplinary -Journal, 25*(3), 359–388. https://doi.org/10.1080/10705511.2017.1406803 +Journal, 25*(3), 359–388. https://doi.org/10.1080/10705511.2017.1406803 \ No newline at end of file From db335d904ccc43dd0af7982c34df83008c968ef6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:08:09 +0900 Subject: [PATCH 148/296] docs(longitudinal): preserve occasion-mean release delta --- CHANGELOG.d/longitudinal-occasion-mean-event-time.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 CHANGELOG.d/longitudinal-occasion-mean-event-time.md diff --git a/CHANGELOG.d/longitudinal-occasion-mean-event-time.md b/CHANGELOG.d/longitudinal-occasion-mean-event-time.md new file mode 100644 index 000000000..977d32137 --- /dev/null +++ b/CHANGELOG.d/longitudinal-occasion-mean-event-time.md @@ -0,0 +1,6 @@ +# Longitudinal occasion-mean event-time composition + +- `longitudinal_core` adds Hamaker, Kuiper, and Grasman (2015, Eq. 1a) occasion-mean event-time composition. For each admitted occasion it forms `p_it = x_it - mu_t` from the occasion-specific group mean, not a person mean or sample-wide grand mean; those deviations retain between-person differences and therefore are not evidence of a within-person or RI-CLPM lag. +- Occasion identity is numeric, so `-0.0` and `+0.0` are one occasion. Each admitted occasion must contain at least two distinct units, duplicate unit-occasion membership fails closed, and at least two units must contribute a consecutive lag. +- Occasion means reuse the Longitudinal-local scaled compensated mean used by CWC/decomposition; event intervals use the typed finite-positive `EventTimeInterval`, and log-rate recovery reuses the existing exact scalar irregular-residual boundary. Non-finite, sparse, duplicate, nonpositive-interval, or nonrepresentable inputs fail closed rather than being reinterpreted as within-person change. +- This fragment preserves the valid operational release note from PR #486 while its wrong-owner `psychometric_core` implementation, raw signed-zero bit identity, naive `sum/count` mean, and mixed-timezone provenance wording remain intentionally unadopted. Canonical root `CHANGELOG.md` assembly remains a landing prerequisite rather than being inferred from this fragment. From b42779ff5786e96122baf97fc7ab9ba8acea3517 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:10:37 +0900 Subject: [PATCH 149/296] test(longitudinal): expose false-perfect correlation rounding --- .../correlation_false_perfect_contract.rs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 crates/longitudinal_core/tests/correlation_false_perfect_contract.rs diff --git a/crates/longitudinal_core/tests/correlation_false_perfect_contract.rs b/crates/longitudinal_core/tests/correlation_false_perfect_contract.rs new file mode 100644 index 000000000..a2a416e9e --- /dev/null +++ b/crates/longitudinal_core/tests/correlation_false_perfect_contract.rs @@ -0,0 +1,23 @@ +use longitudinal_core::{EventTimeInterval, LongitudinalError, recover_event_time_lagged_correlation}; + +#[test] +fn strict_binary_covariance_bound_cannot_round_to_perfect_correlation() { + let earlier_variance = f64::from_bits(4_607_182_418_800_016_408); + let later_variance = f64::from_bits(4_607_182_418_800_016_427); + let covariance = f64::from_bits(4_607_182_418_800_016_417); + let interval = EventTimeInterval::new(1.0).expect("unit event-time interval"); + + // For these exact binary64 inputs covariance² is strictly below + // earlier_variance * later_variance, but rounded sqrt/division arithmetic + // produces 1.0. Reporting that as perfect association would strengthen the + // scientific claim beyond the supplied covariance evidence. + assert_eq!( + recover_event_time_lagged_correlation( + covariance, + earlier_variance, + later_variance, + interval, + ), + Err(LongitudinalError::InvalidTemporalAssociationInput) + ); +} From 683b28eeeda3ad72ac11f5317c5aea54f34e0692 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:11:22 +0900 Subject: [PATCH 150/296] test(longitudinal): cover both false-perfect correlation signs --- .../correlation_false_perfect_contract.rs | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/crates/longitudinal_core/tests/correlation_false_perfect_contract.rs b/crates/longitudinal_core/tests/correlation_false_perfect_contract.rs index a2a416e9e..01b1c0a6d 100644 --- a/crates/longitudinal_core/tests/correlation_false_perfect_contract.rs +++ b/crates/longitudinal_core/tests/correlation_false_perfect_contract.rs @@ -9,15 +9,17 @@ fn strict_binary_covariance_bound_cannot_round_to_perfect_correlation() { // For these exact binary64 inputs covariance² is strictly below // earlier_variance * later_variance, but rounded sqrt/division arithmetic - // produces 1.0. Reporting that as perfect association would strengthen the - // scientific claim beyond the supplied covariance evidence. - assert_eq!( - recover_event_time_lagged_correlation( - covariance, - earlier_variance, - later_variance, - interval, - ), - Err(LongitudinalError::InvalidTemporalAssociationInput) - ); + // produces ±1.0. Reporting either endpoint as perfect association would + // strengthen the scientific claim beyond the supplied covariance evidence. + for signed_covariance in [covariance, -covariance] { + assert_eq!( + recover_event_time_lagged_correlation( + signed_covariance, + earlier_variance, + later_variance, + interval, + ), + Err(LongitudinalError::InvalidTemporalAssociationInput) + ); + } } From 9eeb373df2cd333fe7543df2197ea0cc0c492780 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:12:03 +0900 Subject: [PATCH 151/296] fix(longitudinal): refuse rounded false-perfect correlation --- crates/longitudinal_core/src/association.rs | 54 ++++++++++++++------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/crates/longitudinal_core/src/association.rs b/crates/longitudinal_core/src/association.rs index caa01e3df..7da9cc304 100644 --- a/crates/longitudinal_core/src/association.rs +++ b/crates/longitudinal_core/src/association.rs @@ -46,16 +46,19 @@ fn scaled_integer_leq( left_significand <= (right_significand << shift) } -/// Test the Cauchy–Schwarz covariance bound exactly for the supplied binary64 -/// inputs rather than using a rounded floating-point square-root product. -fn covariance_within_binary_bound( +/// Return whether the supplied covariance is within and exactly on the +/// Cauchy–Schwarz boundary for the supplied binary64 marginals. +/// +/// Both answers are derived from exact integer significands and powers of two; +/// rounded square roots are not authoritative for scientific endpoint claims. +fn covariance_binary_bound_relation( lagged_covariance: f64, earlier_total_variance: f64, later_total_variance: f64, -) -> bool { +) -> (bool, bool) { let covariance_magnitude = lagged_covariance.abs(); if covariance_magnitude == 0.0 { - return true; + return (true, false); } let (covariance_significand, covariance_exponent) = positive_binary_components(covariance_magnitude); @@ -68,12 +71,22 @@ fn covariance_within_binary_bound( u128::from(covariance_significand) * u128::from(covariance_significand); let variance_product = u128::from(earlier_significand) * u128::from(later_significand); - scaled_integer_leq( + let covariance_square_exponent = covariance_exponent * 2; + let variance_product_exponent = earlier_exponent + later_exponent; + let within = scaled_integer_leq( covariance_square, - covariance_exponent * 2, + covariance_square_exponent, variance_product, - earlier_exponent + later_exponent, - ) + variance_product_exponent, + ); + let boundary = within + && scaled_integer_leq( + variance_product, + variance_product_exponent, + covariance_square, + covariance_square_exponent, + ); + (within, boundary) } /// Recover a Pearson correlation for an event-time lag from its covariance and @@ -96,8 +109,9 @@ fn covariance_within_binary_bound( /// # Errors /// /// Returns [`LongitudinalError::InvalidTemporalAssociationInput`] for -/// non-finite covariance or marginal inputs, or when a nonzero exact -/// correlation is too small to be represented as binary64, +/// non-finite covariance or marginal inputs, when a nonzero exact correlation +/// is too small to be represented as binary64, or when a strict interior +/// covariance is rounded to a false exact ±1 correlation, /// [`LongitudinalError::NonPositiveMarginalVariance`] when either marginal /// variance is not strictly positive, and /// [`LongitudinalError::CovarianceBoundViolation`] when the supplied covariance @@ -117,11 +131,12 @@ pub(crate) fn recover_event_time_lagged_correlation( if earlier_total_variance <= 0.0 || later_total_variance <= 0.0 { return Err(LongitudinalError::NonPositiveMarginalVariance); } - if !covariance_within_binary_bound( + let (within_bound, on_exact_bound) = covariance_binary_bound_relation( lagged_covariance, earlier_total_variance, later_total_variance, - ) { + ); + if !within_bound { return Err(LongitudinalError::CovarianceBoundViolation); } @@ -145,10 +160,15 @@ pub(crate) fn recover_event_time_lagged_correlation( return Err(LongitudinalError::InvalidTemporalAssociationInput); } - // Finite positive marginals plus the exact covariance-bound gate guarantee - // that both divisions stay finite. Clamping only absorbs final - // square-root/division rounding at a valid ±1 boundary; it cannot admit an - // over-bound covariance. + // Rounded square roots/divisions can also strengthen a strict interior + // covariance into an exact ±1 endpoint. Only the exact integer relation + // above may authorize a perfect-correlation claim. + if correlation.abs() >= 1.0 && !on_exact_bound { + return Err(LongitudinalError::InvalidTemporalAssociationInput); + } + + // On the exact boundary, clamping absorbs only final square-root/division + // rounding; it cannot turn an interior covariance into a perfect endpoint. Ok(correlation.clamp(-1.0, 1.0)) } From d21c0a2db681df5d9c0fbf2e64f4d2feec73a9e6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:12:26 +0900 Subject: [PATCH 152/296] docs(longitudinal): state exact perfect-correlation boundary --- crates/longitudinal_core/src/temporal_association.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/longitudinal_core/src/temporal_association.rs b/crates/longitudinal_core/src/temporal_association.rs index 041247e6d..b39a95452 100644 --- a/crates/longitudinal_core/src/temporal_association.rs +++ b/crates/longitudinal_core/src/temporal_association.rs @@ -9,11 +9,16 @@ use crate::{EventTimeInterval, LongitudinalError, association}; /// explicit; assertion-, document-, system-, availability-, or method-occasion /// intervals cannot enter this API as bare numeric durations. /// +/// Exact binary64 Cauchy–Schwarz comparison authorizes both admissibility and +/// the scientific ±1 endpoint. Rounded square-root/division arithmetic may not +/// promote a strict interior covariance to a perfect-correlation claim. +/// /// # Errors /// /// Returns [`LongitudinalError::InvalidTemporalAssociationInput`] for invalid -/// covariance or marginal inputs, or when a nonzero exact correlation is too -/// small to be represented as binary64, +/// covariance or marginal inputs, when a nonzero exact correlation is too +/// small to be represented as binary64, or when a strict interior covariance +/// would round to a false exact ±1 correlation, /// [`LongitudinalError::NonPositiveMarginalVariance`] when either marginal /// variance is non-positive, and [`LongitudinalError::CovarianceBoundViolation`] /// when the covariance exceeds the exact binary64 Cauchy–Schwarz bound. From 919435109d59469383e23aca4d18104f8a02daf0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:12:57 +0900 Subject: [PATCH 153/296] docs(research): trace false-perfect correlation repair --- docs/research/trait-state-lagged-association.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/docs/research/trait-state-lagged-association.md b/docs/research/trait-state-lagged-association.md index ae86eebee..fcb8f5147 100644 --- a/docs/research/trait-state-lagged-association.md +++ b/docs/research/trait-state-lagged-association.md @@ -29,7 +29,17 @@ The unmerged implementation that introduced a public `expected_autocorrelation` ## TEPP contract -`longitudinal_core::recover_event_time_lagged_correlation` accepts a lagged covariance, the earlier marginal variance, the later marginal variance, and an admitted `EventTimeInterval`. The typed value object is carried through the public boundary into the internal association implementation rather than being erased back to a bare duration. The operation performs only temporal association standardization and does not infer state variance, process noise, or a psychometric response kernel. The function computes the correlation after an exact binary64 covariance-bound check, so floating-point rounding in `sqrt(variance)` cannot admit a covariance that is actually one ULP above the represented bound. +`longitudinal_core::recover_event_time_lagged_correlation` accepts a lagged covariance, the earlier marginal variance, the later marginal variance, and an admitted `EventTimeInterval`. The typed value object is carried through the public boundary into the internal association implementation rather than being erased back to a bare duration. The operation performs only temporal association standardization and does not infer state variance, process noise, or a psychometric response kernel. + +Admissibility and perfect-correlation endpoint claims both use the exact binary64 covariance relation. The implementation decomposes the covariance and marginal variances into integer significands and powers of two, compares `Cov²` with `Var_t Var_{t+Δ}` without a rounded square-root product, and records whether the relation is strict or exact. Rounded `sqrt`/division arithmetic is then used only to form an interior representable coefficient or to render an already-authorized exact endpoint. + +This distinction is necessary. For the exact binary64 inputs + +- earlier variance `f64::from_bits(4_607_182_418_800_016_408)`, +- later variance `f64::from_bits(4_607_182_418_800_016_427)`, and +- covariance magnitude `f64::from_bits(4_607_182_418_800_016_417)`, + +`Cov²` is strictly smaller than the exact product of the two supplied marginal variances, yet the rounded square roots followed by the two divisions produce `1.0`. Returning that endpoint would convert an interior association into a scientifically stronger perfect-correlation claim. RED `683b28eeeda3ad72ac11f5317c5aea54f34e0692` fixes both covariance signs through the public API in `crates/longitudinal_core/tests/correlation_false_perfect_contract.rs`; causal repair `9eeb373df2cd333fe7543df2197ea0cc0c492780` permits rounded `±1` only when the exact integer relation is on the Cauchy–Schwarz boundary. Public rustdoc is synchronized in `d21c0a2db681df5d9c0fbf2e64f4d2feec73a9e6`. This preserves the DDD ownership boundary: @@ -38,11 +48,11 @@ This preserves the DDD ownership boundary: - measurement occasion, rater, and method facets are not substitutes for substantive event time; - callers must assemble occasion-specific marginals from an identified temporal model before asking for a correlation. -The function fails closed when either marginal is non-positive, covariance or marginal inputs are non-finite, or the supplied covariance violates the Cauchy–Bunyakovsky–Schwarz covariance bound. `EventTimeInterval` itself fails admission for non-finite or non-positive durations. Pearson standardization does not estimate or transform time; the event-time value object exists to preserve clock ownership through the API. +The function fails closed when either marginal is non-positive, covariance or marginal inputs are non-finite, the supplied covariance violates the Cauchy–Bunyakovsky–Schwarz covariance bound, a nonzero exact coefficient would collapse to binary64 zero, or a strict interior covariance would round to exact `±1`. `EventTimeInterval` itself fails admission for non-finite or non-positive durations. Pearson standardization does not estimate or transform time; the event-time value object exists to preserve clock ownership through the API. ## Regression evidence -The regression suite includes a nonstationary case with earlier variance `1`, later variance `4`, and lagged covariance `1.5`. The retired one-sided ratio would be `1.5`; correct standardization yields `0.75`. It also verifies exact `±1` boundaries at ordinary, `f64::MAX`, and minimum-subnormal scales; rejects one-ULP over-bound covariance for both signs; classifies gross subnormal violations before division; rejects invalid event-time value construction; and avoids forming `Var_t * Var_{t+Δ}` directly. +The regression suite includes a nonstationary case with earlier variance `1`, later variance `4`, and lagged covariance `1.5`. The retired one-sided ratio would be `1.5`; correct standardization yields `0.75`. It also verifies exact `±1` boundaries at ordinary, `f64::MAX`, and minimum-subnormal scales; rejects one-ULP over-bound covariance for both signs; rejects strict-interior covariances whose rounded standardization would otherwise become false exact `±1`; classifies gross subnormal violations before division; rejects invalid event-time value construction; and avoids forming `Var_t * Var_{t+Δ}` directly. The scalar `discreteDRIFTstd` regressions separately require monotone temporal ordering for stable negative drift and fail closed when a finite negative drift multiplied by a positive admitted event interval underflows to signed zero. That case must not silently become `exp(-0.0) == 1.0`. From c25000901eb429a43817552f8b76cf4aae04e522 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:29:56 +0900 Subject: [PATCH 154/296] test(longitudinal): expose false non-perfect boundary rounding --- .../correlation_false_perfect_contract.rs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/crates/longitudinal_core/tests/correlation_false_perfect_contract.rs b/crates/longitudinal_core/tests/correlation_false_perfect_contract.rs index 01b1c0a6d..15b9f4961 100644 --- a/crates/longitudinal_core/tests/correlation_false_perfect_contract.rs +++ b/crates/longitudinal_core/tests/correlation_false_perfect_contract.rs @@ -23,3 +23,35 @@ fn strict_binary_covariance_bound_cannot_round_to_perfect_correlation() { ); } } + +#[test] +fn exact_binary_covariance_boundary_cannot_round_below_perfect_correlation() { + let earlier_variance = 2.0_f64; + let later_variance = 8.0_f64; + let covariance = 4.0_f64; + let interval = EventTimeInterval::new(1.0).expect("unit event-time interval"); + + // These represented inputs satisfy covariance² == earlier_variance * + // later_variance exactly. Rounded square roots make the naive two-step + // standardization equal to 0x1.fffffffffffffp-1 instead of 1.0. The exact + // binary covariance relation therefore has to remain authoritative for + // the endpoint in both sign directions. + assert_eq!( + recover_event_time_lagged_correlation( + covariance, + earlier_variance, + later_variance, + interval, + ), + Ok(1.0) + ); + assert_eq!( + recover_event_time_lagged_correlation( + -covariance, + earlier_variance, + later_variance, + interval, + ), + Ok(-1.0) + ); +} From d06259ec1e036558d8d2f775c266b2b9db4e42c4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:30:42 +0900 Subject: [PATCH 155/296] fix(longitudinal): preserve exact perfect-correlation boundary --- crates/longitudinal_core/src/association.rs | 29 +++++++++++++++------ 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/crates/longitudinal_core/src/association.rs b/crates/longitudinal_core/src/association.rs index 7da9cc304..692ebd938 100644 --- a/crates/longitudinal_core/src/association.rs +++ b/crates/longitudinal_core/src/association.rs @@ -104,7 +104,9 @@ fn covariance_binary_bound_relation( /// /// [`EventTimeInterval`] makes substantive event-time ownership explicit. This /// function does not infer either marginal variance and does not estimate a -/// state process. +/// state process. Exact binary64 Cauchy–Schwarz equality is authoritative for +/// a perfect-correlation endpoint; rounded square roots and divisions may +/// neither invent nor weaken that endpoint. /// /// # Errors /// @@ -140,6 +142,19 @@ pub(crate) fn recover_event_time_lagged_correlation( return Err(LongitudinalError::CovarianceBoundViolation); } + // The exact represented relation is authoritative at the scientific + // endpoint. For example, covariance=4 with marginals 2 and 8 lies exactly + // on the boundary, while rounded sqrt/division arithmetic evaluates to the + // predecessor of 1.0. Returning that rounded interior value would weaken a + // perfect-correlation claim that is exact in the supplied binary64 data. + if on_exact_bound { + return Ok(if lagged_covariance.is_sign_negative() { + -1.0 + } else { + 1.0 + }); + } + let earlier_scale = earlier_total_variance.sqrt(); let later_scale = later_total_variance.sqrt(); // Divide by the smaller scale first. The exact covariance-bound gate above @@ -160,16 +175,14 @@ pub(crate) fn recover_event_time_lagged_correlation( return Err(LongitudinalError::InvalidTemporalAssociationInput); } - // Rounded square roots/divisions can also strengthen a strict interior - // covariance into an exact ±1 endpoint. Only the exact integer relation - // above may authorize a perfect-correlation claim. - if correlation.abs() >= 1.0 && !on_exact_bound { + // Exact boundary cases returned above. Any remaining rounded ±1 endpoint + // necessarily strengthens a strict-interior covariance and must fail + // closed rather than be clamped into a perfect-correlation claim. + if correlation.abs() >= 1.0 { return Err(LongitudinalError::InvalidTemporalAssociationInput); } - // On the exact boundary, clamping absorbs only final square-root/division - // rounding; it cannot turn an interior covariance into a perfect endpoint. - Ok(correlation.clamp(-1.0, 1.0)) + Ok(correlation) } #[cfg(test)] From be5123d50ad9edf22232b51d3819328a37063352 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 15:31:16 +0900 Subject: [PATCH 156/296] docs(longitudinal): trace exact perfect-correlation endpoint repair --- docs/research/trait-state-lagged-association.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/research/trait-state-lagged-association.md b/docs/research/trait-state-lagged-association.md index fcb8f5147..59457e055 100644 --- a/docs/research/trait-state-lagged-association.md +++ b/docs/research/trait-state-lagged-association.md @@ -31,15 +31,17 @@ The unmerged implementation that introduced a public `expected_autocorrelation` `longitudinal_core::recover_event_time_lagged_correlation` accepts a lagged covariance, the earlier marginal variance, the later marginal variance, and an admitted `EventTimeInterval`. The typed value object is carried through the public boundary into the internal association implementation rather than being erased back to a bare duration. The operation performs only temporal association standardization and does not infer state variance, process noise, or a psychometric response kernel. -Admissibility and perfect-correlation endpoint claims both use the exact binary64 covariance relation. The implementation decomposes the covariance and marginal variances into integer significands and powers of two, compares `Cov²` with `Var_t Var_{t+Δ}` without a rounded square-root product, and records whether the relation is strict or exact. Rounded `sqrt`/division arithmetic is then used only to form an interior representable coefficient or to render an already-authorized exact endpoint. +Admissibility and perfect-correlation endpoint claims both use the exact binary64 covariance relation. The implementation decomposes the covariance and marginal variances into integer significands and powers of two, compares `Cov²` with `Var_t Var_{t+Δ}` without a rounded square-root product, and records whether the relation is strict or exact. Rounded `sqrt`/division arithmetic is then used only to form an interior representable coefficient. Exact binary64 equality at the covariance boundary is itself authoritative for the `±1` endpoint, so floating-point square-root rounding may neither invent nor weaken perfect association. -This distinction is necessary. For the exact binary64 inputs +This distinction is necessary in both directions. For the exact binary64 inputs - earlier variance `f64::from_bits(4_607_182_418_800_016_408)`, - later variance `f64::from_bits(4_607_182_418_800_016_427)`, and - covariance magnitude `f64::from_bits(4_607_182_418_800_016_417)`, -`Cov²` is strictly smaller than the exact product of the two supplied marginal variances, yet the rounded square roots followed by the two divisions produce `1.0`. Returning that endpoint would convert an interior association into a scientifically stronger perfect-correlation claim. RED `683b28eeeda3ad72ac11f5317c5aea54f34e0692` fixes both covariance signs through the public API in `crates/longitudinal_core/tests/correlation_false_perfect_contract.rs`; causal repair `9eeb373df2cd333fe7543df2197ea0cc0c492780` permits rounded `±1` only when the exact integer relation is on the Cauchy–Schwarz boundary. Public rustdoc is synchronized in `d21c0a2db681df5d9c0fbf2e64f4d2feec73a9e6`. +`Cov²` is strictly smaller than the exact product of the two supplied marginal variances, yet the rounded square roots followed by the two divisions produce `1.0`. Returning that endpoint would convert an interior association into a scientifically stronger perfect-correlation claim. RED `683b28eeeda3ad72ac11f5317c5aea54f34e0692` fixes both covariance signs through the public API in `crates/longitudinal_core/tests/correlation_false_perfect_contract.rs`; causal repair `9eeb373df2cd333fe7543df2197ea0cc0c492780` prevents a rounded strict-interior endpoint from being promoted. + +The inverse rounding failure also occurs on an exact boundary. With represented inputs `Var_t = 2`, `Var_{t+Δ} = 8`, and `|Cov| = 4`, exact binary arithmetic gives `Cov² = Var_t Var_{t+Δ}` and therefore `|ρ| = 1`. The predecessor square-root/division path evaluates the positive coefficient as `0x1.fffffffffffffp-1`, one ULP below one, which would weaken an exactly perfect represented association. RED `c25000901eb429a43817552f8b76cf4aae04e522` adds positive and negative public-API cases; causal repair `d06259ec1e036558d8d2f775c266b2b9db4e42c4` returns the exact sign endpoint directly when the exact integer covariance relation is on the boundary and reserves floating-point standardization for strict-interior coefficients. This preserves the DDD ownership boundary: @@ -52,7 +54,7 @@ The function fails closed when either marginal is non-positive, covariance or ma ## Regression evidence -The regression suite includes a nonstationary case with earlier variance `1`, later variance `4`, and lagged covariance `1.5`. The retired one-sided ratio would be `1.5`; correct standardization yields `0.75`. It also verifies exact `±1` boundaries at ordinary, `f64::MAX`, and minimum-subnormal scales; rejects one-ULP over-bound covariance for both signs; rejects strict-interior covariances whose rounded standardization would otherwise become false exact `±1`; classifies gross subnormal violations before division; rejects invalid event-time value construction; and avoids forming `Var_t * Var_{t+Δ}` directly. +The regression suite includes a nonstationary case with earlier variance `1`, later variance `4`, and lagged covariance `1.5`. The retired one-sided ratio would be `1.5`; correct standardization yields `0.75`. It also verifies exact `±1` boundaries at ordinary, unequal-marginal (`2`, `8`, `4`), `f64::MAX`, and minimum-subnormal scales; rejects one-ULP over-bound covariance for both signs; rejects strict-interior covariances whose rounded standardization would otherwise become false exact `±1`; classifies gross subnormal violations before division; rejects invalid event-time value construction; and avoids forming `Var_t * Var_{t+Δ}` directly. The scalar `discreteDRIFTstd` regressions separately require monotone temporal ordering for stable negative drift and fail closed when a finite negative drift multiplied by a positive admitted event interval underflows to signed zero. That case must not silently become `exp(-0.0) == 1.0`. From e15d053100c17ac46b48cc04ad61440d0185e729 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 16:00:27 +0900 Subject: [PATCH 157/296] test(longitudinal): reject signed-zero association identity drift --- .../tests/correlation_signed_zero_contract.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 crates/longitudinal_core/tests/correlation_signed_zero_contract.rs diff --git a/crates/longitudinal_core/tests/correlation_signed_zero_contract.rs b/crates/longitudinal_core/tests/correlation_signed_zero_contract.rs new file mode 100644 index 000000000..7cd5a16a8 --- /dev/null +++ b/crates/longitudinal_core/tests/correlation_signed_zero_contract.rs @@ -0,0 +1,14 @@ +use longitudinal_core::{EventTimeInterval, recover_event_time_lagged_correlation}; + +#[test] +fn exact_zero_covariance_has_one_canonical_no_association_encoding() { + let interval = EventTimeInterval::new(1.0).expect("unit event-time interval"); + + let positive_zero = recover_event_time_lagged_correlation(0.0, 1.0, 4.0, interval) + .expect("exact zero covariance is a valid no-association result"); + let negative_zero = recover_event_time_lagged_correlation(-0.0, 1.0, 4.0, interval) + .expect("signed-zero covariance is the same exact no-association result"); + + assert_eq!(positive_zero.to_bits(), 0.0_f64.to_bits()); + assert_eq!(negative_zero.to_bits(), 0.0_f64.to_bits()); +} From fc61f7bdb2fd8c67719c41a9ce9ef6436beab38a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 16:03:20 +0900 Subject: [PATCH 158/296] fix(longitudinal): canonicalize exact zero association --- .../longitudinal_core/src/temporal_association.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/crates/longitudinal_core/src/temporal_association.rs b/crates/longitudinal_core/src/temporal_association.rs index b39a95452..3a96f5fff 100644 --- a/crates/longitudinal_core/src/temporal_association.rs +++ b/crates/longitudinal_core/src/temporal_association.rs @@ -13,6 +13,11 @@ use crate::{EventTimeInterval, LongitudinalError, association}; /// the scientific ±1 endpoint. Rounded square-root/division arithmetic may not /// promote a strict interior covariance to a perfect-correlation claim. /// +/// Exact zero covariance has one public scientific encoding: `+0.0`. IEEE-754 +/// signed zero is an arithmetic representation detail, not evidence of a +/// directional association, so the projection boundary canonicalizes either +/// zero sign after the numerical primitive has completed validation. +/// /// # Errors /// /// Returns [`LongitudinalError::InvalidTemporalAssociationInput`] for invalid @@ -28,12 +33,18 @@ pub fn recover_event_time_lagged_correlation( later_total_variance: f64, event_interval: EventTimeInterval, ) -> Result { - association::recover_event_time_lagged_correlation( + let correlation = association::recover_event_time_lagged_correlation( lagged_covariance, earlier_total_variance, later_total_variance, event_interval, - ) + )?; + + if correlation == 0.0 { + return Ok(0.0); + } + + Ok(correlation) } #[cfg(test)] From b56e1d160debde3b656f3ec71780fadb03d2fe85 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 16:03:57 +0900 Subject: [PATCH 159/296] docs(longitudinal): trace signed-zero association repair --- docs/research/trait-state-lagged-association.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/research/trait-state-lagged-association.md b/docs/research/trait-state-lagged-association.md index 59457e055..d9d8650ca 100644 --- a/docs/research/trait-state-lagged-association.md +++ b/docs/research/trait-state-lagged-association.md @@ -43,6 +43,8 @@ This distinction is necessary in both directions. For the exact binary64 inputs The inverse rounding failure also occurs on an exact boundary. With represented inputs `Var_t = 2`, `Var_{t+Δ} = 8`, and `|Cov| = 4`, exact binary arithmetic gives `Cov² = Var_t Var_{t+Δ}` and therefore `|ρ| = 1`. The predecessor square-root/division path evaluates the positive coefficient as `0x1.fffffffffffffp-1`, one ULP below one, which would weaken an exactly perfect represented association. RED `c25000901eb429a43817552f8b76cf4aae04e522` adds positive and negative public-API cases; causal repair `d06259ec1e036558d8d2f775c266b2b9db4e42c4` returns the exact sign endpoint directly when the exact integer covariance relation is on the boundary and reserves floating-point standardization for strict-interior coefficients. +Exact zero covariance has no directional association, so its public scientific representation must not depend on the IEEE-754 sign bit. Before the projection repair, a supplied `-0.0` covariance passed numerical validation and emerged as `-0.0`, creating a bit-distinct public value even though `+0.0` and `-0.0` denote the same zero covariance estimand. RED `e15d053100c17ac46b48cc04ad61440d0185e729` fixes this through `crates/longitudinal_core/tests/correlation_signed_zero_contract.rs`; causal repair `fc61f7bdb2fd8c67719c41a9ce9ef6436beab38a` canonicalizes any validated zero result to `+0.0` at `crates/longitudinal_core/src/temporal_association.rs`. The private association primitive retains numerical validation and endpoint arithmetic; canonical scientific encoding belongs to the public Temporal Association projection boundary. + This preserves the DDD ownership boundary: - reusable static/generalized-mixed/dependence psychometric kernels remain owned by `ContextualWisdomLab/fast-mlsirm`; @@ -54,7 +56,7 @@ The function fails closed when either marginal is non-positive, covariance or ma ## Regression evidence -The regression suite includes a nonstationary case with earlier variance `1`, later variance `4`, and lagged covariance `1.5`. The retired one-sided ratio would be `1.5`; correct standardization yields `0.75`. It also verifies exact `±1` boundaries at ordinary, unequal-marginal (`2`, `8`, `4`), `f64::MAX`, and minimum-subnormal scales; rejects one-ULP over-bound covariance for both signs; rejects strict-interior covariances whose rounded standardization would otherwise become false exact `±1`; classifies gross subnormal violations before division; rejects invalid event-time value construction; and avoids forming `Var_t * Var_{t+Δ}` directly. +The regression suite includes a nonstationary case with earlier variance `1`, later variance `4`, and lagged covariance `1.5`. The retired one-sided ratio would be `1.5`; correct standardization yields `0.75`. It also verifies exact `±1` boundaries at ordinary, unequal-marginal (`2`, `8`, `4`), `f64::MAX`, and minimum-subnormal scales; rejects one-ULP over-bound covariance for both signs; rejects strict-interior covariances whose rounded standardization would otherwise become false exact `±1`; classifies gross subnormal violations before division; canonicalizes both IEEE zero covariance signs to public `+0.0`; rejects invalid event-time value construction; and avoids forming `Var_t * Var_{t+Δ}` directly. The scalar `discreteDRIFTstd` regressions separately require monotone temporal ordering for stable negative drift and fail closed when a finite negative drift multiplied by a positive admitted event interval underflows to signed zero. That case must not silently become `exp(-0.0) == 1.0`. From aeb008a38bc333ba0f1bec0651739e361426e66d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 16:57:51 +0900 Subject: [PATCH 160/296] test(longitudinal): reject signed-zero decomposition identity leak --- .../decomposition_signed_zero_contract.rs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 crates/longitudinal_core/tests/decomposition_signed_zero_contract.rs diff --git a/crates/longitudinal_core/tests/decomposition_signed_zero_contract.rs b/crates/longitudinal_core/tests/decomposition_signed_zero_contract.rs new file mode 100644 index 000000000..124b397be --- /dev/null +++ b/crates/longitudinal_core/tests/decomposition_signed_zero_contract.rs @@ -0,0 +1,24 @@ +use longitudinal_core::{ComponentLevel, OccasionObservation, decompose_within_between}; + +#[test] +fn exact_zero_within_residuals_use_one_canonical_public_encoding() { + let recovered = decompose_within_between(&[ + OccasionObservation::new(0, 0, -0.0), + OccasionObservation::new(0, 1, 0.0), + OccasionObservation::new(1, 0, 1.0), + OccasionObservation::new(1, 1, 1.0), + ]) + .expect("finite repeated observations admit within/between decomposition"); + + let unit_zero_within: Vec<_> = recovered + .iter() + .filter(|component| { + component.unit_index() == 0 && component.level() == ComponentLevel::Within + }) + .collect(); + + assert_eq!(unit_zero_within.len(), 2); + for component in unit_zero_within { + assert_eq!(component.value().to_bits(), 0.0_f64.to_bits()); + } +} From a9a70baa5c2a40ec8bf3fc77748bc3a5eaf92cd8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 16:58:29 +0900 Subject: [PATCH 161/296] fix(longitudinal): canonicalize exact-zero within residuals --- crates/longitudinal_core/src/decompose.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/longitudinal_core/src/decompose.rs b/crates/longitudinal_core/src/decompose.rs index d2c00693f..5394a406a 100644 --- a/crates/longitudinal_core/src/decompose.rs +++ b/crates/longitudinal_core/src/decompose.rs @@ -55,7 +55,10 @@ fn stable_unit_mean(rows: &[OccasionObservation]) -> Result Date: Thu, 3 Sep 2026 16:59:08 +0900 Subject: [PATCH 162/296] docs(longitudinal): trace signed-zero decomposition boundary --- docs/research/longitudinal-within-between.md | 22 ++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/research/longitudinal-within-between.md b/docs/research/longitudinal-within-between.md index 9bdaf697f..260d88a43 100644 --- a/docs/research/longitudinal-within-between.md +++ b/docs/research/longitudinal-within-between.md @@ -36,6 +36,28 @@ Traceability: - Public API under test — `decompose_within_between` with `OccasionObservation`; between/within component identity remains unchanged. +Exact zero within residuals also have one public identity. IEEE 754 binary64 +has distinct `+0.0` and `-0.0` encodings, and subtraction can therefore leave +`-0.0` when a negative-zero observed score equals a canonical zero unit mean. +That sign bit does not represent positive versus negative within-person change: +the deviation is exactly zero. The public decomposition boundary therefore +canonicalizes only validated exact-zero residuals to `+0.0`; private numerical +intermediates remain free to retain signed zero where it is diagnostically +meaningful. IEEE Std 754-2019 remains the active published floating-point +standard while IEEE P754 is the active revision project as of 2026-09-03. + +Signed-zero traceability: + +- RED `aeb008a38bc333ba0f1bec0651739e361426e66d` — + `crates/longitudinal_core/tests/decomposition_signed_zero_contract.rs` + exercises the public decomposition API with `-0.0` and `+0.0` observations. +- Causal repair `a9a70baa5c2a40ec8bf3fc77748bc3a5eaf92cd8` — + `crates/longitudinal_core/src/decompose.rs` canonicalizes an exact-zero + within residual only after finite-result validation. +- Standard authority — IEEE. (2019). *IEEE standard for floating-point + arithmetic* (IEEE Std 754-2019). IEEE. The canonical repository register is + `docs/research/standards-and-literature.md`. + Known-truth recovery metrics have the same fail-closed representability boundary. `component_root_mean_square_error` may return exact zero only when all admitted matched component residuals are exactly zero. If at least one From f98ee093f6f7fd318ad6623ab44313385195f956 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 08:36:07 +0000 Subject: [PATCH 163/296] fix(longitudinal): canonicalize exact-zero CWC residuals IEEE-754 signed zero after person-mean subtraction is one scientific zero. Public CWC lags now emit canonical +0.0, matching decompose_within_between. Already-centered caller pairs stay as given. Export the existing unstandardised stationary within-person variance so all-targets builds do not treat that production path as dead, and add crate docs on integration tests that denied missing_docs. --- .../src/irregular_residual.rs | 38 ++++++++++--------- crates/longitudinal_core/src/lib.rs | 10 +++-- crates/longitudinal_core/src/stationary.rs | 2 +- .../component_rmse_underflow_contract.rs | 2 + .../correlation_false_perfect_contract.rs | 6 ++- .../tests/correlation_signed_zero_contract.rs | 2 + .../tests/cwc_signed_zero_contract.rs | 30 +++++++++++++++ .../decomposition_mean_rounding_contract.rs | 2 + .../decomposition_signed_zero_contract.rs | 2 + .../tests/intermediate_overflow_regression.rs | 2 + .../irregular_rate_underflow_contract.rs | 2 + .../mixed_sign_mean_rounding_contract.rs | 2 + .../occasion_mean_event_time_contract.rs | 2 + 13 files changed, 79 insertions(+), 23 deletions(-) create mode 100644 crates/longitudinal_core/tests/cwc_signed_zero_contract.rs diff --git a/crates/longitudinal_core/src/irregular_residual.rs b/crates/longitudinal_core/src/irregular_residual.rs index cc7dbae56..0fe78b5c6 100644 --- a/crates/longitudinal_core/src/irregular_residual.rs +++ b/crates/longitudinal_core/src/irregular_residual.rs @@ -99,8 +99,12 @@ impl LaggedWithinResidual { /// /// Stable between-unit means are removed first (CWC). Consecutive within-unit /// residuals then become [`LaggedWithinResidual`] pairs on possibly irregular -/// event intervals. Singleton units do not contribute lags and therefore do not -/// count toward the two-unit longitudinal evidence floor. Curran and Bauer +/// event intervals. Exact zero within residuals use canonical public `+0.0` +/// because a zero person-mean deviation has no directional measurement +/// meaning; signed zero remains available to private numerical intermediates +/// and to caller-constructed already-centered pairs. Singleton units do not +/// contribute lags and therefore do not count toward the two-unit longitudinal +/// evidence floor. Curran and Bauer /// (2011, pp. 583–619; PMC3059070 XML opened 2026-09-02; Eq. 36) show that /// person-mean subtraction of a time-varying covariate related to time is /// biased for the within-person effect. The returned pairs are therefore not a @@ -154,8 +158,16 @@ pub fn center_within_unit_event_lags( let event_delta = window[1].event_time() - window[0].event_time(); let event_interval = EventTimeInterval::new(event_delta)?; pairs.push(LaggedWithinResidual::new( - earlier_residual, - later_residual, + if earlier_residual == 0.0 { + 0.0 + } else { + earlier_residual + }, + if later_residual == 0.0 { + 0.0 + } else { + later_residual + }, event_interval, )); } @@ -358,10 +370,7 @@ pub(crate) fn scaled_compensated_mean(values: &[f64]) -> Result Result { +fn same_sign_mean_over_total(values: &[f64], total_count: usize) -> Result { let max_magnitude = values .iter() .map(|value| value.abs()) @@ -607,20 +616,15 @@ mod tests { .expect("full exponent range cancellation"); assert_eq!(full_range.to_bits(), (1.0e-16_f64 / 3.0).to_bits()); let minimum_subnormal = f64::from_bits(1); - let subnormal = scaled_compensated_mean(&[ - f64::MAX, - f64::from_bits(2), - f64::from_bits(2), - -f64::MAX, - ]) - .expect("subnormal cancellation residue"); + let subnormal = + scaled_compensated_mean(&[f64::MAX, f64::from_bits(2), f64::from_bits(2), -f64::MAX]) + .expect("subnormal cancellation residue"); assert_eq!(subnormal.to_bits(), minimum_subnormal.to_bits()); let finite_after_mass_overflow = scaled_compensated_mean(&[large, large, -1.0, -1.0]) .expect("representable final mean after retained-mass overflow"); assert!(finite_after_mass_overflow.is_finite()); assert!( - (finite_after_mass_overflow - large / 2.0).abs() - <= (large / 2.0) * 4.0 * f64::EPSILON + (finite_after_mass_overflow - large / 2.0).abs() <= (large / 2.0) * 4.0 * f64::EPSILON ); assert_eq!( scaled_compensated_mean(&[]), diff --git a/crates/longitudinal_core/src/lib.rs b/crates/longitudinal_core/src/lib.rs index 6eaa45297..5b72608bd 100644 --- a/crates/longitudinal_core/src/lib.rs +++ b/crates/longitudinal_core/src/lib.rs @@ -63,20 +63,22 @@ pub use irregular_residual::LaggedWithinResidual; pub use irregular_residual::center_within_unit_event_lags; /// Refuse treating a CWC residual log-rate as raw-process AR drift. pub use irregular_residual::refuse_cwc_residual_log_rate_as_raw_process_drift; +/// Established longitudinal component level. +pub use level::ComponentLevel; +/// Refuse to treat a between-unit component as within-unit change. +pub use level::refuse_between_as_within_change; /// Form consecutive event-time lags after subtracting each occasion's group mean. pub use occasion_mean::center_occasion_mean_event_lags; /// Recover the exact scalar log-rate of occasion-mean residuals. pub use occasion_mean::recover_occasion_mean_centered_irregular_residual_log_rate; /// Refuse treating occasion-mean residual lag as within-person change. pub use occasion_mean::refuse_occasion_mean_centered_log_rate_as_within_person_lag; -/// Established longitudinal component level. -pub use level::ComponentLevel; -/// Refuse to treat a between-unit component as within-unit change. -pub use level::refuse_between_as_within_change; /// Mean exact scalar log-rate on already-centered residuals with stable count weighting. pub use stable_irregular_rate::recover_centered_irregular_residual_log_rate; /// Pairwise-mean exact log-rate after CWC with stable count weighting. pub use stable_irregular_rate::recover_within_unit_irregular_residual_log_rate; +/// Recover the scalar Driver p.16 unstandardised stationary within-person variance. +pub use stationary::recover_stationary_within_variance; /// Recover a valid event-time lagged correlation from covariance and both /// marginal variances through the typed event-time boundary. pub use temporal_association::recover_event_time_lagged_correlation; diff --git a/crates/longitudinal_core/src/stationary.rs b/crates/longitudinal_core/src/stationary.rs index 59238b6f4..fe72cc197 100644 --- a/crates/longitudinal_core/src/stationary.rs +++ b/crates/longitudinal_core/src/stationary.rs @@ -31,7 +31,7 @@ pub(crate) fn validate_stationary_process_inputs( /// actually returns `p`, so the stationary variance itself must be representable /// as binary64. Callers decide whether zero stationary variance is admissible /// for their named estimand. -pub(crate) fn recover_stationary_within_variance( +pub fn recover_stationary_within_variance( continuous_diffusion: f64, log_rate: f64, ) -> Result { diff --git a/crates/longitudinal_core/tests/component_rmse_underflow_contract.rs b/crates/longitudinal_core/tests/component_rmse_underflow_contract.rs index 32704827b..89ab9196c 100644 --- a/crates/longitudinal_core/tests/component_rmse_underflow_contract.rs +++ b/crates/longitudinal_core/tests/component_rmse_underflow_contract.rs @@ -1,3 +1,5 @@ +//! Nonzero known-truth RMSE must not underflow to false perfect recovery. + use longitudinal_core::{ ComponentLevel, ComponentValue, LongitudinalError, component_root_mean_square_error, }; diff --git a/crates/longitudinal_core/tests/correlation_false_perfect_contract.rs b/crates/longitudinal_core/tests/correlation_false_perfect_contract.rs index 15b9f4961..a43c49a79 100644 --- a/crates/longitudinal_core/tests/correlation_false_perfect_contract.rs +++ b/crates/longitudinal_core/tests/correlation_false_perfect_contract.rs @@ -1,4 +1,8 @@ -use longitudinal_core::{EventTimeInterval, LongitudinalError, recover_event_time_lagged_correlation}; +//! Strict-interior covariance cannot become a false exact ±1 correlation. + +use longitudinal_core::{ + EventTimeInterval, LongitudinalError, recover_event_time_lagged_correlation, +}; #[test] fn strict_binary_covariance_bound_cannot_round_to_perfect_correlation() { diff --git a/crates/longitudinal_core/tests/correlation_signed_zero_contract.rs b/crates/longitudinal_core/tests/correlation_signed_zero_contract.rs index 7cd5a16a8..8666d14ce 100644 --- a/crates/longitudinal_core/tests/correlation_signed_zero_contract.rs +++ b/crates/longitudinal_core/tests/correlation_signed_zero_contract.rs @@ -1,3 +1,5 @@ +//! Exact-zero lagged covariance has one public no-association encoding. + use longitudinal_core::{EventTimeInterval, recover_event_time_lagged_correlation}; #[test] diff --git a/crates/longitudinal_core/tests/cwc_signed_zero_contract.rs b/crates/longitudinal_core/tests/cwc_signed_zero_contract.rs new file mode 100644 index 000000000..8571b0a81 --- /dev/null +++ b/crates/longitudinal_core/tests/cwc_signed_zero_contract.rs @@ -0,0 +1,30 @@ +//! Exact-zero CWC residuals have one public encoding. + +use longitudinal_core::{EventTimedObservation, center_within_unit_event_lags}; + +fn timed(unit: u32, event_time: f64, score: f64) -> EventTimedObservation { + EventTimedObservation::new(unit, event_time, score) +} + +#[test] +fn exact_zero_cwc_residuals_use_one_canonical_public_encoding() { + let pairs = center_within_unit_event_lags(&[ + timed(0, 0.0, -1.0), + timed(0, 1.0, -0.0), + timed(0, 2.0, 1.0), + timed(1, 0.0, 4.0), + timed(1, 1.0, 4.0), + timed(1, 2.0, 4.0), + ]) + .expect("finite repeated observations admit CWC lags"); + + assert_eq!(pairs.len(), 4); + assert_eq!(pairs[0].earlier_residual().to_bits(), (-1.0_f64).to_bits()); + assert_eq!(pairs[0].later_residual().to_bits(), 0.0_f64.to_bits()); + assert_eq!(pairs[1].earlier_residual().to_bits(), 0.0_f64.to_bits()); + assert_eq!(pairs[1].later_residual().to_bits(), 1.0_f64.to_bits()); + for pair in &pairs[2..] { + assert_eq!(pair.earlier_residual().to_bits(), 0.0_f64.to_bits()); + assert_eq!(pair.later_residual().to_bits(), 0.0_f64.to_bits()); + } +} diff --git a/crates/longitudinal_core/tests/decomposition_mean_rounding_contract.rs b/crates/longitudinal_core/tests/decomposition_mean_rounding_contract.rs index 67b6767d5..21fb16567 100644 --- a/crates/longitudinal_core/tests/decomposition_mean_rounding_contract.rs +++ b/crates/longitudinal_core/tests/decomposition_mean_rounding_contract.rs @@ -1,3 +1,5 @@ +//! Unit-mean decomposition uses the shared compensated-mean authority. + use longitudinal_core::{ComponentLevel, OccasionObservation, decompose_within_between}; #[test] diff --git a/crates/longitudinal_core/tests/decomposition_signed_zero_contract.rs b/crates/longitudinal_core/tests/decomposition_signed_zero_contract.rs index 124b397be..a14f6d522 100644 --- a/crates/longitudinal_core/tests/decomposition_signed_zero_contract.rs +++ b/crates/longitudinal_core/tests/decomposition_signed_zero_contract.rs @@ -1,3 +1,5 @@ +//! Exact-zero within residuals have one public encoding. + use longitudinal_core::{ComponentLevel, OccasionObservation, decompose_within_between}; #[test] diff --git a/crates/longitudinal_core/tests/intermediate_overflow_regression.rs b/crates/longitudinal_core/tests/intermediate_overflow_regression.rs index 0fb9e158f..fa66edf51 100644 --- a/crates/longitudinal_core/tests/intermediate_overflow_regression.rs +++ b/crates/longitudinal_core/tests/intermediate_overflow_regression.rs @@ -1,3 +1,5 @@ +//! Finite log-rate pairs must survive overflowing raw ratio intermediates. + use longitudinal_core::{ EventTimeInterval, LaggedWithinResidual, recover_centered_irregular_residual_log_rate, }; diff --git a/crates/longitudinal_core/tests/irregular_rate_underflow_contract.rs b/crates/longitudinal_core/tests/irregular_rate_underflow_contract.rs index bc6827776..afe5d0f5c 100644 --- a/crates/longitudinal_core/tests/irregular_rate_underflow_contract.rs +++ b/crates/longitudinal_core/tests/irregular_rate_underflow_contract.rs @@ -1,3 +1,5 @@ +//! Near-equal large residuals keep a finite log-rate instead of underflowing. + use longitudinal_core::{ EventTimeInterval, LaggedWithinResidual, LongitudinalError, recover_centered_irregular_residual_log_rate, diff --git a/crates/longitudinal_core/tests/mixed_sign_mean_rounding_contract.rs b/crates/longitudinal_core/tests/mixed_sign_mean_rounding_contract.rs index 713232199..138838ee8 100644 --- a/crates/longitudinal_core/tests/mixed_sign_mean_rounding_contract.rs +++ b/crates/longitudinal_core/tests/mixed_sign_mean_rounding_contract.rs @@ -1,3 +1,5 @@ +//! Mixed-sign CWC and occasion means divide by the original sample count. + use longitudinal_core::{ EventTimedObservation, center_occasion_mean_event_lags, center_within_unit_event_lags, }; diff --git a/crates/longitudinal_core/tests/occasion_mean_event_time_contract.rs b/crates/longitudinal_core/tests/occasion_mean_event_time_contract.rs index 0c13b0428..80c155071 100644 --- a/crates/longitudinal_core/tests/occasion_mean_event_time_contract.rs +++ b/crates/longitudinal_core/tests/occasion_mean_event_time_contract.rs @@ -1,3 +1,5 @@ +//! Occasion-mean event-time lags are Hamaker Eq. 1a deviations, not CWC. + use longitudinal_core::{ EventTimedObservation, LongitudinalError, center_occasion_mean_event_lags, recover_occasion_mean_centered_irregular_residual_log_rate, From 5c8fbd7c76e42679ea570beed859b0b4e065c049 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 08:36:07 +0000 Subject: [PATCH 164/296] docs(longitudinal): trace signed-zero CWC residual boundary Record the public +0.0 CWC lag contract, IEEE Std 754-2019 authority, and the Curran/Bauer refusal that this identity repair is not raw-process drift. Meredith (1993) and Mislevy (1991) remain unread. --- CHANGELOG.md | 2 ++ CLAUDE.md | 2 +- .../cwc-irregular-residual-log-rate.md | 29 +++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f2b9d42c..eaeccfe23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,8 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang ## [Unreleased] +- `longitudinal_core` canonicalizes exact-zero CWC lag residuals to public `+0.0`. IEEE-754 signed zero after person-mean subtraction is numerically equal to `+0.0` but bit-distinct; a zero within-person deviation has no directional meaning, so exposing both encodings gave one scientific estimand two public identities. `center_within_unit_event_lags` now matches the `decompose_within_between` public-zero contract. Already-centered caller-constructed pairs are unchanged. The crate also exposes the already-implemented unstandardised stationary within-person variance `p = -q / (2 a)` (Driver et al., 2017, Eq. 4 as `Δt → ∞`; p. 16 `asymDIFFUSION`) so standardised maps can keep cancelling `p` without leaving a dead production path. This is not DSEM, not raw-process drift, and not `asymDIFFUSIONstd`. Meredith (1993) remains unread. Mislevy (1991) remains unread (ERIC ED268155 is the 1985/86 report, not the 1991 *Psychometrika* article). + - `longitudinal_core` folds Draft #327 unique CWC-then-irregular residual log-rate onto the Longitudinal Modeling landing vehicle. Consecutive unit-mean-centered pairs keep typed `EventTimeInterval`. The pairwise mean of the Driver, Oud, and Voelkle (2017, Eq. 3; JSS PDF opened 2026-09-02) scalar inverse `a = ln(|later| / |earlier|) / Δt` is taken on nonzero same-sign residuals, with a stable logarithm when that ratio overflows or underflows, and an incremental mean so two finite rates whose raw sum overflows stay representable. Already-centered pairs recover `ln(0.5)`; CWC of a raw AR path does not. Curran and Bauer (2011, pp. 583–619; PMC3059070 XML opened 2026-09-02; Eq. 36) show that person-mean centering of a time-related TVC is biased for the within-person effect; `refuse_cwc_residual_log_rate_as_raw_process_drift` always fails closed. T=2 CWC is `r, −r` (empty admissible). This is not Newton LS, not DSEM, and not a second invariance crate. Voelkle et al. (2012, Eq. 7) remains previously opened lineage; the ZORA PDF was not re-opened this cycle. Meredith (1993) remains unread. Mislevy (1991) remains unread. - `longitudinal_core` adds event-time lagged-correlation standardization that requires lagged covariance plus both occasion-specific marginal variances, enforces the exact binary64 covariance bound, and uses bounded division order to avoid representable overflow/underflow. The invalid one-sided covariance/earlier-variance `expected_autocorrelation` API is removed; this arithmetic boundary does not claim a DSEM/ctsem/state-transition estimator. diff --git a/CLAUDE.md b/CLAUDE.md index b6cdd2e35..63aa12690 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,7 +16,7 @@ Read and follow `AGENTS.md` before changing this repository. The repository-wide - Do not treat raw topic proportions as ordinary Euclidean indicators. Use logistic-normal coordinates or valid log-ratio coordinates and propagate posterior uncertainty into ESEM/DSEM. - Do not treat metric/weak invariance as a latent-mean license. Strong (equal loading and intercept) or strict is required; `#84` `metric` licenses shared metric meaning only. Putnick and Bornstein (2016, PMC5145197 opened 2026-08-19T22:15Z) require scalar invariance before latent-mean comparison; residual invariance is not a prerequisite. Two-observation series have no residual degrees of freedom (`ordinary_least_squares_fit` returns residual variance `0`) and cap at strong/scalar; they still license means. This is two-group OLS, not MGCFA. Meredith (1993) names remain unread labels (Unpaywall/OpenAlex 2026-08-25T11:32Z: closed). - Do not use the difference quotient as a continuous-time rate. The scalar map is `a = ln(φ) / Δt` on event time. Discrete lags from unequal event intervals are not one coefficient; remap them through that log-rate. Binary64 `exp(a Δt) = 0` is not a discrete lag. A constant predictor's discrete effect is Voelkle et al. (2012, Eq. 12), evaluated as `a_yx (expm1(z) / a_xx)` with `z = a_xx Δt` so a finite result is not lost when `z` overflows to `-∞` or when `a_yx Δt` overflows. When `expm1(z)` overflows at a finite `z`, rewrite in log space; a zero continuous effect is exactly zero; an overflowing `a_yx/a_xx` rewrite term fails closed. The first-order product is the underflow limit of that equation, not the general constant-predictor discrete effect. A time-varying predictor whose sampling interval equals its constancy interval uses Voelkle et al. (2012, Eq. 14): `b* = a_yx Δt`. Unmatched intervals fail closed (Oud & Jansen, 2000, unread). Discrete process noise is Driver et al. (2017, Eq. 3): `Q_Δt = 0.5 q (expm1(z) / a)` with `z = 2 (a Δt)` and `q = G G⊤ ≥ 0`; do not form `2 a` first; `a = 0` and `z → 0` recover `q Δt`; a zero diffusion is exactly zero; an overflowing rewrite scale `0.5 q / a` fails closed; this is not a Kalman filter. `Q_Δt` is `cov(η_t | η_{t-1})`, not `Var(η_t)`. The lagged covariance is `exp(a Δt) p` and the unconditional variance is `exp(2 a Δt) p + Q_Δt` (Driver et al., 2017, Eq. 3–4, pp. 4–5; JSS has no numbered §2.2). A zero diffusion whose `2 (a Δt)` overflows to `+∞` is not a finite `Var(η_t)`. The stationary within-subject variance is the `Δt → ∞` limit of Eq. 4: `-q / (2 a)` for stable `a < 0` (JSS p. 16 `asymDIFFUSION`; §4.3). When `2 a` is finite, form `q / -(2 a)` so `q / a` overflow does not lose a finite result (`q = MAX`, `a = -0.75` → `MAX / 1.5`). When `2 a` overflows, form `(q * 0.5) / |a|`. In that branch `|a|` is already so large that halving a minimum-subnormal `q` cannot discard a representable positive final stationary variance; the ratio-first rewrite can instead overflow before the final factor restores a representable result. `a ≥ 0` has no finite stationary variance. Finite-interval `Q_Δt` is not that limit. Trait-plus-state variance is `trait + state` and lagged covariance is `trait + exp(a Δt) p` (Driver et al., 2017, §4.3, p. 9). Trait variance is not process noise and not `asymDIFFUSION`. Evolving the summed variance as if it were all state is not that map. This is not RI-CLPM. Observed-indicator variance is `λ² Var(η) + θ` when `MANIFESTTRAITVAR` is zero and `λ² Var(η) + θ + ψ` otherwise (Driver et al., 2017, Eq. 5, p. 5; Table 2, p. 12). Lagged observed covariance is `λ² cov(η_t, η_{t-1}) + ψ`; `MANIFESTVAR` does not enter. Observed-indicator mean is `τ + λ μ` (Driver et al., 2017, Eq. 5; Table 2, p. 12). `MANIFESTMEANS` is `τ`, not `E(y)`. `E(η)` is not `E(y)`. `CINT` is not `MANIFESTMEANS`. `T0MEANS` is not `E(y)`. The discrete latent mean is `μ_t = exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ` (Driver et al., 2017, Eq. 3, p. 4; Table 2, p. 12). `T0MEANS` is not `μ_t`. `CINT` is not that discrete increment. A zero drift is `κ Δt`. Underflow of `exp(a Δt)` to `+0` drops the carried `T0MEANS` and keeps `−κ / a`. The evolved observed mean is `τ + λ μ_t` (Driver et al., 2017, Eq. 5 of that Eq. 3 map). The first-occasion map `τ + λ μ_0` is not `E(y_t)`. `μ_t` is not `E(y_t)`. The contemporaneous time-dependent predictor impulse is `m x` (Driver et al., 2017, Eq. 3 fourth summand; Table 2 `TDPREDEFFECT` is `M`). Form `μ_t` first, then add `m x`. `TDPREDEFFECT` is not `CINT`. `M x` is not `A^{-1}[e^{A Δt} − I] B z` and is not Voelkle et al. (2012, Eq. 14). The §7.2 level-change form is not that impulse. The observed mean of that contemporaneous impulse is `τ + λ(μ_t + m x)` (Driver et al., 2017, Eq. 5 of the Eq. 3 fourth-summand composition). The evolved map `τ + λ μ_t` is not that observed mean. The carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`. The evolved-plus-impulse latent mean is not `E(y_t)`. The time-independent predictor increment is `A^{-1}[e^{A Δt} − I] B z` (Driver et al., 2017, Eq. 3 second summand; Table 2 `TIPREDEFFECT` is `B`). Form `B z` first, then the discrete intercept map. A zero drift is `B z Δt`. `TIPREDEFFECT` is `B`, not that discrete increment. `A^{-1}[e^{A Δt} − I] B z` is not `CINT`, not `M x`, and not Voelkle et al. (2012, Eq. 14). The observed mean of that increment is `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` (Driver et al., 2017, Eq. 5 of the Eq. 3 printed addend after the `T0MEANS` carry and the `CINT` increment). The evolved map `τ + λ μ_t` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`. The evolved-plus-increment latent mean is not `E(y_t)`. The within-interval time-dependent impulse carry is `e^{A(t−u)} M x` for `t0 < u < t` (Driver et al., 2017, Eq. 1–2 Green-function integral of Eq. 2; §7.2 dissipation). Form `m x` first, then `e^{a(t−u)} m x`. A zero drift is `m x` with no dissipation. Underflow of `e^{a(t−u)}` to `+0` is vanishing dissipation and is kept. `e^{A(t−u)} M x` is not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle et al. (2012, Eq. 14). An impulse at `u = t` is the contemporaneous map. An impulse at `u ≤ t0` is already in `η(t0)`. The observed mean of that carry is `τ + λ(μ_t + e^{a(t−u)} m x)` (Driver et al., 2017, Eq. 5 of the Eq. 1–2 carried latent mean). The evolved map `τ + λ μ_t` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean when `u ≠ t`. `MANIFESTMEANS` is not `E(y_t)`. The carried latent mean is not `E(y_t)`. The first-occasion time-independent predictor shift is `t0_b z` (Driver et al., 2017, Table 3 `T0TIPREDEFFECT`; Eq. 3 first summand). Form `t0_b z` first, then `e^{a Δt} t0_b z`. Form `μ_t` first, then add that carry. A zero drift is `t0_b z`. Underflow of `e^{a Δt}` to `+0` is a vanishing carry of the first-occasion shift and is kept. `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`. `e^{A Δt} t0_b z` is not `t0_b z`. `T0TIPREDEFFECT` is the coefficient, not the shift. The observed mean of that first-occasion carry is `τ + λ(μ_t + e^{a Δt} t0_b z)` (Driver et al., 2017, Eq. 5 of the Table 3 / Eq. 3 first-summand composition). The evolved map `τ + λ μ_t` is not that observed mean. The process-increment map `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The impulse-carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t0`. The evolved-plus-carry latent mean is not `E(y_t)`. The first-occasion time-dependent predictor shift is `t0_m x0` (Driver et al., 2017, Table 3 `T0TDPREDEFFECT`; Eq. 3 first summand; JSS PDF re-opened 2026-08-20T19:10Z). Form `t0_m x0` first, then `e^{a Δt} t0_m x0`. Form `μ_t` first, then add that carry. A zero drift is `t0_m x0`. Underflow of `e^{a Δt}` to `+0` is a vanishing carry of the first-occasion shift and is kept. `t0_m x0` is not `M x`, not `e^{A(t−u)} M x` for `t0 < u < t`, not `t0_b z`, not `A^{-1}[e^{A Δt} − I] B z`, and not `CINT`. `e^{A Δt} t0_m x0` is not `t0_m x0`. `T0TDPREDEFFECT` is the coefficient, not the shift. An impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`. The observed mean of that first-occasion TD carry is `τ + λ(μ_t + e^{a Δt} t0_m x0)` (Driver et al., 2017, Eq. 5 of the Table 3 / Eq. 3 first-summand TD composition; JSS PDF re-opened 2026-08-20T19:07Z). The evolved map `τ + λ μ_t` is not that observed mean. The process-increment map `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The impulse-carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t0`. The first-occasion TI map `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean. The evolved-plus-carry latent mean is not `E(y_t)`. The lasting level-change `CINT` is `κ = −a m x` (Driver et al., 2017, §7.2, pp. 20–21; JSS PDF re-opened 2026-08-20T19:45Z). Form `m x` first, then multiply by `−a`. Stable `a < 0` is required so `−κ / a = m x` is an equilibrium offset. `a ≥ 0` cannot hold a new process mean. `−a m x` is not the dissipating Dirac `m x`, not a free `CINT`, and not `A^{-1}[e^{A Δt} − I] B z`. The extra near-zero-drift latent process also named in §7.2 is a different specification and is not this `CINT` setting. Equation 3 maps that intercept as `(1 − e^{a Δt}) m x` (JSS PDF re-opened 2026-08-20T19:50Z). Form the level-change `CINT` first, then the discrete intercept map. Underflow of `e^{a Δt}` to `+0` keeps `m x`. `(1 − e^{a Δt}) m x` is not `m x`, not `κ`, and not `A^{-1}[e^{A Δt} − I] B z`. The printed §7.2 lasting level change is an extra near-zero-drift latent process (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-20T23:10Z). `T0MEANS`, `CINT`, `T0VAR`, `DIFFUSION`, and `TRAITVAR` of that process are fixed to 0; `TDPREDEFFECT` on it is fixed to 1; its `DRIFT` diagonal is very close to 0 (printed example `−0.000001`; precisely 0 causes computational problems); the original process is driven by the `DRIFT` coupling `a_{ηξ}`. After a unit identification impulse the scalar contribution is `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (`ε = a` is `a_{ηξ} x Δt e^{a Δt}`). Form `a_{ηξ} x` first. A zero coupling or zero predictor is exactly zero. `ε ≥ 0` fails closed. That contribution is not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`. The observed mean of that extra-process contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a))` (Driver et al., 2017, Eq. 5 of that §7.2 contribution; JSS PDF re-opened 2026-08-21T06:12Z). The extra process has `LAMBDA` 0 and is not an observed indicator. Original indicators load on the original process after the `DRIFT` coupling. The evolved map `τ + λ μ_t` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The contribution is not `E(y_t)`. The evolved-plus-contribution latent mean is not `E(y_t)`. `T0TDPREDEFFECT` on the extra process begins at `t = 0` and uses `Δt = t − t0` for both the original-process evolution and the extra drive. `TDPREDEFFECT` after `t0` uses `t − u` with `t0 < u < t` for the extra drive while `μ_t` still uses `Δt`. The observed mean of that after-t0 extra-process contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a))` (Driver et al., 2017, Eq. 5 of that §7.2 after-t0 contribution; JSS PDF re-opened 2026-08-21T06:32Z). The first-occasion extra-process observed mean is not that observed mean when `u ≠ t0`. The impulse-carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is a Dirac on the original process and is not that `DRIFT` drive. An impulse at `u = t0` or `u = t` is not interior. The asymptotic time-independent predictor effect is `-B z / a` (Driver et al., 2017, §7.2, pp. 20–21; JSS PDF opened 2026-08-21T13:08Z). Form `B z` first, then divide by `-a`. Stable `a < 0` is required. `a ≥ 0` cannot hold a finite process-mean change. `-B z / a` is not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`. The asymptotic time-independent predictor variance is `(B / a)² v` (Driver et al., 2017, §7.2, pp. 20–21 `addedTIPREDVAR`). Form the unit asymptotic effect first, then square, then multiply by `v`. `(B / a)² v` is not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`. The asymptotic continuous intercept is `-κ / a` (Driver et al., 2017, Table 2, p. 12 `asymCINT`; Eq. 3 as `Δt → ∞`; JSS PDF opened 2026-08-21T16:13Z). Form `κ` first, then divide by `-a`. Stable `a < 0` is required. `-κ / a` is not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`. The p. 16 stationary `T0MEANS` constraint is `-κ / a + −B z / a`. Form the intercept contribution first, then include the TI extra effect, then add. That constrained first-occasion mean is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean. Equation 5 of that constrained mean is `τ + λ(−κ / a + −B z / a)` (Driver et al., 2017, §4.3, pp. 9–10; Eq. 5, p. 5; JSS PDF re-opened 2026-08-21T20:07Z). Form the stationary latent mean first, then `τ + λ` of that mean. `τ + λ μ_0` for free `T0MEANS` is not that composition. `τ + λ(−κ / a)` is not that composition when `B z ≠ 0`. `τ + λ μ_t` is not that composition. `MANIFESTMEANS` is not `E(y_0)`. The constrained latent mean is not `E(y_0)`. The p. 16 constrained first-occasion variance `trait + −q / (2 a) + (B / a)² v` is not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`). The lagged covariance of that constrained process is `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T19:13Z). Trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`. Contemporaneous `T0VAR` is not that lagged map. Decaying the constrained total as if it were all state is not that lagged map. Equation 5 of that lagged covariance is `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ`. `Θ` does not enter. Contemporaneous `Var(y_0)` is not that lagged observed covariance. The lagged latent covariance is not that observed covariance. The later-occasion variance of that constrained process is `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T23:12Z). Trait and `addedTIPREDVAR` do not enter `Q_Δt`. Under stationarity that composition equals contemporaneous `T0VAR`. Evolving the constrained total as if it were all state is not that later map. The lagged covariance omits `Q_Δt` and is not that later map. `Q_Δt` is not that later map. Equation 5 of that later-occasion variance is `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ`. The lagged observed covariance omits `Q_Δt` and `θ`. `MANIFESTVAR` is not `Var(y_t)`. The later-occasion latent variance is not `Var(y_t)`. The later-occasion variance of §4.3 predetermined `T0VAR` is `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T05:12Z). Trait and `addedTIPREDVAR` do not enter `Q_Δt`. Free `T0VAR` `p_0` is not that later map. Setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map. Stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free. Evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map. As `Δt → ∞` with stable `a < 0` the composition approaches contemporaneous stationary `T0VAR`. As `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`. Nonzero diffusion with `a ≥ 0` is a growing process and is kept. Equation 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ`. `MANIFESTVAR` is not `Var(y_t)`. The predetermined later-occasion latent variance is not `Var(y_t)`. Stationary later observed variance is not that observed variance when `p_0` is free. The lagged covariance of §4.3 predetermined `T0VAR` is `trait + e^{a Δt} p_0 + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T09:04Z). Trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`. Free `T0VAR` `p_0` is not that lagged map. Setting `p_0 = −q / (2 a)` recovers the stationary lagged map. Stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free. Evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map. Later-occasion variance includes `Q_Δt` and is not that lagged map. As `Δt → ∞` with stable `a < 0` the state term vanishes. As `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`. Equation 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ`. `MANIFESTVAR` does not enter. The predetermined lagged latent covariance is not that observed covariance. Predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance. Stationary lagged observed covariance is not that observed covariance when `p_0` is free. The predetermined first-occasion variance of §4.3 predetermined `T0VAR` is `trait + p_0 + (B / a)² v`. Free `p_0` is not that map. Stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free. Lagged covariance decays the state and is not that map. Later-occasion variance includes `Q_Δt` and is not that map. Equation 5 of that predetermined first-occasion variance is `λ²(trait + p_0 + (B / a)² v) + θ + ψ`. `MANIFESTVAR` is not that first-occasion observed variance. The predetermined first-occasion latent variance is not that observed variance. Stationary first-occasion observed variance is not that observed variance when `p_0` is free. Predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance. Later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z). First-occasion lagged omits `e^{a s} Q_u`. Later-occasion variance does not lag. Stationary lagged uses `−q / (2 a)`. Decaying the later total is not that map. Equation 5 of that later-start lagged covariance is `λ²` of it plus `ψ`. Independent `ε_t` does not enter. First-occasion lagged observed omits `e^{a s} Q_u`. Predetermined later observed variance includes `Q_u` and `θ` and is not that later-start lagged observed covariance. Later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z). Later-occasion variance at `u` omits `Q_s`. Later-start lagged covariance omits `Q_s`. Stationary later uses `−q / (2 a)`. Evolving the later total as if it were all state is not that map. Ignoring `startoffset` omits `e^{2 a s} Q_u`. Equation 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`. `MANIFESTVAR` is not that observed variance. Page 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; footnote 4; §7.1; JSS PDF re-opened 2026-08-23T11:40Z). Unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`. The §7.1 trait-plus-state autocorrelation `(trait + e^{a Δt} p + added) / (trait + p + added)` uses `TRAITVAR` and is not `discreteDRIFTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:06Z). Unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`. The continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`. `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`. `TRAITVAR` is not the standardisation variance. Page 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z). Unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`. The discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`. `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`. `TRAITVAR` is not the standardisation variance. Page 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z). Unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`. The discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`. `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z). Unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`. The finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`. `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z). Unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`. The asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`. The finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`. `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Page 16 / Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z). The affected variance is free first-occasion `T0VAR`, not `asymDIFFUSION`. Unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`. `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`. `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`. `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. 2017-era `addedT0TIPREDVAR` is `t0_b² v` after a first-occasion time-independent predictor (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z). Form `t0_b` first, then square, then multiply by `v`. A zero coefficient or zero predictor variance is exactly zero. Free `T0TIPREDEFFECT` does not require `a < 0`. `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map. `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance. Free `T0VAR` is not this extra TI variance. `TRAITVAR` is not this extra TI variance. Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z). Form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`. A zero loading or zero extra is exactly zero. `t0_b² v` is the latent extra, not the observed extra. `λ² p_0 + θ` is first-occasion observed variance, not this extra. `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra. `MANIFESTVAR` `θ` is not this extra. Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v` (Driver et al., 2017, Eq. 5, p. 5; Table 2, p. 12; §7.2, pp. 20–21; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:23Z). Form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`. A zero loading or zero extra is exactly zero. Lasting asymptotic extra requires `a < 0`. `(B / a)² v` is the latent extra, not the observed extra. `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra. `λ² p + θ` is stationary observed variance, not this extra. `MANIFESTVAR` `θ` is not this extra. Page 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance. Unstandardised `M` is not `TDPREDEFFECTstd`. `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`. intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`. `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`. Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance. Unstandardised `t0_m` is not `T0TDPREDEFFECTstd`. `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`. `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`. `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`. Free `T0VAR` does not require `a < 0`. Page 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; the default ridge is 0). Unstandardised `T0VAR` is not `T0VARstd`. `T0TDPREDEFFECTstd` is not `T0VARstd`. `addedT0TIPREDVAR` is not `T0VARstd`. Page 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend). Unstandardised `TRAITVAR` is not `TRAITVARstd`. `T0VARstd` is not `TRAITVARstd` even when both equal 1. `addedT0TIPREDVAR` is not `TRAITVARstd`. Page 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0). Unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`. `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1. `MANIFESTVAR` is not `MANIFESTTRAITVARstd`. Page 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug). Unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`. `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1. Equation 5 `Var(y)` is not `MANIFESTVARstd`. Page 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`). Unstandardised `TIPREDVAR` is not `TIPREDVARstd`. `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1. Section 7.2 `addedTIPREDVAR` is not `TIPREDVARstd`. Page 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`). Unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`. `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1. `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`. Page 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`. Unstandardised `discreteCINT` is not `discreteCINTstd`. `κ / √p` is not `discreteCINTstd`. `(-κ / a) / √p` is not `discreteCINTstd`. `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`. Unstandardised `asymCINT` is not `asymCINTstd`. `κ / √p` is not `asymCINTstd`. `discreteCINTstd` is not `asymCINTstd`. `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`. Unstandardised `T0MEANS` is not `T0MEANSstd`. `T0VARstd` is not `T0MEANSstd`. `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`. Page 16 `MANIFESTMEANSstd` is `τ / √θ` after strictly positive `MANIFESTVAR`. Unstandardised `MANIFESTMEANS` is not `MANIFESTMEANSstd`. `MANIFESTVARstd` is not `MANIFESTMEANSstd`. `τ / √(λ² Var(η) + θ)` is not `MANIFESTMEANSstd`. Page 16 `CINTstd` is `κ / √p` after strictly positive `asymDIFFUSION`. Unstandardised `CINT` is not `CINTstd`. `asymCINTstd` is not `CINTstd`. `discreteCINTstd` is not `CINTstd`. `κ / √(trait + p + added)` is not `CINTstd`. Evolving from that stationary start with `CINT` and `TIPREDEFFECT` stays at the stationary mean. Equation 1 is the latent SDE, not the measurement model. Form `(λ p) λ` then add `θ`, then add `ψ`. `MANIFESTVAR` is `Θ`, not `Var(y)`. `MANIFESTTRAITVAR` is `Ψ_τ`, not `Θ`. `TRAITVAR` is latent and scaled by `λ²`. `Var(η)` is not `Var(y)`. -- Separate cluster means before within-unit lag. CWC plus an event-time lag is not DSEM. Subtracting the person-specific mean from a raw autoregressive series does not isolate the lagged within-person effect (Curran & Bauer, 2011, pp. 583–619; PMC3059070 XML opened 2026-09-02; Eq. 36). `longitudinal_core` owns CWC-then-pairwise-mean residual log-rate on typed `EventTimeInterval`; for an already-centered `(1, 0.5)` pair it recovers `ln(0.5) / Δt`, equal to `ln(0.5)` only when `Δt = 1`. CWC log-rate is not raw-process drift. +- Separate cluster means before within-unit lag. CWC plus an event-time lag is not DSEM. Subtracting the person-specific mean from a raw autoregressive series does not isolate the lagged within-person effect (Curran & Bauer, 2011, pp. 583–619; PMC3059070 XML opened 2026-09-02; Eq. 36). `longitudinal_core` owns CWC-then-pairwise-mean residual log-rate on typed `EventTimeInterval`; for an already-centered `(1, 0.5)` pair it recovers `ln(0.5) / Δt`, equal to `ln(0.5)` only when `Δt = 1`. Exact zero CWC residuals use canonical public `+0.0`. CWC log-rate is not raw-process drift. - Do not treat the CWC cluster-mean coefficient as the between-cluster effect. It is the contextual effect `between − within` (Enders & Tofighi, 2007, Table 2, pp. 124–127). - Never use future-available evidence in historical model fits. - Do not blanket-mask PII when identity/role/linkage is scientifically required. Follow the purpose-bound separation, opaque-ID, encryption, retention, and audit contract in `docs/PRIVACY_DATA_GOVERNANCE.md`. diff --git a/docs/research/cwc-irregular-residual-log-rate.md b/docs/research/cwc-irregular-residual-log-rate.md index 5b4253938..892561e6d 100644 --- a/docs/research/cwc-irregular-residual-log-rate.md +++ b/docs/research/cwc-irregular-residual-log-rate.md @@ -90,6 +90,32 @@ non-representable means, overflowing CWC residuals after a finite mean, tiny intervals with huge log-ratios, underflowed nonzero final rates, and the Curran refusal. +Exact zero CWC residuals also have one public identity. IEEE 754 binary64 +has distinct `+0.0` and `-0.0` encodings, and subtraction from a canonical +`+0.0` unit mean can therefore leave `-0.0` when an observed score is +signed zero or otherwise equals that mean. That sign bit does not represent +positive versus negative within-person change: the person-mean deviation is +exactly zero. The public CWC lag boundary therefore canonicalizes only +validated exact-zero residuals to `+0.0`; private numerical intermediates +and caller-constructed already-centered pairs remain free to retain signed +zero. IEEE Std 754-2019 remains the active published floating-point +standard while IEEE P754 is the active revision project as of 2026-09-03. +This is the same public-identity contract as +`decompose_within_between`; it is not a license to treat CWC residuals as +raw-process drift (Curran & Bauer, 2011, Eq. 36). + +Signed-zero traceability: + +- RED `crates/longitudinal_core/tests/cwc_signed_zero_contract.rs` drives + `center_within_unit_event_lags` with a `-0.0` score equal to the unit + mean so the public later/earlier residual pair cannot keep `-0.0`. +- Causal repair `f98ee093f6f7fd318ad6623ab44313385195f956` canonicalizes an exact-zero CWC residual only + after finite-result validation. +- Standard authority — IEEE. (2019). *IEEE standard for floating-point + arithmetic* (IEEE Std 754-2019). IEEE. The canonical repository register + is `docs/research/standards-and-literature.md`. + + The current public numerical regressions include same-sign raw-sum overflow, full-exponent mixed-sign cancellation, minimum-subnormal cancellation, halfway ties-to-even for same-sign means, and the mixed-sign `-31u/3` case @@ -110,3 +136,6 @@ Voelkle, M. C., Oud, J. H. L., Davidov, E., & Schmidt, P. (2012). An SEM approach to continuous time modeling of panel data: Relating authoritarianism and anomia. *Psychological Methods, 17*(2), 176–192. https://doi.org/10.1037/a0027543 + +IEEE. (2019). *IEEE standard for floating-point arithmetic* (IEEE Std +754-2019). IEEE. From 238c4def7a5a5f19963d2e5cc936dd750d55c5a1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 08:42:22 +0000 Subject: [PATCH 165/296] fix(longitudinal): canonicalize exact-zero occasion-mean residuals Hamaker Eq. 1a occasion-mean residuals that are exact zero now use one public +0.0 encoding, matching CWC and within/between decomposition. --- crates/longitudinal_core/src/occasion_mean.rs | 17 +++++++++-- .../occasion_mean_signed_zero_contract.rs | 30 +++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 crates/longitudinal_core/tests/occasion_mean_signed_zero_contract.rs diff --git a/crates/longitudinal_core/src/occasion_mean.rs b/crates/longitudinal_core/src/occasion_mean.rs index 1a8cb529e..3cad3a62f 100644 --- a/crates/longitudinal_core/src/occasion_mean.rs +++ b/crates/longitudinal_core/src/occasion_mean.rs @@ -22,6 +22,11 @@ use crate::{EventTimeInterval, LongitudinalError}; /// units must contribute a consecutive lag. Occasion means use the same /// Longitudinal-local overflow-safe, cancellation-safe, halfway-rounding /// numerical authority as CWC means and are bit-stable under row permutation. +/// Exact zero occasion-mean residuals use canonical public `+0.0` because a +/// zero Hamaker Eq. 1a deviation has no directional measurement meaning; +/// signed zero remains available to private numerical intermediates and to +/// caller-constructed already-centered pairs. The returned pairs still retain +/// between-person differences and are not within-person or RI-CLPM lags. /// /// # Errors /// @@ -91,8 +96,16 @@ pub fn center_occasion_mean_event_lags( } let event_interval = EventTimeInterval::new(later.event_time() - earlier.event_time())?; pairs.push(LaggedWithinResidual::new( - earlier_residual, - later_residual, + if earlier_residual == 0.0 { + 0.0 + } else { + earlier_residual + }, + if later_residual == 0.0 { + 0.0 + } else { + later_residual + }, event_interval, )); } diff --git a/crates/longitudinal_core/tests/occasion_mean_signed_zero_contract.rs b/crates/longitudinal_core/tests/occasion_mean_signed_zero_contract.rs new file mode 100644 index 000000000..f0835aa21 --- /dev/null +++ b/crates/longitudinal_core/tests/occasion_mean_signed_zero_contract.rs @@ -0,0 +1,30 @@ +//! Exact-zero occasion-mean residuals have one public encoding. + +use longitudinal_core::{EventTimedObservation, center_occasion_mean_event_lags}; + +fn timed(unit: u32, event_time: f64, score: f64) -> EventTimedObservation { + EventTimedObservation::new(unit, event_time, score) +} + +#[test] +fn exact_zero_occasion_mean_residuals_use_one_canonical_public_encoding() { + let pairs = center_occasion_mean_event_lags(&[ + timed(0, 0.0, -1.0), + timed(0, 1.0, -0.0), + timed(0, 2.0, 1.0), + timed(1, 0.0, 1.0), + timed(1, 1.0, 0.0), + timed(1, 2.0, -1.0), + ]) + .expect("finite aligned occasions admit Hamaker Eq. 1a lags"); + + assert_eq!(pairs.len(), 4); + assert_eq!(pairs[0].earlier_residual().to_bits(), (-1.0_f64).to_bits()); + assert_eq!(pairs[0].later_residual().to_bits(), 0.0_f64.to_bits()); + assert_eq!(pairs[1].earlier_residual().to_bits(), 0.0_f64.to_bits()); + assert_eq!(pairs[1].later_residual().to_bits(), 1.0_f64.to_bits()); + assert_eq!(pairs[2].earlier_residual().to_bits(), 1.0_f64.to_bits()); + assert_eq!(pairs[2].later_residual().to_bits(), 0.0_f64.to_bits()); + assert_eq!(pairs[3].earlier_residual().to_bits(), 0.0_f64.to_bits()); + assert_eq!(pairs[3].later_residual().to_bits(), (-1.0_f64).to_bits()); +} From 884030d96eff62392517bbb53b02fea0b19f517e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 08:42:55 +0000 Subject: [PATCH 166/296] docs(longitudinal): trace signed-zero occasion-mean residual boundary Record the public +0.0 Hamaker Eq. 1a residual contract, IEEE Std 754-2019 authority, and the refusal that this identity repair is not a within-person or RI-CLPM lag. Meredith (1993) and Mislevy (1991) remain unread. --- CHANGELOG.d/longitudinal-occasion-mean-event-time.md | 1 + CHANGELOG.md | 2 ++ CLAUDE.md | 1 + docs/research/occasion-mean-event-time-composition.md | 6 +++++- 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.d/longitudinal-occasion-mean-event-time.md b/CHANGELOG.d/longitudinal-occasion-mean-event-time.md index 977d32137..3a1227fbb 100644 --- a/CHANGELOG.d/longitudinal-occasion-mean-event-time.md +++ b/CHANGELOG.d/longitudinal-occasion-mean-event-time.md @@ -2,5 +2,6 @@ - `longitudinal_core` adds Hamaker, Kuiper, and Grasman (2015, Eq. 1a) occasion-mean event-time composition. For each admitted occasion it forms `p_it = x_it - mu_t` from the occasion-specific group mean, not a person mean or sample-wide grand mean; those deviations retain between-person differences and therefore are not evidence of a within-person or RI-CLPM lag. - Occasion identity is numeric, so `-0.0` and `+0.0` are one occasion. Each admitted occasion must contain at least two distinct units, duplicate unit-occasion membership fails closed, and at least two units must contribute a consecutive lag. +- Exact zero Hamaker Eq. 1a residuals use canonical public `+0.0`. IEEE-754 signed zero after subtracting a canonical occasion mean is not a directional deviation; exposing both encodings would give one scientific estimand two public identities. Already-centered caller-constructed pairs stay as given. These residuals still retain between-person differences and are not within-person or RI-CLPM lags. - Occasion means reuse the Longitudinal-local scaled compensated mean used by CWC/decomposition; event intervals use the typed finite-positive `EventTimeInterval`, and log-rate recovery reuses the existing exact scalar irregular-residual boundary. Non-finite, sparse, duplicate, nonpositive-interval, or nonrepresentable inputs fail closed rather than being reinterpreted as within-person change. - This fragment preserves the valid operational release note from PR #486 while its wrong-owner `psychometric_core` implementation, raw signed-zero bit identity, naive `sum/count` mean, and mixed-timezone provenance wording remain intentionally unadopted. Canonical root `CHANGELOG.md` assembly remains a landing prerequisite rather than being inferred from this fragment. diff --git a/CHANGELOG.md b/CHANGELOG.md index eaeccfe23..f761d9629 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,8 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang ## [Unreleased] +- `longitudinal_core` canonicalizes exact-zero Hamaker Eq. 1a occasion-mean residuals to public `+0.0`. IEEE-754 signed zero after subtracting a canonical `+0.0` occasion mean is numerically equal to `+0.0` but bit-distinct; a zero occasion-mean deviation has no directional meaning, so exposing both encodings gave one scientific estimand two public identities. `center_occasion_mean_event_lags` now matches the CWC and `decompose_within_between` public-zero contracts. Already-centered caller-constructed pairs are unchanged. These residuals still retain between-person differences and are not within-person, RI-CLPM, or DSEM lags (Hamaker, Kuiper, & Grasman, 2015, Eq. 1a). Meredith (1993) remains unread. Mislevy (1991) remains unread (ERIC ED268155 is the 1985/86 report, not the 1991 *Psychometrika* article). + - `longitudinal_core` canonicalizes exact-zero CWC lag residuals to public `+0.0`. IEEE-754 signed zero after person-mean subtraction is numerically equal to `+0.0` but bit-distinct; a zero within-person deviation has no directional meaning, so exposing both encodings gave one scientific estimand two public identities. `center_within_unit_event_lags` now matches the `decompose_within_between` public-zero contract. Already-centered caller-constructed pairs are unchanged. The crate also exposes the already-implemented unstandardised stationary within-person variance `p = -q / (2 a)` (Driver et al., 2017, Eq. 4 as `Δt → ∞`; p. 16 `asymDIFFUSION`) so standardised maps can keep cancelling `p` without leaving a dead production path. This is not DSEM, not raw-process drift, and not `asymDIFFUSIONstd`. Meredith (1993) remains unread. Mislevy (1991) remains unread (ERIC ED268155 is the 1985/86 report, not the 1991 *Psychometrika* article). - `longitudinal_core` folds Draft #327 unique CWC-then-irregular residual log-rate onto the Longitudinal Modeling landing vehicle. Consecutive unit-mean-centered pairs keep typed `EventTimeInterval`. The pairwise mean of the Driver, Oud, and Voelkle (2017, Eq. 3; JSS PDF opened 2026-09-02) scalar inverse `a = ln(|later| / |earlier|) / Δt` is taken on nonzero same-sign residuals, with a stable logarithm when that ratio overflows or underflows, and an incremental mean so two finite rates whose raw sum overflows stay representable. Already-centered pairs recover `ln(0.5)`; CWC of a raw AR path does not. Curran and Bauer (2011, pp. 583–619; PMC3059070 XML opened 2026-09-02; Eq. 36) show that person-mean centering of a time-related TVC is biased for the within-person effect; `refuse_cwc_residual_log_rate_as_raw_process_drift` always fails closed. T=2 CWC is `r, −r` (empty admissible). This is not Newton LS, not DSEM, and not a second invariance crate. Voelkle et al. (2012, Eq. 7) remains previously opened lineage; the ZORA PDF was not re-opened this cycle. Meredith (1993) remains unread. Mislevy (1991) remains unread. diff --git a/CLAUDE.md b/CLAUDE.md index 63aa12690..a35b5aeef 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,6 +17,7 @@ Read and follow `AGENTS.md` before changing this repository. The repository-wide - Do not treat metric/weak invariance as a latent-mean license. Strong (equal loading and intercept) or strict is required; `#84` `metric` licenses shared metric meaning only. Putnick and Bornstein (2016, PMC5145197 opened 2026-08-19T22:15Z) require scalar invariance before latent-mean comparison; residual invariance is not a prerequisite. Two-observation series have no residual degrees of freedom (`ordinary_least_squares_fit` returns residual variance `0`) and cap at strong/scalar; they still license means. This is two-group OLS, not MGCFA. Meredith (1993) names remain unread labels (Unpaywall/OpenAlex 2026-08-25T11:32Z: closed). - Do not use the difference quotient as a continuous-time rate. The scalar map is `a = ln(φ) / Δt` on event time. Discrete lags from unequal event intervals are not one coefficient; remap them through that log-rate. Binary64 `exp(a Δt) = 0` is not a discrete lag. A constant predictor's discrete effect is Voelkle et al. (2012, Eq. 12), evaluated as `a_yx (expm1(z) / a_xx)` with `z = a_xx Δt` so a finite result is not lost when `z` overflows to `-∞` or when `a_yx Δt` overflows. When `expm1(z)` overflows at a finite `z`, rewrite in log space; a zero continuous effect is exactly zero; an overflowing `a_yx/a_xx` rewrite term fails closed. The first-order product is the underflow limit of that equation, not the general constant-predictor discrete effect. A time-varying predictor whose sampling interval equals its constancy interval uses Voelkle et al. (2012, Eq. 14): `b* = a_yx Δt`. Unmatched intervals fail closed (Oud & Jansen, 2000, unread). Discrete process noise is Driver et al. (2017, Eq. 3): `Q_Δt = 0.5 q (expm1(z) / a)` with `z = 2 (a Δt)` and `q = G G⊤ ≥ 0`; do not form `2 a` first; `a = 0` and `z → 0` recover `q Δt`; a zero diffusion is exactly zero; an overflowing rewrite scale `0.5 q / a` fails closed; this is not a Kalman filter. `Q_Δt` is `cov(η_t | η_{t-1})`, not `Var(η_t)`. The lagged covariance is `exp(a Δt) p` and the unconditional variance is `exp(2 a Δt) p + Q_Δt` (Driver et al., 2017, Eq. 3–4, pp. 4–5; JSS has no numbered §2.2). A zero diffusion whose `2 (a Δt)` overflows to `+∞` is not a finite `Var(η_t)`. The stationary within-subject variance is the `Δt → ∞` limit of Eq. 4: `-q / (2 a)` for stable `a < 0` (JSS p. 16 `asymDIFFUSION`; §4.3). When `2 a` is finite, form `q / -(2 a)` so `q / a` overflow does not lose a finite result (`q = MAX`, `a = -0.75` → `MAX / 1.5`). When `2 a` overflows, form `(q * 0.5) / |a|`. In that branch `|a|` is already so large that halving a minimum-subnormal `q` cannot discard a representable positive final stationary variance; the ratio-first rewrite can instead overflow before the final factor restores a representable result. `a ≥ 0` has no finite stationary variance. Finite-interval `Q_Δt` is not that limit. Trait-plus-state variance is `trait + state` and lagged covariance is `trait + exp(a Δt) p` (Driver et al., 2017, §4.3, p. 9). Trait variance is not process noise and not `asymDIFFUSION`. Evolving the summed variance as if it were all state is not that map. This is not RI-CLPM. Observed-indicator variance is `λ² Var(η) + θ` when `MANIFESTTRAITVAR` is zero and `λ² Var(η) + θ + ψ` otherwise (Driver et al., 2017, Eq. 5, p. 5; Table 2, p. 12). Lagged observed covariance is `λ² cov(η_t, η_{t-1}) + ψ`; `MANIFESTVAR` does not enter. Observed-indicator mean is `τ + λ μ` (Driver et al., 2017, Eq. 5; Table 2, p. 12). `MANIFESTMEANS` is `τ`, not `E(y)`. `E(η)` is not `E(y)`. `CINT` is not `MANIFESTMEANS`. `T0MEANS` is not `E(y)`. The discrete latent mean is `μ_t = exp(a Δt) μ_0 + (exp(a Δt) − 1)/a κ` (Driver et al., 2017, Eq. 3, p. 4; Table 2, p. 12). `T0MEANS` is not `μ_t`. `CINT` is not that discrete increment. A zero drift is `κ Δt`. Underflow of `exp(a Δt)` to `+0` drops the carried `T0MEANS` and keeps `−κ / a`. The evolved observed mean is `τ + λ μ_t` (Driver et al., 2017, Eq. 5 of that Eq. 3 map). The first-occasion map `τ + λ μ_0` is not `E(y_t)`. `μ_t` is not `E(y_t)`. The contemporaneous time-dependent predictor impulse is `m x` (Driver et al., 2017, Eq. 3 fourth summand; Table 2 `TDPREDEFFECT` is `M`). Form `μ_t` first, then add `m x`. `TDPREDEFFECT` is not `CINT`. `M x` is not `A^{-1}[e^{A Δt} − I] B z` and is not Voelkle et al. (2012, Eq. 14). The §7.2 level-change form is not that impulse. The observed mean of that contemporaneous impulse is `τ + λ(μ_t + m x)` (Driver et al., 2017, Eq. 5 of the Eq. 3 fourth-summand composition). The evolved map `τ + λ μ_t` is not that observed mean. The carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`. The evolved-plus-impulse latent mean is not `E(y_t)`. The time-independent predictor increment is `A^{-1}[e^{A Δt} − I] B z` (Driver et al., 2017, Eq. 3 second summand; Table 2 `TIPREDEFFECT` is `B`). Form `B z` first, then the discrete intercept map. A zero drift is `B z Δt`. `TIPREDEFFECT` is `B`, not that discrete increment. `A^{-1}[e^{A Δt} − I] B z` is not `CINT`, not `M x`, and not Voelkle et al. (2012, Eq. 14). The observed mean of that increment is `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` (Driver et al., 2017, Eq. 5 of the Eq. 3 printed addend after the `T0MEANS` carry and the `CINT` increment). The evolved map `τ + λ μ_t` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t`. The evolved-plus-increment latent mean is not `E(y_t)`. The within-interval time-dependent impulse carry is `e^{A(t−u)} M x` for `t0 < u < t` (Driver et al., 2017, Eq. 1–2 Green-function integral of Eq. 2; §7.2 dissipation). Form `m x` first, then `e^{a(t−u)} m x`. A zero drift is `m x` with no dissipation. Underflow of `e^{a(t−u)}` to `+0` is vanishing dissipation and is kept. `e^{A(t−u)} M x` is not the contemporaneous Dirac, not `CINT`, not `TIPREDEFFECT`, and not Voelkle et al. (2012, Eq. 14). An impulse at `u = t` is the contemporaneous map. An impulse at `u ≤ t0` is already in `η(t0)`. The observed mean of that carry is `τ + λ(μ_t + e^{a(t−u)} m x)` (Driver et al., 2017, Eq. 5 of the Eq. 1–2 carried latent mean). The evolved map `τ + λ μ_t` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean when `u ≠ t`. `MANIFESTMEANS` is not `E(y_t)`. The carried latent mean is not `E(y_t)`. The first-occasion time-independent predictor shift is `t0_b z` (Driver et al., 2017, Table 3 `T0TIPREDEFFECT`; Eq. 3 first summand). Form `t0_b z` first, then `e^{a Δt} t0_b z`. Form `μ_t` first, then add that carry. A zero drift is `t0_b z`. Underflow of `e^{a Δt}` to `+0` is a vanishing carry of the first-occasion shift and is kept. `t0_b z` is not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`. `e^{A Δt} t0_b z` is not `t0_b z`. `T0TIPREDEFFECT` is the coefficient, not the shift. The observed mean of that first-occasion carry is `τ + λ(μ_t + e^{a Δt} t0_b z)` (Driver et al., 2017, Eq. 5 of the Table 3 / Eq. 3 first-summand composition). The evolved map `τ + λ μ_t` is not that observed mean. The process-increment map `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The impulse-carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t0`. The evolved-plus-carry latent mean is not `E(y_t)`. The first-occasion time-dependent predictor shift is `t0_m x0` (Driver et al., 2017, Table 3 `T0TDPREDEFFECT`; Eq. 3 first summand; JSS PDF re-opened 2026-08-20T19:10Z). Form `t0_m x0` first, then `e^{a Δt} t0_m x0`. Form `μ_t` first, then add that carry. A zero drift is `t0_m x0`. Underflow of `e^{a Δt}` to `+0` is a vanishing carry of the first-occasion shift and is kept. `t0_m x0` is not `M x`, not `e^{A(t−u)} M x` for `t0 < u < t`, not `t0_b z`, not `A^{-1}[e^{A Δt} − I] B z`, and not `CINT`. `e^{A Δt} t0_m x0` is not `t0_m x0`. `T0TDPREDEFFECT` is the coefficient, not the shift. An impulse at `u ≤ t0` that used `M` is already in `η(t0)` as `TDPREDEFFECT`, not as `T0TDPREDEFFECT`. The observed mean of that first-occasion TD carry is `τ + λ(μ_t + e^{a Δt} t0_m x0)` (Driver et al., 2017, Eq. 5 of the Table 3 / Eq. 3 first-summand TD composition; JSS PDF re-opened 2026-08-20T19:07Z). The evolved map `τ + λ μ_t` is not that observed mean. The process-increment map `τ + λ(μ_t + A^{-1}[e^{A Δt} − I] B z)` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The impulse-carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is not that observed mean when `u ≠ t0`. The first-occasion TI map `τ + λ(μ_t + e^{a Δt} t0_b z)` is not that observed mean. The evolved-plus-carry latent mean is not `E(y_t)`. The lasting level-change `CINT` is `κ = −a m x` (Driver et al., 2017, §7.2, pp. 20–21; JSS PDF re-opened 2026-08-20T19:45Z). Form `m x` first, then multiply by `−a`. Stable `a < 0` is required so `−κ / a = m x` is an equilibrium offset. `a ≥ 0` cannot hold a new process mean. `−a m x` is not the dissipating Dirac `m x`, not a free `CINT`, and not `A^{-1}[e^{A Δt} − I] B z`. The extra near-zero-drift latent process also named in §7.2 is a different specification and is not this `CINT` setting. Equation 3 maps that intercept as `(1 − e^{a Δt}) m x` (JSS PDF re-opened 2026-08-20T19:50Z). Form the level-change `CINT` first, then the discrete intercept map. Underflow of `e^{a Δt}` to `+0` keeps `m x`. `(1 − e^{a Δt}) m x` is not `m x`, not `κ`, and not `A^{-1}[e^{A Δt} − I] B z`. The printed §7.2 lasting level change is an extra near-zero-drift latent process (Driver et al., 2017, §7.2, pp. 22–23; JSS PDF re-opened 2026-08-20T23:10Z). `T0MEANS`, `CINT`, `T0VAR`, `DIFFUSION`, and `TRAITVAR` of that process are fixed to 0; `TDPREDEFFECT` on it is fixed to 1; its `DRIFT` diagonal is very close to 0 (printed example `−0.000001`; precisely 0 causes computational problems); the original process is driven by the `DRIFT` coupling `a_{ηξ}`. After a unit identification impulse the scalar contribution is `a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a)` (`ε = a` is `a_{ηξ} x Δt e^{a Δt}`). Form `a_{ηξ} x` first. A zero coupling or zero predictor is exactly zero. `ε ≥ 0` fails closed. That contribution is not `κ = −a m x`, not `(1 − e^{a Δt}) m x`, and not the dissipating Dirac `m x`. The observed mean of that extra-process contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε Δt} − e^{a Δt}) / (ε − a))` (Driver et al., 2017, Eq. 5 of that §7.2 contribution; JSS PDF re-opened 2026-08-21T06:12Z). The extra process has `LAMBDA` 0 and is not an observed indicator. Original indicators load on the original process after the `DRIFT` coupling. The evolved map `τ + λ μ_t` is not that observed mean. The contemporaneous map `τ + λ(μ_t + m x)` is not that observed mean. The contribution is not `E(y_t)`. The evolved-plus-contribution latent mean is not `E(y_t)`. `T0TDPREDEFFECT` on the extra process begins at `t = 0` and uses `Δt = t − t0` for both the original-process evolution and the extra drive. `TDPREDEFFECT` after `t0` uses `t − u` with `t0 < u < t` for the extra drive while `μ_t` still uses `Δt`. The observed mean of that after-t0 extra-process contribution is `τ + λ(μ_t + a_{ηξ} x (e^{ε(t−u)} − e^{a(t−u)}) / (ε − a))` (Driver et al., 2017, Eq. 5 of that §7.2 after-t0 contribution; JSS PDF re-opened 2026-08-21T06:32Z). The first-occasion extra-process observed mean is not that observed mean when `u ≠ t0`. The impulse-carry map `τ + λ(μ_t + e^{a(t−u)} m x)` is a Dirac on the original process and is not that `DRIFT` drive. An impulse at `u = t0` or `u = t` is not interior. The asymptotic time-independent predictor effect is `-B z / a` (Driver et al., 2017, §7.2, pp. 20–21; JSS PDF opened 2026-08-21T13:08Z). Form `B z` first, then divide by `-a`. Stable `a < 0` is required. `a ≥ 0` cannot hold a finite process-mean change. `-B z / a` is not the coefficient `B`, not `A^{-1}[e^{A Δt} − I] B z`, not `CINT`, and not `M x`. The asymptotic time-independent predictor variance is `(B / a)² v` (Driver et al., 2017, §7.2, pp. 20–21 `addedTIPREDVAR`). Form the unit asymptotic effect first, then square, then multiply by `v`. `(B / a)² v` is not `TRAITVAR`, not `asymDIFFUSION`, and not `-B z / a`. The asymptotic continuous intercept is `-κ / a` (Driver et al., 2017, Table 2, p. 12 `asymCINT`; Eq. 3 as `Δt → ∞`; JSS PDF opened 2026-08-21T16:13Z). Form `κ` first, then divide by `-a`. Stable `a < 0` is required. `-κ / a` is not `κ`, not `A^{-1}[e^{A Δt} − I] κ`, not `T0MEANS`, and not `-B z / a`. The p. 16 stationary `T0MEANS` constraint is `-κ / a + −B z / a`. Form the intercept contribution first, then include the TI extra effect, then add. That constrained first-occasion mean is not free `T0MEANS`, not `asymCINT` alone, not `asymTIPREDEFFECT` alone, and not the finite-interval discrete latent mean. Equation 5 of that constrained mean is `τ + λ(−κ / a + −B z / a)` (Driver et al., 2017, §4.3, pp. 9–10; Eq. 5, p. 5; JSS PDF re-opened 2026-08-21T20:07Z). Form the stationary latent mean first, then `τ + λ` of that mean. `τ + λ μ_0` for free `T0MEANS` is not that composition. `τ + λ(−κ / a)` is not that composition when `B z ≠ 0`. `τ + λ μ_t` is not that composition. `MANIFESTMEANS` is not `E(y_0)`. The constrained latent mean is not `E(y_0)`. The p. 16 constrained first-occasion variance `trait + −q / (2 a) + (B / a)² v` is not free `T0VAR`, not `asymDIFFUSION` alone, not `TRAITVAR` alone, not `addedTIPREDVAR` alone, and not the finite-interval discrete latent variance. Eq. 5 of that constrained variance is `λ²(trait + −q / (2 a) + (B / a)² v) + θ + ψ` (JSS PDF re-opened 2026-08-22T03:20Z; form the stationary latent variance first, then `λ² p + θ + ψ`; `λ² p_0` is not that observed variance; `λ²(−q / (2 a)) + θ` is not that observed variance when `TRAITVAR` or `addedTIPREDVAR` is nonzero; `MANIFESTVAR` is not `Var(y_0)`; the constrained latent variance is not `Var(y_0)`). The lagged covariance of that constrained process is `trait + e^{a Δt}(−q / (2 a)) + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T19:13Z). Trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`. Contemporaneous `T0VAR` is not that lagged map. Decaying the constrained total as if it were all state is not that lagged map. Equation 5 of that lagged covariance is `λ²(trait + e^{a Δt}(−q / (2 a)) + (B / a)² v) + ψ`. `Θ` does not enter. Contemporaneous `Var(y_0)` is not that lagged observed covariance. The lagged latent covariance is not that observed covariance. The later-occasion variance of that constrained process is `trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 / p. 16 `T0VAR`; JSS PDF re-opened 2026-08-22T23:12Z). Trait and `addedTIPREDVAR` do not enter `Q_Δt`. Under stationarity that composition equals contemporaneous `T0VAR`. Evolving the constrained total as if it were all state is not that later map. The lagged covariance omits `Q_Δt` and is not that later map. `Q_Δt` is not that later map. Equation 5 of that later-occasion variance is `λ²(trait + e^{2 a Δt}(−q / (2 a)) + Q_Δt + (B / a)² v) + θ + ψ`. The lagged observed covariance omits `Q_Δt` and `θ`. `MANIFESTVAR` is not `Var(y_t)`. The later-occasion latent variance is not `Var(y_t)`. The later-occasion variance of §4.3 predetermined `T0VAR` is `trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T05:12Z). Trait and `addedTIPREDVAR` do not enter `Q_Δt`. Free `T0VAR` `p_0` is not that later map. Setting `p_0 = −q / (2 a)` recovers the stationary later-occasion map. Stationary later variance uses `−q / (2 a)` in place of `p_0` and is not that later map when `p_0` is free. Evolving `trait + p_0 + (B / a)² v` as if it were all state is not that later map. As `Δt → ∞` with stable `a < 0` the composition approaches contemporaneous stationary `T0VAR`. As `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`. Nonzero diffusion with `a ≥ 0` is a growing process and is kept. Equation 5 of that predetermined later-occasion variance is `λ²(trait + e^{2 a Δt} p_0 + Q_Δt + (B / a)² v) + θ + ψ`. `MANIFESTVAR` is not `Var(y_t)`. The predetermined later-occasion latent variance is not `Var(y_t)`. Stationary later observed variance is not that observed variance when `p_0` is free. The lagged covariance of §4.3 predetermined `T0VAR` is `trait + e^{a Δt} p_0 + (B / a)² v` (Driver et al., 2017, Eq. 3–4 of §4.3 predetermined first occasion; JSS PDF re-opened 2026-08-23T09:04Z). Trait and `addedTIPREDVAR` do not decay with `e^{a Δt}`. Free `T0VAR` `p_0` is not that lagged map. Setting `p_0 = −q / (2 a)` recovers the stationary lagged map. Stationary lagged covariance uses `−q / (2 a)` in place of `p_0` and is not that lagged map when `p_0` is free. Evolving `trait + p_0 + (B / a)² v` as if it were all state is not that lagged map. Later-occasion variance includes `Q_Δt` and is not that lagged map. As `Δt → ∞` with stable `a < 0` the state term vanishes. As `Δt → 0+` the composition approaches `trait + p_0 + (B / a)² v`. Equation 5 of that predetermined lagged covariance is `λ²(trait + e^{a Δt} p_0 + (B / a)² v) + ψ`. `MANIFESTVAR` does not enter. The predetermined lagged latent covariance is not that observed covariance. Predetermined later observed variance includes `Q_Δt` and `θ` and is not that lagged observed covariance. Stationary lagged observed covariance is not that observed covariance when `p_0` is free. The predetermined first-occasion variance of §4.3 predetermined `T0VAR` is `trait + p_0 + (B / a)² v`. Free `p_0` is not that map. Stationary first-occasion variance uses `−q / (2 a)` in place of `p_0` and is not that map when `p_0` is free. Lagged covariance decays the state and is not that map. Later-occasion variance includes `Q_Δt` and is not that map. Equation 5 of that predetermined first-occasion variance is `λ²(trait + p_0 + (B / a)² v) + θ + ψ`. `MANIFESTVAR` is not that first-occasion observed variance. The predetermined first-occasion latent variance is not that observed variance. Stationary first-occasion observed variance is not that observed variance when `p_0` is free. Predetermined later observed variance includes `Q_Δt` and is not that first-occasion observed variance. Later-start lagged covariance of predetermined `T0VAR` is `trait + e^{a s}(e^{2 a u} p_0 + Q_u) + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 4; JSS PDF re-opened 2026-08-23T10:27Z). First-occasion lagged omits `e^{a s} Q_u`. Later-occasion variance does not lag. Stationary lagged uses `−q / (2 a)`. Decaying the later total is not that map. Equation 5 of that later-start lagged covariance is `λ²` of it plus `ψ`. Independent `ε_t` does not enter. First-occasion lagged observed omits `e^{a s} Q_u`. Predetermined later observed variance includes `Q_u` and `θ` and is not that later-start lagged observed covariance. Later-start later-occasion variance of predetermined `T0VAR` is `trait + e^{2 a s}(e^{2 a u} p_0 + Q_u) + Q_s + (B / a)² v` (Driver et al., 2017, §4.3 `startoffset`; Eq. 3–4 Chapman–Kolmogorov `Q_{u+s} = e^{2 a s} Q_u + Q_s`; JSS PDF re-opened 2026-08-23T11:05Z). Later-occasion variance at `u` omits `Q_s`. Later-start lagged covariance omits `Q_s`. Stationary later uses `−q / (2 a)`. Evolving the later total as if it were all state is not that map. Ignoring `startoffset` omits `e^{2 a s} Q_u`. Equation 5 of that later-start later-occasion variance is `λ²` of it plus `θ + ψ`. `MANIFESTVAR` is not that observed variance. Page 16 `discreteDRIFTstd` is `e^{a Δt}` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; footnote 4; §7.1; JSS PDF re-opened 2026-08-23T11:40Z). Unstandardised `e^{a Δt}` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDRIFTstd`. The §7.1 trait-plus-state autocorrelation `(trait + e^{a Δt} p + added) / (trait + p + added)` uses `TRAITVAR` and is not `discreteDRIFTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `discreteDIFFUSIONstd` is `Q_Δt / (−q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:06Z). Unstandardised `Q_Δt` is defined for growing `a ≥ 0` and for zero diffusion and is not `discreteDIFFUSIONstd`. The continuous standardisation `−2 a` is not `discreteDIFFUSIONstd`. `Q_Δt / (trait + p + added)` uses `TRAITVAR` and is not `discreteDIFFUSIONstd`. `TRAITVAR` is not the standardisation variance. Page 16 `DIFFUSIONstd` is `q / (−q / (2 a)) = −2 a` after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 4; footnote 4; JSS PDF re-opened 2026-08-23T13:20Z). Unstandardised `q` is defined for growing `a ≥ 0` and for zero diffusion and is not `DIFFUSIONstd`. The discrete standardisation `Q_Δt / (−q / (2 a))` depends on `Δt` and is not `DIFFUSIONstd`. `q / (trait + p + added)` uses `TRAITVAR` and is not `DIFFUSIONstd`. `TRAITVAR` is not the standardisation variance. Page 16 `DRIFTstd` is the continuous auto-effect after strictly positive `asymDIFFUSION` `-q / (2 a)` (Driver et al., 2017, p. 16; Eq. 1; footnote 4; JSS PDF re-opened 2026-08-23T13:28Z). Unstandardised `a` is defined for growing `a ≥ 0` and for zero diffusion and is not `DRIFTstd`. The discrete standardisation `e^{a Δt}` depends on the event interval and is not `DRIFTstd`. `a p / (trait + p + added)` uses `TRAITVAR` and is not `DRIFTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `asymTIPREDEFFECTstd` is `(-B / a) · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T14:25Z). Unstandardised `-B / a` is defined for a zero coefficient and for zero predictor variance and is not `asymTIPREDEFFECTstd`. The finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `asymTIPREDEFFECTstd`. `(-B / a) · √v / √(trait + p + added)` uses `TRAITVAR` and is not `asymTIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Page 16 `TIPREDEFFECTstd` is `B · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` `-q / (2 a)` and strictly positive predictor variance `v` (Driver et al., 2017, p. 16; §7.2; footnote 4; JSS PDF re-opened 2026-08-23T16:21Z). Unstandardised `B` is defined for a zero coefficient and for zero predictor variance and is not `TIPREDEFFECTstd`. The asymptotic standardisation `(-B / a) · √v / √p` is the total change and is not `TIPREDEFFECTstd`. The finite-interval standardisation `A^{-1}[e^{A Δt} − I] B · √v / √p` depends on the event interval and is not `TIPREDEFFECTstd`. `B · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. Page 16 / Table 3 `T0TIPREDEFFECTstd` is `t0_b · √v / √p_0` after strictly positive free `T0VAR` `p_0` and strictly positive predictor variance `v` (Driver et al., 2017, Table 3, p. 13; p. 16; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T17:20Z). The affected variance is free first-occasion `T0VAR`, not `asymDIFFUSION`. Unstandardised `t0_b` is defined for a zero coefficient and for zero predictor variance and is not `T0TIPREDEFFECTstd`. `TIPREDEFFECTstd` `B · √v / √(-q / (2 a))` is the continuous coefficient and is not `T0TIPREDEFFECTstd`. `asymTIPREDEFFECTstd` `(-B / a) · √v / √p` is the total change and is not `T0TIPREDEFFECTstd`. `t0_b · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TIPREDEFFECTstd`. `TRAITVAR` is not the standardisation variance. 2017-era `addedT0TIPREDVAR` is `t0_b² v` after a first-occasion time-independent predictor (Driver et al., 2017, Table 3, p. 13; p. 16; §7.2; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T18:20Z). Form `t0_b` first, then square, then multiply by `v`. A zero coefficient or zero predictor variance is exactly zero. Free `T0TIPREDEFFECT` does not require `a < 0`. `(B / a)² v` is `addedTIPREDVAR` and is not this first-occasion map. `t0_b · √v / √p_0` is `T0TIPREDEFFECTstd` and is not this variance. Free `T0VAR` is not this extra TI variance. `TRAITVAR` is not this extra TI variance. Equation 5 of 2017-era `addedT0TIPREDVAR` is `λ² t0_b² v` (Driver et al., 2017, Eq. 5, p. 5; Table 3, p. 13; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:10Z). Form `t0_b² v` first, then `(λ extra) λ` with `θ = 0`. A zero loading or zero extra is exactly zero. `t0_b² v` is the latent extra, not the observed extra. `λ² p_0 + θ` is first-occasion observed variance, not this extra. `λ² (B / a)² v` is Eq. 5 of `addedTIPREDVAR`, not this first-occasion observed extra. `MANIFESTVAR` `θ` is not this extra. Equation 5 of §7.2 `addedTIPREDVAR` is `λ² (B / a)² v` (Driver et al., 2017, Eq. 5, p. 5; Table 2, p. 12; §7.2, pp. 20–21; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T19:23Z). Form `(B / a)² v` first, then `(λ extra) λ` with `θ = 0`. A zero loading or zero extra is exactly zero. Lasting asymptotic extra requires `a < 0`. `(B / a)² v` is the latent extra, not the observed extra. `λ² t0_b² v` is first-occasion extra observed TI variance, not this extra. `λ² p + θ` is stationary observed variance, not this extra. `MANIFESTVAR` `θ` is not this extra. Page 16 `TDPREDEFFECTstd` is `m · √v / √(-q / (2 a))` after strictly positive `asymDIFFUSION` and strictly positive time-dependent predictor variance. Unstandardised `M` is not `TDPREDEFFECTstd`. `TIPREDEFFECTstd` is not `TDPREDEFFECTstd` even when `M = B`. intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` is not `TDPREDEFFECTstd`. `m · √v / √(trait + p + added)` uses `TRAITVAR` and is not `TDPREDEFFECTstd`. Table 3 / p. 16 `T0TDPREDEFFECTstd` is `t0_m · √v / √p_0` after strictly positive free `T0VAR` and strictly positive TD predictor variance. Unstandardised `t0_m` is not `T0TDPREDEFFECTstd`. `TDPREDEFFECTstd` uses `asymDIFFUSION` and is not `T0TDPREDEFFECTstd`. `T0TIPREDEFFECTstd` is not `T0TDPREDEFFECTstd` even when `t0_m = t0_b`. `t0_m · √v / √(trait + p_0 + added)` uses `TRAITVAR` and is not `T0TDPREDEFFECTstd`. Free `T0VAR` does not require `a < 0`. Page 16 `T0VARstd` is `p_0 / p_0 = 1` after strictly positive free `T0VAR` (`solve(sqrt(diag(T0VAR))) %&% T0VAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; the default ridge is 0). Unstandardised `T0VAR` is not `T0VARstd`. `T0TDPREDEFFECTstd` is not `T0VARstd`. `addedT0TIPREDVAR` is not `T0VARstd`. Page 16 `TRAITVARstd` is `trait / trait = 1` after strictly positive `TRAITVAR` (`solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; no ridge addend). Unstandardised `TRAITVAR` is not `TRAITVARstd`. `T0VARstd` is not `TRAITVARstd` even when both equal 1. `addedT0TIPREDVAR` is not `TRAITVARstd`. Page 16 `MANIFESTTRAITVARstd` is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR` (`solve(sqrt(diag(MANIFESTTRAITVAR))) %&% MANIFESTTRAITVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0). Unstandardised `MANIFESTTRAITVAR` is not `MANIFESTTRAITVARstd`. `TRAITVARstd` is not `MANIFESTTRAITVARstd` even when both equal 1. `MANIFESTVAR` is not `MANIFESTTRAITVARstd`. Page 16 `MANIFESTVARstd` is `θ / θ = 1` after strictly positive `MANIFESTVAR` (`solve(sqrt(diag(MANIFESTVAR))) %&% MANIFESTVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; 2017-era `dimnames` assignment to `latentNames` is a source bug). Unstandardised `MANIFESTVAR` is not `MANIFESTVARstd`. `MANIFESTTRAITVARstd` is not `MANIFESTVARstd` even when both equal 1. Equation 5 `Var(y)` is not `MANIFESTVARstd`. Page 16 `TIPREDVARstd` is `v / v = 1` after strictly positive `TIPREDVAR` (`solve(sqrt(diag(TIPREDVAR))) %&% TIPREDVAR`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `TIpredNames`). Unstandardised `TIPREDVAR` is not `TIPREDVARstd`. `MANIFESTVARstd` is not `TIPREDVARstd` even when both equal 1. Section 7.2 `addedTIPREDVAR` is not `TIPREDVARstd`. Page 16 `asymDIFFUSIONstd` is `p / p = 1` after strictly positive `asymDIFFUSION` (`solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION`; OpenMx `%&%` is `t(A) %*% B %*% A`; 2017-era source adds ridging; default ridge is 0; `dimnames` are `latentNames`). Unstandardised `asymDIFFUSION` is not `asymDIFFUSIONstd`. `TIPREDVARstd` is not `asymDIFFUSIONstd` even when both equal 1. `DIFFUSIONstd` `−2 a` is not `asymDIFFUSIONstd`. Page 16 `discreteCINTstd` is `A^{-1}[e^{A Δt} − I] κ / √p` after strictly positive `asymDIFFUSION`. Unstandardised `discreteCINT` is not `discreteCINTstd`. `κ / √p` is not `discreteCINTstd`. `(-κ / a) / √p` is not `discreteCINTstd`. `asymCINTstd` is `(-κ / a) / √p` after strictly positive `asymDIFFUSION`. Unstandardised `asymCINT` is not `asymCINTstd`. `κ / √p` is not `asymCINTstd`. `discreteCINTstd` is not `asymCINTstd`. `T0MEANSstd` is `μ_0 / √p_0` after strictly positive free `T0VAR`. Unstandardised `T0MEANS` is not `T0MEANSstd`. `T0VARstd` is not `T0MEANSstd`. `μ_0 / √asymDIFFUSION` is not `T0MEANSstd`. Page 16 `MANIFESTMEANSstd` is `τ / √θ` after strictly positive `MANIFESTVAR`. Unstandardised `MANIFESTMEANS` is not `MANIFESTMEANSstd`. `MANIFESTVARstd` is not `MANIFESTMEANSstd`. `τ / √(λ² Var(η) + θ)` is not `MANIFESTMEANSstd`. Page 16 `CINTstd` is `κ / √p` after strictly positive `asymDIFFUSION`. Unstandardised `CINT` is not `CINTstd`. `asymCINTstd` is not `CINTstd`. `discreteCINTstd` is not `CINTstd`. `κ / √(trait + p + added)` is not `CINTstd`. Evolving from that stationary start with `CINT` and `TIPREDEFFECT` stays at the stationary mean. Equation 1 is the latent SDE, not the measurement model. Form `(λ p) λ` then add `θ`, then add `ψ`. `MANIFESTVAR` is `Θ`, not `Var(y)`. `MANIFESTTRAITVAR` is `Ψ_τ`, not `Θ`. `TRAITVAR` is latent and scaled by `λ²`. `Var(η)` is not `Var(y)`. - Separate cluster means before within-unit lag. CWC plus an event-time lag is not DSEM. Subtracting the person-specific mean from a raw autoregressive series does not isolate the lagged within-person effect (Curran & Bauer, 2011, pp. 583–619; PMC3059070 XML opened 2026-09-02; Eq. 36). `longitudinal_core` owns CWC-then-pairwise-mean residual log-rate on typed `EventTimeInterval`; for an already-centered `(1, 0.5)` pair it recovers `ln(0.5) / Δt`, equal to `ln(0.5)` only when `Δt = 1`. Exact zero CWC residuals use canonical public `+0.0`. CWC log-rate is not raw-process drift. +- Occasion-mean residuals are Hamaker, Kuiper, and Grasman (2015, Eq. 1a) `x_it = μ_t + p_it` deviations, not CWC and not RI-CLPM. Exact zero occasion-mean residuals use canonical public `+0.0`. They still retain between-person differences and are not within-person change. - Do not treat the CWC cluster-mean coefficient as the between-cluster effect. It is the contextual effect `between − within` (Enders & Tofighi, 2007, Table 2, pp. 124–127). - Never use future-available evidence in historical model fits. - Do not blanket-mask PII when identity/role/linkage is scientifically required. Follow the purpose-bound separation, opaque-ID, encryption, retention, and audit contract in `docs/PRIVACY_DATA_GOVERNANCE.md`. diff --git a/docs/research/occasion-mean-event-time-composition.md b/docs/research/occasion-mean-event-time-composition.md index 6707a7242..c7450e6a1 100644 --- a/docs/research/occasion-mean-event-time-composition.md +++ b/docs/research/occasion-mean-event-time-composition.md @@ -15,6 +15,7 @@ Aligned occasions may be irregularly spaced. Each consecutive unit-specific pair `longitudinal_core::center_occasion_mean_event_lags` owns the temporal composition boundary. - Occasion identity is numeric event time. IEEE-754 `-0.0` and `+0.0` are one occasion, not two bit-pattern identities. +- Exact zero Hamaker Eq. 1a residuals use one public encoding: canonical `+0.0`. A `-0.0` observed score equal to a canonical `+0.0` occasion mean must not leak a signed-zero public residual. IEEE-754 signed zeros are numerically equal but bit-distinct; exact zero occasion-mean deviation has no positive/negative measurement direction. Private numerical intermediates and caller-constructed already-centered pairs may still retain signed zero. This is the same public-identity contract as CWC and `decompose_within_between`; it is not a license to treat occasion-mean residuals as within-person or RI-CLPM lags. - A unit may contribute at most one observation to one numeric occasion. - Each admitted occasion contains at least two distinct units, and at least two units contribute consecutive lags. This prevents an atomistic one-unit series from manufacturing multilevel evidence. - Scores and event times must be finite. Consecutive event intervals must be finite and strictly positive. @@ -28,6 +29,7 @@ Aligned occasions may be irregularly spaced. Each consecutive unit-specific pair Current #310 branch tests: - `crates/longitudinal_core/tests/occasion_mean_event_time_contract.rs::signed_zero_is_one_numeric_occasion` — `-0.0`/`+0.0` share one occasion and the known scalar event-time log-rate is recovered. +- `crates/longitudinal_core/tests/occasion_mean_signed_zero_contract.rs` — a `-0.0` observed score equal to a canonical `+0.0` occasion mean cannot keep a signed-zero public residual; nonzero mixed-sign residuals stay bit-identical. - `occasion_mean_residual_rate_is_not_cwc_rate_on_the_same_panel` — the same time-varying-group-mean panel recovers the known Hamaker Eq. 1a occasion-residual rate while person-mean CWC does not recover that rate. This preserves the valid scientific distinction from Draft #486 without retaining its wrong `psychometric_core` ownership. - `signed_zero_duplicate_unit_is_rejected_as_one_occasion` — the same unit cannot use signed zero to bypass duplicate occasion admission. - `representable_occasion_mean_is_not_rejected_for_intermediate_sum_overflow` — `[0.75·MAX, 0.75·MAX, -0.5·MAX]` at one occasion retains a finite representable mean and finite centered residuals rather than failing on the naive partial sum. @@ -36,7 +38,7 @@ Current #310 branch tests: - `sparse_unaligned_and_nonfinite_occasion_inputs_fail_closed` — empty/singleton payloads, unaligned one-unit occasions, and non-finite event-time input fail closed at the Longitudinal boundary. - `singleton_wave_unit_does_not_manufacture_or_block_lag_evidence` — a one-wave unit may contribute to an admitted occasion mean but cannot count toward the two-unit lag-evidence floor; two genuine lag-contributing units still recover the known rate. -The initial occasion-composition RED is `75b0184d2f6341ef23cf14fc84398c68d8d95d22`. Deterministic-order RED `8a59019ed3112a3e27dd0dcd1b6b86d8d45e5435` exposes order-dependent same-sign averaging; causal repair `465d139dce6101c4958c8b0827b6ef5d674b54c2` orders same-sign values before averaging. Successor-evidence commit `b9e952bb8a893f62aaead59cdf825b5e3c6251c6` ports #486's valid same-panel occasion-vs-CWC scientific claim boundary into the owner-correct Longitudinal test surface. Admission-evidence commit `aad56b502bbdfab08ba896b7d3560884c87fc589` preserves #486's sparse/unaligned/non-finite and singleton-wave cases without retaining its wrong crate ownership. Subnormal-rounding RED `9aff817f9e0f82b9cdb2077f3f62bb3e6a987103` exposes the incremental same-sign mean's loss of a half-ULP update at the binary64 floor; causal repair `40e057b83980a0cc501ad936c02e2d59f90a6fe9` replaces that recurrence with deterministic normalized compensated averaging. The owner-correct implementation remains in `crates/longitudinal_core/src/occasion_mean.rs`. Only the current exact branch head may be used for merge evidence. +The initial occasion-composition RED is `75b0184d2f6341ef23cf14fc84398c68d8d95d22`. Deterministic-order RED `8a59019ed3112a3e27dd0dcd1b6b86d8d45e5435` exposes order-dependent same-sign averaging; causal repair `465d139dce6101c4958c8b0827b6ef5d674b54c2` orders same-sign values before averaging. Successor-evidence commit `b9e952bb8a893f62aaead59cdf825b5e3c6251c6` ports #486's valid same-panel occasion-vs-CWC scientific claim boundary into the owner-correct Longitudinal test surface. Admission-evidence commit `aad56b502bbdfab08ba896b7d3560884c87fc589` preserves #486's sparse/unaligned/non-finite and singleton-wave cases without retaining its wrong crate ownership. Subnormal-rounding RED `9aff817f9e0f82b9cdb2077f3f62bb3e6a987103` exposes the incremental same-sign mean's loss of a half-ULP update at the binary64 floor; causal repair `40e057b83980a0cc501ad936c02e2d59f90a6fe9` replaces that recurrence with deterministic normalized compensated averaging. Signed-zero residual RED `crates/longitudinal_core/tests/occasion_mean_signed_zero_contract.rs` drives `center_occasion_mean_event_lags` with a T=3 mixed-sign panel whose middle score is `-0.0`; causal repair `238c4def7a5a5f19963d2e5cc936dd750d55c5a1` canonicalizes only a validated exact-zero occasion-mean residual to public `+0.0`. IEEE Std 754-2019 remains the active published floating-point standard while IEEE P754 is the active revision project as of 2026-09-03. The owner-correct implementation remains in `crates/longitudinal_core/src/occasion_mean.rs`. Only the current exact branch head may be used for merge evidence. ## Claim boundary @@ -46,4 +48,6 @@ This slice is an occasion-mean residual event-time association. It is not RI-CLP Hamaker, E. L., Kuiper, R. M., & Grasman, R. P. P. P. (2015). A critique of the cross-lagged panel model. *Psychological Methods, 20*(1), 102–116. https://doi.org/10.1037/a0038889 +IEEE. (2019). *IEEE standard for floating-point arithmetic* (IEEE Std 754-2019). IEEE. + Repository evidence was cross-checked on 2026-09-03 against the Utrecht University/UvA-DARE final-published-version record and PubMed record (PMID 25822208). The repository record identifies the DOI, journal, volume, issue, pages, and peer-reviewed publication. No timezone-suffixed access timestamp is used as scientific evidence. From 96f1c3342071173ba870e6ef5e11b826391c7621 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 17:59:29 +0900 Subject: [PATCH 167/296] test(longitudinal): reject false-zero mean log-rate --- .../irregular_rate_mean_underflow_contract.rs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 crates/longitudinal_core/tests/irregular_rate_mean_underflow_contract.rs diff --git a/crates/longitudinal_core/tests/irregular_rate_mean_underflow_contract.rs b/crates/longitudinal_core/tests/irregular_rate_mean_underflow_contract.rs new file mode 100644 index 000000000..63f340079 --- /dev/null +++ b/crates/longitudinal_core/tests/irregular_rate_mean_underflow_contract.rs @@ -0,0 +1,28 @@ +//! A nonzero mean irregular-residual log-rate must not collapse to exact zero. + +use longitudinal_core::{ + EventTimeInterval, LaggedWithinResidual, LongitudinalError, + recover_centered_irregular_residual_log_rate, +}; + +#[test] +fn nonzero_mean_log_rate_that_is_not_binary64_representable_fails_closed() { + let tiny_interval = EventTimeInterval::new(1.0e307).expect("finite positive event interval"); + let unit_interval = EventTimeInterval::new(1.0).expect("unit interval"); + let earlier = 1.0_f64; + let later = f64::from_bits(earlier.to_bits() + 1); + let nonzero_pair = LaggedWithinResidual::new(earlier, later, tiny_interval); + + let one_pair_rate = recover_centered_irregular_residual_log_rate(&[nonzero_pair]) + .expect("the individual positive log-rate is representable"); + assert!(one_pair_rate > 0.0); + + let mut pairs = vec![LaggedWithinResidual::new(1.0, 1.0, unit_interval); 15]; + pairs.push(nonzero_pair); + + assert_eq!( + recover_centered_irregular_residual_log_rate(&pairs), + Err(LongitudinalError::InvalidTemporalTransformInput), + "a mathematically positive mean rate must not be reported as exact no-change" + ); +} From ae5081d8ae580c19e73aff7f03711e50c3c631dd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 18:02:36 +0900 Subject: [PATCH 168/296] fix(longitudinal): reject false-zero mean log-rate --- crates/longitudinal_core/src/irregular_residual.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/longitudinal_core/src/irregular_residual.rs b/crates/longitudinal_core/src/irregular_residual.rs index 0fe78b5c6..eee6422c9 100644 --- a/crates/longitudinal_core/src/irregular_residual.rs +++ b/crates/longitudinal_core/src/irregular_residual.rs @@ -295,7 +295,9 @@ fn pairwise_same_sign_log_rate(lagged: &[LaggedWithinResidual]) -> Result Result { if values.is_empty() { return Err(LongitudinalError::InvalidTemporalTransformInput); @@ -392,6 +394,9 @@ fn same_sign_mean_over_total(values: &[f64], total_count: usize) -> Result Date: Thu, 3 Sep 2026 18:03:22 +0900 Subject: [PATCH 169/296] docs(longitudinal): trace false-zero mean refusal --- .../cwc-irregular-residual-log-rate.md | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/docs/research/cwc-irregular-residual-log-rate.md b/docs/research/cwc-irregular-residual-log-rate.md index 892561e6d..bf730eb95 100644 --- a/docs/research/cwc-irregular-residual-log-rate.md +++ b/docs/research/cwc-irregular-residual-log-rate.md @@ -50,6 +50,24 @@ applies the original denominator inside the shared normalized sum. Contract `mixed_sign_mean_rounding_contract.rs` also exercises the public occasion-mean path so the shared numerical authority cannot silently split again. +A second boundary applies after the normalized sum. If the retained residual +mass has one sign and is nonzero, its real mean is also nonzero. Binary64 may +still round that final mean to exact zero when the magnitude lies below half +the minimum subnormal. Reporting that zero would change a nonzero temporal +rate into exact no-change. `same_sign_mean_over_total` therefore accepts zero +only when its admitted mass was itself exact zero; otherwise final zero is +`InvalidTemporalTransformInput`. + +Public RED `96f1c3342071173ba870e6ef5e11b826391c7621` adds +`irregular_rate_mean_underflow_contract.rs`. One already-centered pair has an +exact zero rate and one has a representable positive subnormal rate; averaging +that nonzero rate with fifteen exact-zero rates makes the real mean positive +but too small for binary64. Causal repair +`ae5081d8ae580c19e73aff7f03711e50c3c631dd` fails closed at the shared mean +boundary rather than returning exact zero. Exact cancellation of mixed-sign +rates still returns zero before the one-sign helper, and an all-zero input +still returns canonical `+0.0`. + This arithmetic remains a Longitudinal composition primitive in this stack. It is not a new reusable static psychometric kernel. A fast-mlsirm handoff requires semantic-equivalence evidence and an immutable released owner @@ -87,8 +105,8 @@ CWC of a raw AR path with a stable between-unit offset does **not** recover that `a`. Fail-closed cases cover empty and singleton-only rows, fewer than two lag-contributing units, non-positive intervals, non-finite scores, non-representable means, overflowing CWC residuals after a finite mean, -tiny intervals with huge log-ratios, underflowed nonzero final rates, and the -Curran refusal. +tiny intervals with huge log-ratios, underflowed nonzero individual rates, +underflowed nonzero final mean rates, and the Curran refusal. Exact zero CWC residuals also have one public identity. IEEE 754 binary64 has distinct `+0.0` and `-0.0` encodings, and subtraction from a canonical @@ -115,12 +133,12 @@ Signed-zero traceability: arithmetic* (IEEE Std 754-2019). IEEE. The canonical repository register is `docs/research/standards-and-literature.md`. - The current public numerical regressions include same-sign raw-sum overflow, full-exponent mixed-sign cancellation, minimum-subnormal cancellation, -halfway ties-to-even for same-sign means, and the mixed-sign `-31u/3` case -above. Hosted exact-head CI and independent review remain delivery gates; these -source contracts do not by themselves establish release readiness. +halfway ties-to-even for same-sign means, the mixed-sign `-31u/3` case, and +nonzero irregular-rate means that are not representable in binary64. Hosted +exact-head CI and independent review remain delivery gates; these source +contracts do not by themselves establish release readiness. ## Traceability From 766ddc7a3d95102450d663f06d2c577306964d79 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 19:25:36 +0900 Subject: [PATCH 170/296] test(longitudinal): expose adjacent-growth log-rate rounding --- ...irregular_rate_adjacent_growth_contract.rs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 crates/longitudinal_core/tests/irregular_rate_adjacent_growth_contract.rs diff --git a/crates/longitudinal_core/tests/irregular_rate_adjacent_growth_contract.rs b/crates/longitudinal_core/tests/irregular_rate_adjacent_growth_contract.rs new file mode 100644 index 000000000..6e37928ea --- /dev/null +++ b/crates/longitudinal_core/tests/irregular_rate_adjacent_growth_contract.rs @@ -0,0 +1,33 @@ +#![forbid(unsafe_code)] + +//! Regression contract for adjacent-float irregular residual growth. +//! +//! Driver et al. (2017, Eq. 3) requires the logarithm of the exact represented +//! magnitude ratio. Forming that ratio first can round an adjacent-float growth +//! step at a power-of-two boundary from roughly half an epsilon to one epsilon, +//! doubling the recovered log-rate before the logarithm is evaluated. + +use longitudinal_core::{ + EventTimeInterval, LaggedWithinResidual, recover_centered_irregular_residual_log_rate, +}; + +#[test] +fn adjacent_power_of_two_growth_keeps_the_represented_relative_change() { + let earlier = f64::from_bits(2.0_f64.to_bits() - 1); + let later = 2.0_f64; + let interval = EventTimeInterval::new(1.0).expect("unit event-time interval"); + let pair = LaggedWithinResidual::new(earlier, later, interval); + + let exact_relative_change = f64::EPSILON / 2.0; + let expected = -(-exact_relative_change).ln_1p(); + let rounded_ratio_log = (later / earlier).ln(); + assert!( + rounded_ratio_log > expected * 1.9, + "the fixture must reproduce ratio-first double rounding" + ); + + let recovered = recover_centered_irregular_residual_log_rate(&[pair]) + .expect("adjacent represented growth must remain recoverable"); + + assert_eq!(recovered.to_bits(), expected.to_bits()); +} From 16f21d9a223fb34d52f24d148c7cad9b385e1d91 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 19:31:15 +0900 Subject: [PATCH 171/296] fix(longitudinal): preserve adjacent-float log-rate change --- .../src/irregular_residual.rs | 88 ++++++++++++------- 1 file changed, 57 insertions(+), 31 deletions(-) diff --git a/crates/longitudinal_core/src/irregular_residual.rs b/crates/longitudinal_core/src/irregular_residual.rs index eee6422c9..d811b8527 100644 --- a/crates/longitudinal_core/src/irregular_residual.rs +++ b/crates/longitudinal_core/src/irregular_residual.rs @@ -182,17 +182,19 @@ pub fn center_within_unit_event_lags( /// /// This is [`center_within_unit_event_lags`] then the pairwise mean of the /// Driver, Oud, and Voelkle (2017, Eq. 3) scalar inverse -/// `a = ln(|later| / |earlier|) / Δt` on nonzero same-sign residuals. When -/// `|later| / |earlier|` is finite and positive the finite ratio logarithm is -/// used so near-equal large residuals do not collapse to zero. Overflowed or -/// underflowed ratios fall back to `ln|later| − ln|earlier|`. Opposite-sign -/// and zero residuals have no real logarithm and are skipped. The pairwise -/// mean cancels opposite-signed finite rates from largest magnitude downward -/// before averaging the surviving same-sign residuals. This avoids both -/// overflowing a raw same-sign sum and destroying representable subnormal -/// terms by pre-scaling them. An empty admissible set fails closed. This is -/// not Newton LS and does not recover raw-process drift from CWC of a raw AR -/// path (Curran & Bauer, 2011, pp. 583–619; Eq. 36). +/// `a = ln(|later| / |earlier|) / Δt` on nonzero same-sign residuals. For +/// magnitudes within a factor of two, the represented magnitude difference is +/// converted with `ln_1p` so an adjacent-float change is not rounded once by a +/// quotient and again by the logarithm. More extreme ratios use the direct +/// quotient when representable and otherwise fall back to +/// `ln|later| − ln|earlier|`. Opposite-sign and zero residuals have no real +/// logarithm and are skipped. The pairwise mean cancels opposite-signed finite +/// rates from largest magnitude downward before averaging the surviving +/// same-sign residuals. This avoids both overflowing a raw same-sign sum and +/// destroying representable subnormal terms by pre-scaling them. An empty +/// admissible set fails closed. This is not Newton LS and does not recover +/// raw-process drift from CWC of a raw AR path (Curran & Bauer, 2011, +/// pp. 583–619; Eq. 36). /// /// # Errors /// @@ -210,12 +212,13 @@ pub fn recover_within_unit_irregular_residual_log_rate( /// /// Each pair is `a = ln(|later| / |earlier|) / Δt` (Driver et al., 2017, /// Eq. 3 inverse). The function does **not** center again. Residuals must be -/// finite, nonzero, and of equal sign. A finite positive direct ratio is used -/// when representable; ratio overflow or underflow falls back to the equivalent -/// `ln|later| - ln|earlier|` so a representable final log-rate is not rejected -/// because of a non-representable intermediate. This is the known-truth path -/// that recovers `ln(0.5)` from already-centered pairs `(1, 0.5)` over unit -/// event time. +/// finite, nonzero, and of equal sign. Nearby represented magnitudes are +/// compared through their difference and `ln_1p`, avoiding a ratio-first +/// rounding step that can nearly double an adjacent-float change at a +/// power-of-two boundary. Extreme ratios retain direct-ratio and log-domain +/// fallbacks so a representable final log-rate is not rejected because of a +/// non-representable intermediate. This is the known-truth path that recovers +/// `ln(0.5)` from already-centered pairs `(1, 0.5)` over unit event time. /// /// # Errors /// @@ -420,11 +423,14 @@ pub(crate) fn same_sign_nonzero(earlier: f64, later: f64) -> bool { /// Driver et al. (2017, Eq. 3) inverse `a = ln(|later| / |earlier|) / Δt`. /// /// Caller already established same-sign nonzero residuals and an admitted -/// event interval. Prefer the finite ratio logarithm so near-equal large -/// residuals keep a nonzero rate. Fall back to `ln|later| − ln|earlier|` -/// only when that ratio overflows or underflows. A represented zero rate is -/// accepted only when the residual magnitudes are exactly equal; otherwise it -/// is a non-representable nonzero change and fails closed. +/// event interval. When the represented magnitudes differ by no more than a +/// factor of two, subtraction is exact in binary floating point and `ln_1p` +/// preserves the represented relative change without first rounding a quotient. +/// Growth uses `-ln1p(-(later-earlier)/later)` and decay uses +/// `ln1p((later-earlier)/earlier)`. More extreme scales retain the finite direct +/// ratio and log-domain fallbacks. A represented zero rate is accepted only +/// when the residual magnitudes are exactly equal; otherwise it is a +/// non-representable nonzero change and fails closed. pub(crate) fn driver_same_sign_log_rate( earlier: f64, later: f64, @@ -432,11 +438,25 @@ pub(crate) fn driver_same_sign_log_rate( ) -> Result { let earlier_magnitude = earlier.abs(); let later_magnitude = later.abs(); - let ratio = later_magnitude / earlier_magnitude; - let log_ratio = if ratio.is_finite() && ratio > 0.0 { - ratio.ln() + let log_ratio = if later_magnitude >= earlier_magnitude + && later_magnitude <= earlier_magnitude * 2.0 + { + let relative_loss_from_later = + (later_magnitude - earlier_magnitude) / later_magnitude; + -(-relative_loss_from_later).ln_1p() + } else if earlier_magnitude > later_magnitude + && earlier_magnitude <= later_magnitude * 2.0 + { + let relative_change_from_earlier = + (later_magnitude - earlier_magnitude) / earlier_magnitude; + relative_change_from_earlier.ln_1p() } else { - later_magnitude.ln() - earlier_magnitude.ln() + let ratio = later_magnitude / earlier_magnitude; + if ratio.is_finite() && ratio > 0.0 { + ratio.ln() + } else { + later_magnitude.ln() - earlier_magnitude.ln() + } }; let rate = log_ratio / event_interval.as_f64(); if !rate.is_finite() || (rate == 0.0 && later_magnitude != earlier_magnitude) { @@ -584,14 +604,20 @@ mod tests { } #[test] - fn driver_same_sign_prefers_finite_ratio_ln_for_near_equal_large_residuals() { + fn driver_same_sign_uses_relative_change_for_nearby_residuals() { let earlier = 1e20_f64; let later = earlier * (-1e-12_f64).exp(); - let ratio = later.abs() / earlier.abs(); - assert!(ratio.is_finite() && ratio > 0.0); - let from_ratio = ratio.ln(); + let relative_change = (later.abs() - earlier.abs()) / earlier.abs(); + let from_relative_change = relative_change.ln_1p(); let rate = driver_same_sign_log_rate(earlier, later, unit_interval()).expect("near-equal"); - assert_eq!(rate.to_bits(), from_ratio.to_bits()); + assert_eq!(rate.to_bits(), from_relative_change.to_bits()); + + let adjacent_earlier = f64::from_bits(2.0_f64.to_bits() - 1); + let adjacent_growth = driver_same_sign_log_rate(adjacent_earlier, 2.0, unit_interval()) + .expect("adjacent power-of-two growth"); + let exact_adjacent_growth = -(-(f64::EPSILON / 2.0)).ln_1p(); + assert_eq!(adjacent_growth.to_bits(), exact_adjacent_growth.to_bits()); + let overflow_rate = driver_same_sign_log_rate(f64::from_bits(1), f64::MAX, unit_interval()) .expect("overflow arm"); let overflow_logs = f64::MAX.ln() - f64::from_bits(1).ln(); From 0a19f8e2be9e537a2995728e5938489c66c632d3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 19:32:09 +0900 Subject: [PATCH 172/296] docs(longitudinal): trace adjacent-growth log-rate repair --- .../cwc-irregular-residual-log-rate.md | 44 ++++++++++++++++--- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/docs/research/cwc-irregular-residual-log-rate.md b/docs/research/cwc-irregular-residual-log-rate.md index bf730eb95..d126a29ec 100644 --- a/docs/research/cwc-irregular-residual-log-rate.md +++ b/docs/research/cwc-irregular-residual-log-rate.md @@ -18,16 +18,43 @@ raw-process autoregressive drift. Singleton units are skipped and at least two units must contribute lags. 2. `recover_within_unit_irregular_residual_log_rate` is the mean of the Driver, Oud, and Voelkle (2017, Eq. 3) scalar inverse - `a = ln(|later| / |earlier|) / Δt` on nonzero same-sign residuals. When - the absolute residual ratio is finite and positive the finite-ratio - logarithm is used; overflowed or underflowed ratios fall back to - `ln|later| − ln|earlier|`. + `a = ln(|later| / |earlier|) / Δt` on nonzero same-sign residuals. Nearby + represented magnitudes are evaluated from their exact binary difference + with `ln_1p`; more extreme ratios use a direct finite quotient when possible + and otherwise fall back to `ln|later| − ln|earlier|`. 3. `recover_centered_irregular_residual_log_rate` is the already-centered path. It does **not** re-center. Residuals must be nonzero and have equal sign. Known-truth pairs `(1, 0.5)` with `Δt = 1` recover `ln(0.5)`; for a general admitted interval the scalar result is `ln(0.5) / Δt`. 4. `refuse_cwc_residual_log_rate_as_raw_process_drift` always fails closed. +## Log-ratio numerical contract + +The log-rate must represent the ratio of the two **represented residual +magnitudes**, not the ratio after an avoidable intermediate quotient rounding. +That distinction is material near a power-of-two boundary. If `earlier` is the +binary64 value immediately below `2.0` and `later = 2.0`, the represented +magnitude change is one ULP below the boundary. Forming `later / earlier` +first rounds that ratio to `1 + 2^-52`; applying `ln` then reports a change +close to `2^-52`. The correctly rounded logarithm of the represented endpoint +ratio is instead close to `2^-53`, so the quotient-first path nearly doubles +the temporal rate. + +RED `766ddc7a3d95102450d663f06d2c577306964d79` adds +`irregular_rate_adjacent_growth_contract.rs` through the public already-centered +recovery API. Causal repair `16f21d9a223fb34d52f24d148c7cad9b385e1d91` +uses the exact represented magnitude difference for values within a factor of +two. Growth evaluates `-ln1p(-(later-earlier)/later)` and decay evaluates +`ln1p((later-earlier)/earlier)`. This keeps the difference subtraction in the +Sterbenz exact-subtraction region and avoids the quotient-first rounding step. +Direct-ratio and log-domain fallbacks remain for extreme scale differences, so +previous overflow/underflow recovery behavior is retained rather than traded +for local near-equality accuracy. + +This is a numerical implementation repair of the Driver et al. (2017, Eq. 3) +Longitudinal estimand. It does not create a second arithmetic owner, change the +scientific target, or authorize CWC residual rates as raw-process drift. + ## Numerical mean contract CWC means, occasion means, and admitted irregular-rate means share @@ -106,7 +133,9 @@ that `a`. Fail-closed cases cover empty and singleton-only rows, fewer than two lag-contributing units, non-positive intervals, non-finite scores, non-representable means, overflowing CWC residuals after a finite mean, tiny intervals with huge log-ratios, underflowed nonzero individual rates, -underflowed nonzero final mean rates, and the Curran refusal. +underflowed nonzero final mean rates, and the Curran refusal. Adjacent-float +power-of-two growth additionally verifies that a quotient-rounding artifact +cannot nearly double a representable temporal change. Exact zero CWC residuals also have one public identity. IEEE 754 binary64 has distinct `+0.0` and `-0.0` encodings, and subtraction from a canonical @@ -135,8 +164,9 @@ Signed-zero traceability: The current public numerical regressions include same-sign raw-sum overflow, full-exponent mixed-sign cancellation, minimum-subnormal cancellation, -halfway ties-to-even for same-sign means, the mixed-sign `-31u/3` case, and -nonzero irregular-rate means that are not representable in binary64. Hosted +halfway ties-to-even for same-sign means, the mixed-sign `-31u/3` case, +nonzero irregular-rate means that are not representable in binary64, and +adjacent-float log-rate changes across a power-of-two boundary. Hosted exact-head CI and independent review remain delivery gates; these source contracts do not by themselves establish release readiness. From 698f12f5b2f7a3c194e9d1d3f00c5aeaf10591f8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 20:06:13 +0900 Subject: [PATCH 173/296] test(longitudinal): reject duplicate RMSE component identity --- ...ponent_rmse_duplicate_identity_contract.rs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 crates/longitudinal_core/tests/component_rmse_duplicate_identity_contract.rs diff --git a/crates/longitudinal_core/tests/component_rmse_duplicate_identity_contract.rs b/crates/longitudinal_core/tests/component_rmse_duplicate_identity_contract.rs new file mode 100644 index 000000000..499882a17 --- /dev/null +++ b/crates/longitudinal_core/tests/component_rmse_duplicate_identity_contract.rs @@ -0,0 +1,28 @@ +//! Known-truth recovery denominators require unique component identities. + +use longitudinal_core::{ + ComponentLevel, ComponentValue, LongitudinalError, component_root_mean_square_error, +}; + +#[test] +fn duplicate_component_identity_cannot_reweight_known_truth_rmse() { + let truth = [ + ComponentValue::new(0, 0, ComponentLevel::Between, 1.0), + ComponentValue::new(0, 0, ComponentLevel::Between, 1.0), + ComponentValue::new(1, 0, ComponentLevel::Between, 3.0), + ]; + let decided = [ + ComponentValue::new(0, 0, ComponentLevel::Between, 1.0), + ComponentValue::new(0, 0, ComponentLevel::Between, 1.0), + ComponentValue::new(1, 0, ComponentLevel::Between, 5.0), + ]; + + // The duplicate (unit, occasion, level) would count the zero-error unit + // twice and silently lower the RMSE denominator from the unique-component + // target. Recovery evidence must fail closed instead of changing weight by + // duplicate identity multiplicity. + assert_eq!( + component_root_mean_square_error(&truth, &decided), + Err(LongitudinalError::InvalidComponentPayload) + ); +} From 2fae4cb2e7df2845270bd27192000ca370fb05ad Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 20:06:47 +0900 Subject: [PATCH 174/296] fix(longitudinal): preserve unique RMSE recovery denominator --- crates/longitudinal_core/src/component.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/crates/longitudinal_core/src/component.rs b/crates/longitudinal_core/src/component.rs index 51f74a81d..c19cdd990 100644 --- a/crates/longitudinal_core/src/component.rs +++ b/crates/longitudinal_core/src/component.rs @@ -1,5 +1,7 @@ //! Known-truth RMSE for within/between components. +use std::collections::HashSet; + use crate::{ComponentLevel, LongitudinalError}; /// One unit-specific within or between component. @@ -88,12 +90,16 @@ fn add_scaled_square( /// represented as `endpoint_scale × normalized_difference`, where the latter /// is bounded by two. The root-mean-square accumulator then rescales those /// representations without ever materializing a non-representable residual. +/// Every `(unit, occasion, level)` identity may contribute exactly once; +/// duplicate identities would silently change the recovery denominator and +/// therefore fail closed. /// /// # Errors /// /// Returns [`LongitudinalError::InvalidComponentPayload`] when either slice is -/// empty, the lengths differ, a unit/occasion/level identity mismatches, an -/// input value is non-finite, or the final RMSE is not representable. +/// empty, the lengths differ, a unit/occasion/level identity mismatches or is +/// duplicated, an input value is non-finite, or the final RMSE is not +/// representable. pub fn component_root_mean_square_error( truth: &[ComponentValue], decided: &[ComponentValue], @@ -102,6 +108,7 @@ pub fn component_root_mean_square_error( return Err(LongitudinalError::InvalidComponentPayload); } + let mut seen_identities = HashSet::with_capacity(truth.len()); let mut scale = 0.0_f64; let mut scaled_sum_squares = 0.0_f64; for (truth_row, decided_row) in truth.iter().zip(decided) { @@ -114,6 +121,15 @@ pub fn component_root_mean_square_error( return Err(LongitudinalError::InvalidComponentPayload); } + let identity = ( + truth_row.unit_index(), + truth_row.occasion_index(), + truth_row.level().wire_name(), + ); + if !seen_identities.insert(identity) { + return Err(LongitudinalError::InvalidComponentPayload); + } + let residual = decided_row.value() - truth_row.value(); if residual.is_finite() { add_scaled_square( From 04e9b52d36812a202fe4717e92593221f7bdeb55 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 20:07:23 +0900 Subject: [PATCH 175/296] docs(research): trace RMSE component identity denominator --- ...component-recovery-denominator-identity.md | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 docs/research/component-recovery-denominator-identity.md diff --git a/docs/research/component-recovery-denominator-identity.md b/docs/research/component-recovery-denominator-identity.md new file mode 100644 index 000000000..ffe051b88 --- /dev/null +++ b/docs/research/component-recovery-denominator-identity.md @@ -0,0 +1,30 @@ +# Component recovery denominator identity + +## Decision + +Known-truth component RMSE in the Longitudinal Modeling bounded context uses one contribution per unique `(unit_index, occasion_index, component_level)` identity. Duplicate identity multiplicity is invalid evidence and fails closed with `LongitudinalError::InvalidComponentPayload`; it is not an implicit observation weight. + +This boundary matters because repeating an already-matched component changes the RMSE denominator and can improve or worsen the reported recovery metric without adding a new known-truth target. The metric therefore cannot distinguish a duplicated record from an intentional weight unless identity uniqueness is enforced before accumulation. TEPP does not assign such weights in this API. + +Morris, White, and Crowther (2019) frame simulation evaluation around explicitly defined estimands and performance measures. For TEPP's known-truth recovery contract, the performance unit is the identified longitudinal component, so denominator membership must be determined by component identity rather than accidental row multiplicity. The current published *Standards for Educational and Psychological Testing* remains the 2014 AERA/APA/NCME edition while a revision is in progress; TEPP treats reproducible evidence and clearly specified score/evidence interpretation as governing constraints rather than inferring validity from duplicated records. + +## RED → repair trace + +- RED `698f12f5b2f7a3c194e9d1d3f00c5aeaf10591f8`: `crates/longitudinal_core/tests/component_rmse_duplicate_identity_contract.rs` supplies the same `(unit, occasion, level)` twice in both truth and decided series. The predecessor implementation accepted it and therefore allowed duplicate multiplicity to change the recovery denominator. +- Causal repair `2fae4cb2e7df2845270bd27192000ca370fb05ad`: `crates/longitudinal_core/src/component.rs` records admitted component identities before residual accumulation and rejects a duplicate. Existing pairwise identity matching, finite-input checks, overflow-safe residual scaling, nonzero-underflow refusal, and exact perfect-recovery zero remain unchanged. +- Public API: `component_root_mean_square_error`. +- Domain owner: `crates/longitudinal_core`; this is longitudinal Validation Evidence identity/admission policy, not reusable static psychometric arithmetic for `fast-mlsirm`. + +## Invariant + +For an admitted recovery vector of length `n`, there are exactly `n` unique component identities in both aligned slices. A caller that needs weighted recovery must use a separately named, explicitly weighted contract with its own denominator and validation evidence; duplicate rows are not weights. + +## References + +American Educational Research Association, American Psychological Association, & National Council on Measurement in Education. (2014). *Standards for educational and psychological testing*. American Educational Research Association. https://www.testingstandards.net/open-access-files.html + +Morris, T. P., White, I. R., & Crowther, M. J. (2019). Using simulation studies to evaluate statistical methods. *Statistics in Medicine, 38*(11), 2074–2102. https://doi.org/10.1002/sim.8086 + +### Standards currency note + +The joint AERA/APA/NCME site still distributes the 2014 edition as the current published edition. A joint revision process is active; this note should be revisited when the successor edition is formally published rather than treating revision activity as a released standard. From be0f21e9826bf18f8e4bd67e319495fc4b1be4d9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 20:07:28 +0900 Subject: [PATCH 176/296] docs(changelog): record RMSE identity admission --- CHANGELOG.d/longitudinal-component-rmse-identity.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 CHANGELOG.d/longitudinal-component-rmse-identity.md diff --git a/CHANGELOG.d/longitudinal-component-rmse-identity.md b/CHANGELOG.d/longitudinal-component-rmse-identity.md new file mode 100644 index 000000000..f41fc5c04 --- /dev/null +++ b/CHANGELOG.d/longitudinal-component-rmse-identity.md @@ -0,0 +1,3 @@ +### Longitudinal Modeling + +- Known-truth component RMSE now rejects duplicate `(unit, occasion, level)` identities instead of letting repeated rows silently reweight the scientific recovery denominator. From 8ad72ac91cbddd5ce6432fc70630bad6ce7072ce Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 20:57:33 +0900 Subject: [PATCH 177/296] test(longitudinal): expose RMSE row-order identity bug --- ...nent_rmse_permutation_identity_contract.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 crates/longitudinal_core/tests/component_rmse_permutation_identity_contract.rs diff --git a/crates/longitudinal_core/tests/component_rmse_permutation_identity_contract.rs b/crates/longitudinal_core/tests/component_rmse_permutation_identity_contract.rs new file mode 100644 index 000000000..e17ab7cc9 --- /dev/null +++ b/crates/longitudinal_core/tests/component_rmse_permutation_identity_contract.rs @@ -0,0 +1,19 @@ +use longitudinal_core::{ + ComponentLevel, ComponentValue, component_root_mean_square_error, +}; + +#[test] +fn component_rmse_aligns_by_scientific_identity_not_row_position() { + let truth = [ + ComponentValue::new(7, 2, ComponentLevel::Between, 1.25), + ComponentValue::new(7, 3, ComponentLevel::Within, -0.5), + ComponentValue::new(11, 1, ComponentLevel::Between, 4.0), + ]; + let decided = [ + ComponentValue::new(11, 1, ComponentLevel::Between, 4.0), + ComponentValue::new(7, 2, ComponentLevel::Between, 1.25), + ComponentValue::new(7, 3, ComponentLevel::Within, -0.5), + ]; + + assert_eq!(component_root_mean_square_error(&truth, &decided), Ok(0.0)); +} From 2dd9537e04dd2048559ba707fecd2404db5a2a31 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 20:58:17 +0900 Subject: [PATCH 178/296] fix(longitudinal): align RMSE rows by component identity --- crates/longitudinal_core/src/component.rs | 41 +++++++++++++++-------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/crates/longitudinal_core/src/component.rs b/crates/longitudinal_core/src/component.rs index c19cdd990..aa6f0e95a 100644 --- a/crates/longitudinal_core/src/component.rs +++ b/crates/longitudinal_core/src/component.rs @@ -1,6 +1,6 @@ //! Known-truth RMSE for within/between components. -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use crate::{ComponentLevel, LongitudinalError}; @@ -92,14 +92,14 @@ fn add_scaled_square( /// representations without ever materializing a non-representable residual. /// Every `(unit, occasion, level)` identity may contribute exactly once; /// duplicate identities would silently change the recovery denominator and -/// therefore fail closed. +/// therefore fail closed. Slice order is not scientific identity: truth and +/// recovered rows are aligned by that tuple before residual accumulation. /// /// # Errors /// /// Returns [`LongitudinalError::InvalidComponentPayload`] when either slice is -/// empty, the lengths differ, a unit/occasion/level identity mismatches or is -/// duplicated, an input value is non-finite, or the final RMSE is not -/// representable. +/// empty, the lengths differ, an identity is missing or duplicated, an input +/// value is non-finite, or the final RMSE is not representable. pub fn component_root_mean_square_error( truth: &[ComponentValue], decided: &[ComponentValue], @@ -108,16 +108,26 @@ pub fn component_root_mean_square_error( return Err(LongitudinalError::InvalidComponentPayload); } - let mut seen_identities = HashSet::with_capacity(truth.len()); + let mut decided_by_identity = HashMap::with_capacity(decided.len()); + for decided_row in decided { + if !decided_row.value().is_finite() { + return Err(LongitudinalError::InvalidComponentPayload); + } + let identity = ( + decided_row.unit_index(), + decided_row.occasion_index(), + decided_row.level().wire_name(), + ); + if decided_by_identity.insert(identity, decided_row).is_some() { + return Err(LongitudinalError::InvalidComponentPayload); + } + } + + let mut seen_truth_identities = HashSet::with_capacity(truth.len()); let mut scale = 0.0_f64; let mut scaled_sum_squares = 0.0_f64; - for (truth_row, decided_row) in truth.iter().zip(decided) { - if truth_row.unit_index() != decided_row.unit_index() - || truth_row.occasion_index() != decided_row.occasion_index() - || truth_row.level() != decided_row.level() - || !truth_row.value().is_finite() - || !decided_row.value().is_finite() - { + for truth_row in truth { + if !truth_row.value().is_finite() { return Err(LongitudinalError::InvalidComponentPayload); } @@ -126,9 +136,12 @@ pub fn component_root_mean_square_error( truth_row.occasion_index(), truth_row.level().wire_name(), ); - if !seen_identities.insert(identity) { + if !seen_truth_identities.insert(identity) { return Err(LongitudinalError::InvalidComponentPayload); } + let Some(decided_row) = decided_by_identity.get(&identity) else { + return Err(LongitudinalError::InvalidComponentPayload); + }; let residual = decided_row.value() - truth_row.value(); if residual.is_finite() { From 31994934e253ba91dc19cd1e6ee2d518a9d8f7e8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 20:59:03 +0900 Subject: [PATCH 179/296] docs(longitudinal): trace RMSE identity alignment --- .../component-recovery-denominator-identity.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/research/component-recovery-denominator-identity.md b/docs/research/component-recovery-denominator-identity.md index ffe051b88..c6ef6ec4f 100644 --- a/docs/research/component-recovery-denominator-identity.md +++ b/docs/research/component-recovery-denominator-identity.md @@ -2,22 +2,24 @@ ## Decision -Known-truth component RMSE in the Longitudinal Modeling bounded context uses one contribution per unique `(unit_index, occasion_index, component_level)` identity. Duplicate identity multiplicity is invalid evidence and fails closed with `LongitudinalError::InvalidComponentPayload`; it is not an implicit observation weight. +Known-truth component RMSE in the Longitudinal Modeling bounded context uses one contribution per unique `(unit_index, occasion_index, component_level)` identity. Duplicate identity multiplicity is invalid evidence and fails closed with `LongitudinalError::InvalidComponentPayload`; it is not an implicit observation weight. The order in which truth and recovered rows are serialized is not part of that scientific identity, so the two slices are aligned by the identity tuple before residual accumulation. -This boundary matters because repeating an already-matched component changes the RMSE denominator and can improve or worsen the reported recovery metric without adding a new known-truth target. The metric therefore cannot distinguish a duplicated record from an intentional weight unless identity uniqueness is enforced before accumulation. TEPP does not assign such weights in this API. +This boundary matters for two separate reasons. Repeating an already-matched component changes the RMSE denominator and can improve or worsen the reported recovery metric without adding a new known-truth target. Requiring pairwise row order, on the other hand, can reject two scientifically identical recovery sets merely because an upstream repository, query, or transport emitted the same unique component identities in a different order. TEPP therefore treats tuple identity as authoritative while rejecting missing or duplicated identities. -Morris, White, and Crowther (2019) frame simulation evaluation around explicitly defined estimands and performance measures. For TEPP's known-truth recovery contract, the performance unit is the identified longitudinal component, so denominator membership must be determined by component identity rather than accidental row multiplicity. The current published *Standards for Educational and Psychological Testing* remains the 2014 AERA/APA/NCME edition while a revision is in progress; TEPP treats reproducible evidence and clearly specified score/evidence interpretation as governing constraints rather than inferring validity from duplicated records. +Morris, White, and Crowther (2019) frame simulation evaluation around explicitly defined estimands and performance measures. For TEPP's known-truth recovery contract, the performance unit is the identified longitudinal component, so denominator membership and truth-to-recovery alignment must be determined by component identity rather than accidental row multiplicity or serialization order. The current published *Standards for Educational and Psychological Testing* remains the 2014 AERA/APA/NCME edition while a revision is in progress; TEPP treats reproducible evidence and clearly specified score/evidence interpretation as governing constraints rather than inferring validity from transport order. ## RED → repair trace -- RED `698f12f5b2f7a3c194e9d1d3f00c5aeaf10591f8`: `crates/longitudinal_core/tests/component_rmse_duplicate_identity_contract.rs` supplies the same `(unit, occasion, level)` twice in both truth and decided series. The predecessor implementation accepted it and therefore allowed duplicate multiplicity to change the recovery denominator. -- Causal repair `2fae4cb2e7df2845270bd27192000ca370fb05ad`: `crates/longitudinal_core/src/component.rs` records admitted component identities before residual accumulation and rejects a duplicate. Existing pairwise identity matching, finite-input checks, overflow-safe residual scaling, nonzero-underflow refusal, and exact perfect-recovery zero remain unchanged. +- Duplicate-identity RED `698f12f5b2f7a3c194e9d1d3f00c5aeaf10591f8`: `crates/longitudinal_core/tests/component_rmse_duplicate_identity_contract.rs` supplies the same `(unit, occasion, level)` twice in both truth and decided series. The predecessor implementation accepted it and therefore allowed duplicate multiplicity to change the recovery denominator. +- Duplicate-identity repair `2fae4cb2e7df2845270bd27192000ca370fb05ad`: `crates/longitudinal_core/src/component.rs` records admitted component identities before residual accumulation and rejects a duplicate. +- Row-order RED `8ad72ac91cbddd5ce6432fc70630bad6ce7072ce`: `crates/longitudinal_core/tests/component_rmse_permutation_identity_contract.rs` supplies the same three unique known-truth components and recovered values in a different row order. The predecessor pairwise `zip` path rejects the scientifically identical perfect recovery solely because serialization order differs. +- Row-order causal repair `2dd9537e04dd2048559ba707fecd2404db5a2a31`: recovered rows are indexed by `(unit, occasion, level)` after finite-value and uniqueness admission, truth identities are independently checked for uniqueness, and residuals are accumulated against the matching recovered identity. Missing identities still fail closed. Existing overflow-safe residual scaling, nonzero-underflow refusal, and exact perfect-recovery zero remain unchanged. - Public API: `component_root_mean_square_error`. - Domain owner: `crates/longitudinal_core`; this is longitudinal Validation Evidence identity/admission policy, not reusable static psychometric arithmetic for `fast-mlsirm`. ## Invariant -For an admitted recovery vector of length `n`, there are exactly `n` unique component identities in both aligned slices. A caller that needs weighted recovery must use a separately named, explicitly weighted contract with its own denominator and validation evidence; duplicate rows are not weights. +For an admitted recovery vector of length `n`, there are exactly `n` unique component identities in each slice and the two identity sets are equal. Any permutation of either slice that preserves those identity-value pairs yields the same RMSE. A caller that needs weighted recovery must use a separately named, explicitly weighted contract with its own denominator and validation evidence; duplicate rows are not weights. ## References From 5aa44c3df7deb44183cb7156d5e3d7713e6d6a08 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 20:59:12 +0900 Subject: [PATCH 180/296] docs(changelog): record RMSE identity alignment --- CHANGELOG.d/longitudinal-component-rmse-identity.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.d/longitudinal-component-rmse-identity.md b/CHANGELOG.d/longitudinal-component-rmse-identity.md index f41fc5c04..63cb91c34 100644 --- a/CHANGELOG.d/longitudinal-component-rmse-identity.md +++ b/CHANGELOG.d/longitudinal-component-rmse-identity.md @@ -1,3 +1,4 @@ ### Longitudinal Modeling -- Known-truth component RMSE now rejects duplicate `(unit, occasion, level)` identities instead of letting repeated rows silently reweight the scientific recovery denominator. +- Known-truth component RMSE rejects duplicate `(unit, occasion, level)` identities instead of letting repeated rows silently reweight the scientific recovery denominator. +- Truth and recovered component rows are matched by `(unit, occasion, level)` identity rather than slice position, so a serialization-order permutation cannot turn scientifically identical recovery evidence into an invalid payload. From 5fb93c40eddbd9e7920196ef09594457b8ac72d3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 21:01:48 +0900 Subject: [PATCH 181/296] test(longitudinal): expose RMSE truth-order rounding drift --- ...mponent_rmse_truth_permutation_contract.rs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 crates/longitudinal_core/tests/component_rmse_truth_permutation_contract.rs diff --git a/crates/longitudinal_core/tests/component_rmse_truth_permutation_contract.rs b/crates/longitudinal_core/tests/component_rmse_truth_permutation_contract.rs new file mode 100644 index 000000000..e7385ad03 --- /dev/null +++ b/crates/longitudinal_core/tests/component_rmse_truth_permutation_contract.rs @@ -0,0 +1,31 @@ +use longitudinal_core::{ + ComponentLevel, ComponentValue, component_root_mean_square_error, +}; + +#[test] +fn component_rmse_is_bit_identical_under_truth_row_permutation() { + let just_below_one = f64::from_bits(1.0_f64.to_bits() - 1); + let truth_a = [ + ComponentValue::new(0, 0, ComponentLevel::Between, 0.0), + ComponentValue::new(1, 0, ComponentLevel::Between, 0.0), + ComponentValue::new(2, 0, ComponentLevel::Between, 0.0), + ComponentValue::new(3, 0, ComponentLevel::Between, 0.0), + ]; + let truth_b = [ + ComponentValue::new(0, 0, ComponentLevel::Between, 0.0), + ComponentValue::new(1, 0, ComponentLevel::Between, 0.0), + ComponentValue::new(3, 0, ComponentLevel::Between, 0.0), + ComponentValue::new(2, 0, ComponentLevel::Between, 0.0), + ]; + let decided = [ + ComponentValue::new(0, 0, ComponentLevel::Between, 1.0), + ComponentValue::new(1, 0, ComponentLevel::Between, 1e-100), + ComponentValue::new(2, 0, ComponentLevel::Between, 3.0), + ComponentValue::new(3, 0, ComponentLevel::Between, just_below_one), + ]; + + let first = component_root_mean_square_error(&truth_a, &decided).expect("first RMSE"); + let permuted = component_root_mean_square_error(&truth_b, &decided).expect("permuted RMSE"); + + assert_eq!(first.to_bits(), permuted.to_bits()); +} From 025dce7fd98cfb4f94ea790cacd555b744095377 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 21:02:34 +0900 Subject: [PATCH 182/296] fix(longitudinal): canonicalize RMSE identity accumulation order --- crates/longitudinal_core/src/component.rs | 41 +++++++++++++---------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/crates/longitudinal_core/src/component.rs b/crates/longitudinal_core/src/component.rs index aa6f0e95a..492f1f623 100644 --- a/crates/longitudinal_core/src/component.rs +++ b/crates/longitudinal_core/src/component.rs @@ -1,6 +1,6 @@ //! Known-truth RMSE for within/between components. -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use crate::{ComponentLevel, LongitudinalError}; @@ -93,7 +93,8 @@ fn add_scaled_square( /// Every `(unit, occasion, level)` identity may contribute exactly once; /// duplicate identities would silently change the recovery denominator and /// therefore fail closed. Slice order is not scientific identity: truth and -/// recovered rows are aligned by that tuple before residual accumulation. +/// recovered rows are aligned by that tuple and accumulated in canonical tuple +/// order so row permutations cannot change the deterministic `f64` reference. /// /// # Errors /// @@ -108,6 +109,21 @@ pub fn component_root_mean_square_error( return Err(LongitudinalError::InvalidComponentPayload); } + let mut truth_by_identity = HashMap::with_capacity(truth.len()); + for truth_row in truth { + if !truth_row.value().is_finite() { + return Err(LongitudinalError::InvalidComponentPayload); + } + let identity = ( + truth_row.unit_index(), + truth_row.occasion_index(), + truth_row.level().wire_name(), + ); + if truth_by_identity.insert(identity, truth_row).is_some() { + return Err(LongitudinalError::InvalidComponentPayload); + } + } + let mut decided_by_identity = HashMap::with_capacity(decided.len()); for decided_row in decided { if !decided_row.value().is_finite() { @@ -123,23 +139,14 @@ pub fn component_root_mean_square_error( } } - let mut seen_truth_identities = HashSet::with_capacity(truth.len()); + let mut identities: Vec<_> = truth_by_identity.keys().copied().collect(); + identities.sort_unstable(); + let mut scale = 0.0_f64; let mut scaled_sum_squares = 0.0_f64; - for truth_row in truth { - if !truth_row.value().is_finite() { - return Err(LongitudinalError::InvalidComponentPayload); - } - - let identity = ( - truth_row.unit_index(), - truth_row.occasion_index(), - truth_row.level().wire_name(), - ); - if !seen_truth_identities.insert(identity) { - return Err(LongitudinalError::InvalidComponentPayload); - } - let Some(decided_row) = decided_by_identity.get(&identity) else { + for identity in identities { + let truth_row = truth_by_identity[&identity]; + let Some(decided_row) = decided_by_identity.get(&identity).copied() else { return Err(LongitudinalError::InvalidComponentPayload); }; From 976ce7d710125717ff2f8daeb943d54278c4acde Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 21:02:47 +0900 Subject: [PATCH 183/296] test(longitudinal): cover truth-side RMSE duplicate admission --- ...omponent_rmse_duplicate_identity_contract.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/crates/longitudinal_core/tests/component_rmse_duplicate_identity_contract.rs b/crates/longitudinal_core/tests/component_rmse_duplicate_identity_contract.rs index 499882a17..9973047ec 100644 --- a/crates/longitudinal_core/tests/component_rmse_duplicate_identity_contract.rs +++ b/crates/longitudinal_core/tests/component_rmse_duplicate_identity_contract.rs @@ -26,3 +26,20 @@ fn duplicate_component_identity_cannot_reweight_known_truth_rmse() { Err(LongitudinalError::InvalidComponentPayload) ); } + +#[test] +fn duplicate_truth_identity_fails_even_when_recovered_identities_are_unique() { + let truth = [ + ComponentValue::new(0, 0, ComponentLevel::Between, 1.0), + ComponentValue::new(0, 0, ComponentLevel::Between, 1.0), + ]; + let decided = [ + ComponentValue::new(0, 0, ComponentLevel::Between, 1.0), + ComponentValue::new(1, 0, ComponentLevel::Between, 1.0), + ]; + + assert_eq!( + component_root_mean_square_error(&truth, &decided), + Err(LongitudinalError::InvalidComponentPayload) + ); +} From 1106c005060c667e892f62ae8763e6b0cc478f0a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 21:03:28 +0900 Subject: [PATCH 184/296] docs(longitudinal): trace deterministic RMSE identity order --- .../component-recovery-denominator-identity.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/research/component-recovery-denominator-identity.md b/docs/research/component-recovery-denominator-identity.md index c6ef6ec4f..6649d3977 100644 --- a/docs/research/component-recovery-denominator-identity.md +++ b/docs/research/component-recovery-denominator-identity.md @@ -2,9 +2,9 @@ ## Decision -Known-truth component RMSE in the Longitudinal Modeling bounded context uses one contribution per unique `(unit_index, occasion_index, component_level)` identity. Duplicate identity multiplicity is invalid evidence and fails closed with `LongitudinalError::InvalidComponentPayload`; it is not an implicit observation weight. The order in which truth and recovered rows are serialized is not part of that scientific identity, so the two slices are aligned by the identity tuple before residual accumulation. +Known-truth component RMSE in the Longitudinal Modeling bounded context uses one contribution per unique `(unit_index, occasion_index, component_level)` identity. Duplicate identity multiplicity is invalid evidence and fails closed with `LongitudinalError::InvalidComponentPayload`; it is not an implicit observation weight. The order in which truth and recovered rows are serialized is not part of that scientific identity. Both slices are therefore admitted as identity maps and residuals are accumulated in canonical tuple order. -This boundary matters for two separate reasons. Repeating an already-matched component changes the RMSE denominator and can improve or worsen the reported recovery metric without adding a new known-truth target. Requiring pairwise row order, on the other hand, can reject two scientifically identical recovery sets merely because an upstream repository, query, or transport emitted the same unique component identities in a different order. TEPP therefore treats tuple identity as authoritative while rejecting missing or duplicated identities. +This boundary matters for two separate reasons. Repeating an already-matched component changes the RMSE denominator and can improve or worsen the reported recovery metric without adding a new known-truth target. Requiring pairwise row order can reject two scientifically identical recovery sets merely because an upstream repository, query, or transport emitted the same unique component identities in a different order. Even after identity lookup removes pairwise order coupling, iterating residuals in caller-provided truth order can still change the last bit of the scaled binary64 sum of squares. TEPP therefore treats tuple identity as authoritative for both alignment and deterministic accumulation while rejecting missing or duplicated identities. Morris, White, and Crowther (2019) frame simulation evaluation around explicitly defined estimands and performance measures. For TEPP's known-truth recovery contract, the performance unit is the identified longitudinal component, so denominator membership and truth-to-recovery alignment must be determined by component identity rather than accidental row multiplicity or serialization order. The current published *Standards for Educational and Psychological Testing* remains the 2014 AERA/APA/NCME edition while a revision is in progress; TEPP treats reproducible evidence and clearly specified score/evidence interpretation as governing constraints rather than inferring validity from transport order. @@ -12,14 +12,17 @@ Morris, White, and Crowther (2019) frame simulation evaluation around explicitly - Duplicate-identity RED `698f12f5b2f7a3c194e9d1d3f00c5aeaf10591f8`: `crates/longitudinal_core/tests/component_rmse_duplicate_identity_contract.rs` supplies the same `(unit, occasion, level)` twice in both truth and decided series. The predecessor implementation accepted it and therefore allowed duplicate multiplicity to change the recovery denominator. - Duplicate-identity repair `2fae4cb2e7df2845270bd27192000ca370fb05ad`: `crates/longitudinal_core/src/component.rs` records admitted component identities before residual accumulation and rejects a duplicate. -- Row-order RED `8ad72ac91cbddd5ce6432fc70630bad6ce7072ce`: `crates/longitudinal_core/tests/component_rmse_permutation_identity_contract.rs` supplies the same three unique known-truth components and recovered values in a different row order. The predecessor pairwise `zip` path rejects the scientifically identical perfect recovery solely because serialization order differs. -- Row-order causal repair `2dd9537e04dd2048559ba707fecd2404db5a2a31`: recovered rows are indexed by `(unit, occasion, level)` after finite-value and uniqueness admission, truth identities are independently checked for uniqueness, and residuals are accumulated against the matching recovered identity. Missing identities still fail closed. Existing overflow-safe residual scaling, nonzero-underflow refusal, and exact perfect-recovery zero remain unchanged. +- Cross-slice row-order RED `8ad72ac91cbddd5ce6432fc70630bad6ce7072ce`: `crates/longitudinal_core/tests/component_rmse_permutation_identity_contract.rs` supplies the same three unique known-truth components and recovered values in a different recovered-row order. The predecessor pairwise `zip` path rejects the scientifically identical perfect recovery solely because serialization order differs. +- Initial identity-alignment repair `2dd9537e04dd2048559ba707fecd2404db5a2a31`: recovered rows are indexed by `(unit, occasion, level)` so decided-row permutation no longer changes admission. Review of that repair found that residual accumulation still followed caller-provided truth order, leaving deterministic binary64 output order-sensitive. +- Truth-order rounding RED `5fb93c40eddbd9e7920196ef09594457b8ac72d3`: `crates/longitudinal_core/tests/component_rmse_truth_permutation_contract.rs` uses residual magnitudes `1`, `1e-100`, `3`, and the representable value immediately below `1`. Two truth-row permutations contain exactly the same identity-value pairs but drive the scaled sum-of-squares through different rounding paths and produce different RMSE bit patterns on the predecessor identity-aligned implementation. +- Causal deterministic repair `025dce7fd98cfb4f94ea790cacd555b744095377`: both truth and recovered slices are admitted into unique identity maps, truth identities are sorted by `(unit, occasion, level wire name)`, and residual accumulation follows that canonical order. Missing identities still fail closed. Existing overflow-safe residual scaling, nonzero-underflow refusal, and exact perfect-recovery zero remain unchanged. +- Edge-coverage reinforcement `976ce7d710125717ff2f8daeb943d54278c4acde`: a truth-side duplicate with unique recovered identities exercises the independent truth uniqueness gate rather than relying only on a payload where both slices contain duplicates. - Public API: `component_root_mean_square_error`. - Domain owner: `crates/longitudinal_core`; this is longitudinal Validation Evidence identity/admission policy, not reusable static psychometric arithmetic for `fast-mlsirm`. ## Invariant -For an admitted recovery vector of length `n`, there are exactly `n` unique component identities in each slice and the two identity sets are equal. Any permutation of either slice that preserves those identity-value pairs yields the same RMSE. A caller that needs weighted recovery must use a separately named, explicitly weighted contract with its own denominator and validation evidence; duplicate rows are not weights. +For an admitted recovery vector of length `n`, there are exactly `n` unique component identities in each slice and the two identity sets are equal. Any permutation of either slice that preserves those identity-value pairs yields bit-identical deterministic CPU `f64` RMSE. A caller that needs weighted recovery must use a separately named, explicitly weighted contract with its own denominator and validation evidence; duplicate rows are not weights. ## References From 464df01e9268f13ba8e135f72d0fb62beead3e04 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 21:03:38 +0900 Subject: [PATCH 185/296] docs(changelog): record deterministic RMSE identity order --- CHANGELOG.d/longitudinal-component-rmse-identity.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.d/longitudinal-component-rmse-identity.md b/CHANGELOG.d/longitudinal-component-rmse-identity.md index 63cb91c34..ad788311d 100644 --- a/CHANGELOG.d/longitudinal-component-rmse-identity.md +++ b/CHANGELOG.d/longitudinal-component-rmse-identity.md @@ -1,4 +1,4 @@ ### Longitudinal Modeling - Known-truth component RMSE rejects duplicate `(unit, occasion, level)` identities instead of letting repeated rows silently reweight the scientific recovery denominator. -- Truth and recovered component rows are matched by `(unit, occasion, level)` identity rather than slice position, so a serialization-order permutation cannot turn scientifically identical recovery evidence into an invalid payload. +- Truth and recovered component rows are matched by `(unit, occasion, level)` identity and accumulated in canonical identity order, so serialization-order permutations preserve the same deterministic CPU `f64` recovery result instead of changing admission or the last-bit RMSE rounding path. From 0a03041c5c1c2152fe90f86204edf2c957cebbfe Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 00:08:30 +0900 Subject: [PATCH 186/296] test(longitudinal): reject occasion aliases for stable between recovery --- ...t_rmse_stable_between_identity_contract.rs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 crates/longitudinal_core/tests/component_rmse_stable_between_identity_contract.rs diff --git a/crates/longitudinal_core/tests/component_rmse_stable_between_identity_contract.rs b/crates/longitudinal_core/tests/component_rmse_stable_between_identity_contract.rs new file mode 100644 index 000000000..c91cdb1f0 --- /dev/null +++ b/crates/longitudinal_core/tests/component_rmse_stable_between_identity_contract.rs @@ -0,0 +1,22 @@ +use longitudinal_core::{ + ComponentLevel, ComponentValue, LongitudinalError, component_root_mean_square_error, +}; + +#[test] +fn stable_between_component_cannot_gain_weight_from_occasion_aliases() { + let truth = [ + ComponentValue::new(7, 0, ComponentLevel::Between, 1.25), + ComponentValue::new(7, 2, ComponentLevel::Between, 1.25), + ComponentValue::new(11, 1, ComponentLevel::Within, 0.0), + ]; + let decided = [ + ComponentValue::new(7, 0, ComponentLevel::Between, 1.25), + ComponentValue::new(7, 2, ComponentLevel::Between, 1.25), + ComponentValue::new(11, 1, ComponentLevel::Within, 3.0), + ]; + + assert_eq!( + component_root_mean_square_error(&truth, &decided), + Err(LongitudinalError::InvalidComponentPayload) + ); +} From 53563e975dd10e4f18218880cca2bb4cd79d0e73 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 00:08:47 +0900 Subject: [PATCH 187/296] test(longitudinal): keep between recovery fixture unit-level --- .../tests/component_rmse_permutation_identity_contract.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/longitudinal_core/tests/component_rmse_permutation_identity_contract.rs b/crates/longitudinal_core/tests/component_rmse_permutation_identity_contract.rs index e17ab7cc9..ff3ec21e2 100644 --- a/crates/longitudinal_core/tests/component_rmse_permutation_identity_contract.rs +++ b/crates/longitudinal_core/tests/component_rmse_permutation_identity_contract.rs @@ -5,13 +5,13 @@ use longitudinal_core::{ #[test] fn component_rmse_aligns_by_scientific_identity_not_row_position() { let truth = [ - ComponentValue::new(7, 2, ComponentLevel::Between, 1.25), + ComponentValue::new(7, 0, ComponentLevel::Between, 1.25), ComponentValue::new(7, 3, ComponentLevel::Within, -0.5), - ComponentValue::new(11, 1, ComponentLevel::Between, 4.0), + ComponentValue::new(11, 0, ComponentLevel::Between, 4.0), ]; let decided = [ - ComponentValue::new(11, 1, ComponentLevel::Between, 4.0), - ComponentValue::new(7, 2, ComponentLevel::Between, 1.25), + ComponentValue::new(11, 0, ComponentLevel::Between, 4.0), + ComponentValue::new(7, 0, ComponentLevel::Between, 1.25), ComponentValue::new(7, 3, ComponentLevel::Within, -0.5), ]; From ec2c1219127e834d89051415f1de8e1cb48507aa Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 00:10:32 +0900 Subject: [PATCH 188/296] fix(longitudinal): make stable between recovery unit-level --- crates/longitudinal_core/src/component.rs | 48 ++++++++++++++--------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/crates/longitudinal_core/src/component.rs b/crates/longitudinal_core/src/component.rs index 492f1f623..59a6a7ada 100644 --- a/crates/longitudinal_core/src/component.rs +++ b/crates/longitudinal_core/src/component.rs @@ -16,8 +16,9 @@ pub struct ComponentValue { impl ComponentValue { /// Construct a component record from its identity fields and raw value. /// - /// The value is stored exactly as given, including non-finite values; - /// this constructor performs no validation. + /// The value and identity fields are stored exactly as given; this + /// constructor performs no validation. Recovery admission enforces that a + /// stable between-unit component uses canonical occasion index `0`. #[must_use] pub const fn new( unit_index: u32, @@ -58,6 +59,19 @@ impl ComponentValue { } } +fn validated_component_identity( + row: &ComponentValue, +) -> Result<(u32, u32, &'static str), LongitudinalError> { + if row.level() == ComponentLevel::Between && row.occasion_index() != 0 { + return Err(LongitudinalError::InvalidComponentPayload); + } + Ok(( + row.unit_index(), + row.occasion_index(), + row.level().wire_name(), + )) +} + fn add_scaled_square( scale: &mut f64, scaled_sum_squares: &mut f64, @@ -90,17 +104,21 @@ fn add_scaled_square( /// represented as `endpoint_scale × normalized_difference`, where the latter /// is bounded by two. The root-mean-square accumulator then rescales those /// representations without ever materializing a non-representable residual. -/// Every `(unit, occasion, level)` identity may contribute exactly once; -/// duplicate identities would silently change the recovery denominator and -/// therefore fail closed. Slice order is not scientific identity: truth and -/// recovered rows are aligned by that tuple and accumulated in canonical tuple -/// order so row permutations cannot change the deterministic `f64` reference. +/// Stable between-unit components have one unit-level scientific identity and +/// therefore use canonical occasion index `0`; within-unit components retain +/// their actual `(unit, occasion)` identity. Every admitted identity may +/// contribute exactly once. Duplicate or aliased identities would silently +/// change the recovery denominator and therefore fail closed. Slice order is +/// not scientific identity: truth and recovered rows are aligned by the +/// admitted identity tuple and accumulated in canonical tuple order so row +/// permutations cannot change the deterministic `f64` reference. /// /// # Errors /// /// Returns [`LongitudinalError::InvalidComponentPayload`] when either slice is -/// empty, the lengths differ, an identity is missing or duplicated, an input -/// value is non-finite, or the final RMSE is not representable. +/// empty, the lengths differ, a stable between-unit component uses a nonzero +/// occasion index, an identity is missing or duplicated, an input value is +/// non-finite, or the final RMSE is not representable. pub fn component_root_mean_square_error( truth: &[ComponentValue], decided: &[ComponentValue], @@ -114,11 +132,7 @@ pub fn component_root_mean_square_error( if !truth_row.value().is_finite() { return Err(LongitudinalError::InvalidComponentPayload); } - let identity = ( - truth_row.unit_index(), - truth_row.occasion_index(), - truth_row.level().wire_name(), - ); + let identity = validated_component_identity(truth_row)?; if truth_by_identity.insert(identity, truth_row).is_some() { return Err(LongitudinalError::InvalidComponentPayload); } @@ -129,11 +143,7 @@ pub fn component_root_mean_square_error( if !decided_row.value().is_finite() { return Err(LongitudinalError::InvalidComponentPayload); } - let identity = ( - decided_row.unit_index(), - decided_row.occasion_index(), - decided_row.level().wire_name(), - ); + let identity = validated_component_identity(decided_row)?; if decided_by_identity.insert(identity, decided_row).is_some() { return Err(LongitudinalError::InvalidComponentPayload); } From c9cb68d30ea81bf15ec1326e74e94679f80287d5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 00:11:52 +0900 Subject: [PATCH 189/296] docs(research): trace unit-level between recovery identity --- .../component-recovery-denominator-identity.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/docs/research/component-recovery-denominator-identity.md b/docs/research/component-recovery-denominator-identity.md index 6649d3977..cd45b89f8 100644 --- a/docs/research/component-recovery-denominator-identity.md +++ b/docs/research/component-recovery-denominator-identity.md @@ -2,11 +2,13 @@ ## Decision -Known-truth component RMSE in the Longitudinal Modeling bounded context uses one contribution per unique `(unit_index, occasion_index, component_level)` identity. Duplicate identity multiplicity is invalid evidence and fails closed with `LongitudinalError::InvalidComponentPayload`; it is not an implicit observation weight. The order in which truth and recovered rows are serialized is not part of that scientific identity. Both slices are therefore admitted as identity maps and residuals are accumulated in canonical tuple order. +Known-truth component RMSE in the Longitudinal Modeling bounded context uses one contribution per admitted scientific component identity. A stable `Between` component is unit-level, not occasion-level: TEPP's decomposition emits exactly one such component per unit and encodes its non-temporal carrier with canonical `occasion_index = 0`. A `Within` component remains occasion-specific and is identified by `(unit_index, occasion_index, component_level)`. A `Between` row with a nonzero occasion index is therefore an invalid alias rather than a second target or an implicit weight. -This boundary matters for two separate reasons. Repeating an already-matched component changes the RMSE denominator and can improve or worsen the reported recovery metric without adding a new known-truth target. Requiring pairwise row order can reject two scientifically identical recovery sets merely because an upstream repository, query, or transport emitted the same unique component identities in a different order. Even after identity lookup removes pairwise order coupling, iterating residuals in caller-provided truth order can still change the last bit of the scaled binary64 sum of squares. TEPP therefore treats tuple identity as authoritative for both alignment and deterministic accumulation while rejecting missing or duplicated identities. +Duplicate identity multiplicity is invalid evidence and fails closed with `LongitudinalError::InvalidComponentPayload`; it is not an implicit observation weight. The order in which truth and recovered rows are serialized is also not part of scientific identity. Both slices are admitted as identity maps and residuals are accumulated in canonical identity order. -Morris, White, and Crowther (2019) frame simulation evaluation around explicitly defined estimands and performance measures. For TEPP's known-truth recovery contract, the performance unit is the identified longitudinal component, so denominator membership and truth-to-recovery alignment must be determined by component identity rather than accidental row multiplicity or serialization order. The current published *Standards for Educational and Psychological Testing* remains the 2014 AERA/APA/NCME edition while a revision is in progress; TEPP treats reproducible evidence and clearly specified score/evidence interpretation as governing constraints rather than inferring validity from transport order. +This boundary matters in three distinct ways. Repeating an already-matched component changes the RMSE denominator and can improve or worsen the reported recovery metric without adding a new known-truth target. Giving a stable between-unit component different occasion numbers creates the same denominator distortion while disguising the duplicate as two tuple identities. Requiring pairwise row order can instead reject two scientifically identical recovery sets merely because an upstream repository, query, or transport emitted the same unique component identities in a different order. Even after identity lookup removes pairwise order coupling, iterating residuals in caller-provided truth order can still change the last bit of the scaled binary64 sum of squares. TEPP therefore treats bounded-context component identity as authoritative for admission, alignment, denominator membership, and deterministic accumulation. + +Curran and Bauer (2011) distinguish stable between-person information from occasion-specific within-person deviations in longitudinal decomposition, while Hamaker, Kuiper, and Grasman (2015) explicitly separate stable between-person differences from the within-person process. Those sources support the scientific level distinction; `occasion_index = 0` is TEPP's canonical storage/projection encoding for its one stable between component per unit, not a claim that the stable component occurs at substantive occasion zero. Morris, White, and Crowther (2019) frame simulation evaluation around explicitly defined estimands and performance measures. For TEPP's known-truth recovery contract, denominator membership and truth-to-recovery alignment therefore follow the identified longitudinal component rather than accidental row multiplicity, serialization order, or a synthetic occasion alias. The current published *Standards for Educational and Psychological Testing* remains the 2014 AERA/APA/NCME edition while a revision is in progress; TEPP treats reproducible evidence and clearly specified score/evidence interpretation as governing constraints rather than inferring validity from transport shape. ## RED → repair trace @@ -17,17 +19,24 @@ Morris, White, and Crowther (2019) frame simulation evaluation around explicitly - Truth-order rounding RED `5fb93c40eddbd9e7920196ef09594457b8ac72d3`: `crates/longitudinal_core/tests/component_rmse_truth_permutation_contract.rs` uses residual magnitudes `1`, `1e-100`, `3`, and the representable value immediately below `1`. Two truth-row permutations contain exactly the same identity-value pairs but drive the scaled sum-of-squares through different rounding paths and produce different RMSE bit patterns on the predecessor identity-aligned implementation. - Causal deterministic repair `025dce7fd98cfb4f94ea790cacd555b744095377`: both truth and recovered slices are admitted into unique identity maps, truth identities are sorted by `(unit, occasion, level wire name)`, and residual accumulation follows that canonical order. Missing identities still fail closed. Existing overflow-safe residual scaling, nonzero-underflow refusal, and exact perfect-recovery zero remain unchanged. - Edge-coverage reinforcement `976ce7d710125717ff2f8daeb943d54278c4acde`: a truth-side duplicate with unique recovered identities exercises the independent truth uniqueness gate rather than relying only on a payload where both slices contain duplicates. +- Stable-between alias RED `0a03041c5c1c2152fe90f86204edf2c957cebbfe`: `crates/longitudinal_core/tests/component_rmse_stable_between_identity_contract.rs` supplies two stable `Between` rows for one unit under different occasion indices. The predecessor identity tuple accepts both as separate targets and lets the alias change the recovery denominator. +- Fixture correction `53563e975dd10e4f18218880cca2bb4cd79d0e73`: the earlier cross-slice permutation contract now keeps all stable `Between` fixtures at canonical occasion index `0`; transport-order coverage remains intact without encoding a scientifically invalid between-at-occasion fixture. +- Stable-between identity repair `ec2c1219127e834d89051415f1de8e1cb48507aa`: `component_root_mean_square_error` validates the bounded-context identity before either map insertion. `Between` requires canonical occasion index `0`; `Within` retains its actual occasion. The repair changes admission only and leaves residual arithmetic, canonical accumulation order, nonzero-underflow refusal, and exact perfect-recovery zero unchanged. - Public API: `component_root_mean_square_error`. - Domain owner: `crates/longitudinal_core`; this is longitudinal Validation Evidence identity/admission policy, not reusable static psychometric arithmetic for `fast-mlsirm`. ## Invariant -For an admitted recovery vector of length `n`, there are exactly `n` unique component identities in each slice and the two identity sets are equal. Any permutation of either slice that preserves those identity-value pairs yields bit-identical deterministic CPU `f64` RMSE. A caller that needs weighted recovery must use a separately named, explicitly weighted contract with its own denominator and validation evidence; duplicate rows are not weights. +For an admitted recovery vector of length `n`, there are exactly `n` unique scientific component identities in each slice and the two identity sets are equal. Each unit can contribute at most one stable `Between` target, encoded with `occasion_index = 0`; each `Within` target is identified by its actual unit and occasion. Any permutation of either slice that preserves those admitted identity-value pairs yields bit-identical deterministic CPU `f64` RMSE. A caller that needs weighted recovery must use a separately named, explicitly weighted contract with its own denominator and validation evidence; duplicate rows and between-component occasion aliases are not weights. ## References American Educational Research Association, American Psychological Association, & National Council on Measurement in Education. (2014). *Standards for educational and psychological testing*. American Educational Research Association. https://www.testingstandards.net/open-access-files.html +Curran, P. J., & Bauer, D. J. (2011). The disaggregation of within-person and between-person effects in longitudinal models of change. *Annual Review of Psychology, 62*, 583–619. https://doi.org/10.1146/annurev.psych.093008.100356 + +Hamaker, E. L., Kuiper, R. M., & Grasman, R. P. P. P. (2015). A critique of the cross-lagged panel model. *Psychological Methods, 20*(1), 102–116. https://doi.org/10.1037/a0038889 + Morris, T. P., White, I. R., & Crowther, M. J. (2019). Using simulation studies to evaluate statistical methods. *Statistics in Medicine, 38*(11), 2074–2102. https://doi.org/10.1002/sim.8086 ### Standards currency note From c6680450152b1e0a2c9abb553772d74a23923335 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 00:12:05 +0900 Subject: [PATCH 190/296] docs(changelog): record stable between recovery identity --- CHANGELOG.d/longitudinal-component-rmse-identity.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.d/longitudinal-component-rmse-identity.md b/CHANGELOG.d/longitudinal-component-rmse-identity.md index ad788311d..5ad924ceb 100644 --- a/CHANGELOG.d/longitudinal-component-rmse-identity.md +++ b/CHANGELOG.d/longitudinal-component-rmse-identity.md @@ -1,4 +1,5 @@ ### Longitudinal Modeling -- Known-truth component RMSE rejects duplicate `(unit, occasion, level)` identities instead of letting repeated rows silently reweight the scientific recovery denominator. -- Truth and recovered component rows are matched by `(unit, occasion, level)` identity and accumulated in canonical identity order, so serialization-order permutations preserve the same deterministic CPU `f64` recovery result instead of changing admission or the last-bit RMSE rounding path. +- Known-truth component RMSE rejects duplicate admitted identities instead of letting repeated rows silently reweight the scientific recovery denominator. +- Stable `Between` recovery is unit-level and uses the decomposition contract's canonical `occasion_index = 0`; a nonzero occasion alias is rejected rather than counted as another target or implicit weight. `Within` recovery remains unit-and-occasion specific. +- Truth and recovered component rows are matched by admitted scientific identity and accumulated in canonical identity order, so serialization-order permutations preserve the same deterministic CPU `f64` recovery result instead of changing admission or the last-bit RMSE rounding path. From 372971d172f24a8a51f2203e7451b14fbe95b490 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 09:37:10 +0900 Subject: [PATCH 191/296] ci(actions): absorb main docs-quality concurrency and path filters --- .github/workflows/docs-quality.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docs-quality.yml b/.github/workflows/docs-quality.yml index edc8720e8..cf03a3ef3 100644 --- a/.github/workflows/docs-quality.yml +++ b/.github/workflows/docs-quality.yml @@ -13,7 +13,6 @@ on: - ".github/workflows/**" - "scripts/validate_documentation.py" - "tests/quality/test_validate_documentation.py" - - "crates/**" push: branches: - main @@ -23,15 +22,14 @@ on: - ".github/workflows/**" - "scripts/validate_documentation.py" - "tests/quality/test_validate_documentation.py" - - "crates/**" workflow_dispatch: permissions: contents: read concurrency: - group: docs-quality-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true + group: docs-quality-${{ github.repository }}-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: validate: From 6103ed10f9835af7815c26d4d116de921f3d713c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 09:37:32 +0900 Subject: [PATCH 192/296] docs(validation): absorb main planning-pack workflow ownership wording --- docs/validation/planning-pack-validation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/validation/planning-pack-validation.md b/docs/validation/planning-pack-validation.md index 5f3ec77d7..5dd1f5012 100644 --- a/docs/validation/planning-pack-validation.md +++ b/docs/validation/planning-pack-validation.md @@ -16,7 +16,7 @@ The validation pack checks the approved product requirements, delivery roadmap, - No unresolved placeholder markers occur in approved artifacts. - Required governance files exist. - GitHub Action references are pinned to full commit SHAs. -- Hourly PR maintenance and hourly product-development schedules are distinct and concurrency bounded. +- PR maintenance is owned by the central required workflow; local product development remains separately concurrency bounded. - Autonomous LLM development maps `NVIDIA_NIM_API_KEY` to the provider runtime and contains no `COPILOT_GITHUB_TOKEN` reference. - Source artifacts are listed in a SHA-256 manifest and the generated source archives are reproducible. - Temporal leakage, relation-aware splitting, multilevel/multiple-membership, realistic truth simulation, 100% production coverage/docstrings, CPU/GPU parity, SBOM, provenance, and rollback requirements are represented in the plan. From 35429421d8e9bbf68a62b3a1f4737d0ec0cf0229 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 09:37:36 +0900 Subject: [PATCH 193/296] test(actions): add main workflow queue hygiene contracts --- tests/quality/test_workflow_queue_hygiene.py | 67 ++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 tests/quality/test_workflow_queue_hygiene.py diff --git a/tests/quality/test_workflow_queue_hygiene.py b/tests/quality/test_workflow_queue_hygiene.py new file mode 100644 index 000000000..431687af5 --- /dev/null +++ b/tests/quality/test_workflow_queue_hygiene.py @@ -0,0 +1,67 @@ +"""Queue-isolation contracts for repository-local GitHub Actions.""" + +from pathlib import Path +import unittest + + +WORKFLOW_DIRECTORY = Path(".github/workflows") + + +class WorkflowQueueHygieneTests(unittest.TestCase): + """Keep local validation isolated by repository and pull request.""" + + def test_pull_request_workflows_cancel_only_older_same_pr_runs(self) -> None: + """CI groups include workflow, repository, and PR identity.""" + + expected_groups = { + "ci.yml": ( + "tepp-rust-foundation-${{ github.repository }}-" + "${{ github.event.pull_request.number || github.run_id }}" + ), + "docs-quality.yml": ( + "docs-quality-${{ github.repository }}-" + "${{ github.event.pull_request.number || github.run_id }}" + ), + } + for workflow_name, expected_group in expected_groups.items(): + workflow = (WORKFLOW_DIRECTORY / workflow_name).read_text(encoding="utf-8") + self.assertIn(f"group: {expected_group}", workflow) + self.assertIn( + "cancel-in-progress: ${{ github.event_name == 'pull_request' }}", + workflow, + ) + + def test_central_scheduler_replaces_local_hourly_caller(self) -> None: + """A local timer must not duplicate central PR scheduling.""" + + self.assertFalse((WORKFLOW_DIRECTORY / "hourly-pr-maintenance.yml").exists()) + + def test_documentation_workflow_does_not_duplicate_crate_ci(self) -> None: + """Crate changes already run documentation contracts in Rust CI.""" + + workflow = (WORKFLOW_DIRECTORY / "docs-quality.yml").read_text( + encoding="utf-8" + ) + self.assertNotIn(' - "crates/**"', workflow) + + def test_rust_ci_reuses_one_runner_for_both_coverage_reports(self) -> None: + """Coverage keeps both gates while avoiding a second runner boot.""" + + workflow = (WORKFLOW_DIRECTORY / "ci.yml").read_text(encoding="utf-8") + self.assertIn(" production-coverage:\n", workflow) + self.assertNotIn(" line-coverage:\n", workflow) + self.assertNotIn(" branch-coverage:\n", workflow) + self.assertIn("id: line-report", workflow) + self.assertIn("id: branch-report", workflow) + self.assertIn("if: ${{ !cancelled() }}", workflow) + self.assertNotIn( + "cargo check --workspace --all-targets --all-features", workflow + ) + self.assertIn( + "cargo clippy --workspace --all-targets --all-features -- -D warnings", + workflow, + ) + + +if __name__ == "__main__": # pragma: no cover + unittest.main() From bcde643bd021ba5f36ce9eba8ee904cccc1c754a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 09:37:47 +0900 Subject: [PATCH 194/296] docs(operations): absorb main Actions fleet protected-path set --- docs/operations/ACTIONS_WORKFLOW_FLEET.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/operations/ACTIONS_WORKFLOW_FLEET.md b/docs/operations/ACTIONS_WORKFLOW_FLEET.md index 358f63dee..27d4c3557 100644 --- a/docs/operations/ACTIONS_WORKFLOW_FLEET.md +++ b/docs/operations/ACTIONS_WORKFLOW_FLEET.md @@ -25,7 +25,6 @@ Protected production paths are never disabled: - `.github/workflows/ci.yml` - `.github/workflows/docs-quality.yml` - `.github/workflows/hourly-nim-product-development.yml` -- `.github/workflows/hourly-pr-maintenance.yml` ## Credentials @@ -68,11 +67,12 @@ Retain the JSON inventory (workflow ID, path, state, classification, default-branch SHA, timestamp, and pagination receipts). Re-run the read-only audit after any workflow deletion. Coordinate with `ContextualWisdomLab/.github#945` and `ContextualWisdomLab/appguardrail#929`. -Do not recreate deleted bootstrap or repair YAML. +Do not recreate deleted bootstrap, repair, or repository-local PR-maintenance +YAML. PR review and merge scheduling belongs to the central required workflow. ## 2026-08-13 live remediation Inventory bound to `main` SHA `3810bb73e3606431e1e19497b9746a8335e5d379` reported 15 identities and 10 orphans. After `--apply`, those 10 records -were `disabled_manually` and `orphan_count` was 0. The four protected +were `disabled_manually` and `orphan_count` was 0. The then-current protected repository workflows and GitHub-owned CodeQL remained `active`. From 1caf1e4e9aa1dce0a45c3840f0a35a6eecb841f2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 09:39:06 +0900 Subject: [PATCH 195/296] ci(actions): absorb main CI concurrency and consolidated coverage runner --- .github/workflows/ci.yml | 48 +++++++++++----------------------------- 1 file changed, 13 insertions(+), 35 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f8560363b..18bc8defa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,8 +35,8 @@ permissions: contents: read concurrency: - group: tepp-rust-foundation-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true + group: tepp-rust-foundation-${{ github.repository }}-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} env: CARGO_TERM_COLOR: always @@ -96,20 +96,17 @@ jobs: ~/.cargo/bin/cargo-nextest ~/.cargo/bin/cargo-deny key: ${{ runner.os }}-${{ runner.arch }}-tepp-rust-tools-nextest-${{ env.CARGO_NEXTEST_VERSION }}-deny-${{ env.CARGO_DENY_VERSION }} - - name: Install cargo-nextest + - name: Install pinned Rust quality tools if: steps.rust-tools-cache.outputs.cache-hit != 'true' - run: cargo install cargo-nextest --locked --version "$CARGO_NEXTEST_VERSION" - - name: Install cargo-deny - if: steps.rust-tools-cache.outputs.cache-hit != 'true' - run: cargo install cargo-deny --locked --version "$CARGO_DENY_VERSION" + run: | + cargo install cargo-nextest --locked --version "$CARGO_NEXTEST_VERSION" + cargo install cargo-deny --locked --version "$CARGO_DENY_VERSION" - name: Verify pinned Rust quality tool versions run: | cargo nextest --version | grep -F "$CARGO_NEXTEST_VERSION" cargo deny --version | grep -F "$CARGO_DENY_VERSION" - name: Check formatting run: cargo fmt --all -- --check - - name: Compile all targets - run: cargo check --workspace --all-targets --all-features - name: Run Clippy run: cargo clippy --workspace --all-targets --all-features -- -D warnings - name: Run test suite without retries @@ -121,10 +118,10 @@ jobs: - name: Enforce dependency, license, advisory, and source policy run: cargo deny check - line-coverage: - name: Production line coverage + production-coverage: + name: Production line and branch coverage runs-on: ubuntu-latest - timeout-minutes: 25 + timeout-minutes: 45 steps: - name: Checkout exact head uses: actions/checkout@631c942040754b6e095e929c1677c07e10ed4f87 @@ -132,6 +129,8 @@ jobs: persist-credentials: false - name: Install pinned Rust toolchain with LLVM tools run: rustup toolchain install 1.98.0 --profile minimal --component llvm-tools-preview + - name: Install pinned nightly with LLVM tools + run: rustup toolchain install nightly-2026-08-21 --profile minimal --component llvm-tools-preview - name: Restore pinned cargo-llvm-cov id: llvm-cov-cache uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 @@ -216,33 +215,12 @@ jobs: coverage.lcov if-no-files-found: error retention-days: 1 - - branch-coverage: - name: Production branch coverage on pinned nightly - runs-on: ubuntu-latest - timeout-minutes: 25 - steps: - - name: Checkout exact head - uses: actions/checkout@631c942040754b6e095e929c1677c07e10ed4f87 - with: - persist-credentials: false - - name: Install pinned nightly with LLVM tools - run: rustup toolchain install nightly-2026-08-21 --profile minimal --component llvm-tools-preview - - name: Restore pinned cargo-llvm-cov - id: llvm-cov-cache - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 - with: - path: ~/.cargo/bin/cargo-llvm-cov - key: ${{ runner.os }}-${{ runner.arch }}-tepp-cargo-llvm-cov-${{ env.CARGO_LLVM_COV_VERSION }} - - name: Install cargo-llvm-cov - if: steps.llvm-cov-cache.outputs.cache-hit != 'true' - run: cargo install cargo-llvm-cov --locked --version "$CARGO_LLVM_COV_VERSION" - - name: Verify pinned cargo-llvm-cov version - run: cargo llvm-cov --version | grep -F "$CARGO_LLVM_COV_VERSION" - name: Generate exact branch coverage id: branch-report + if: ${{ !cancelled() }} run: cargo +nightly-2026-08-21 llvm-cov --branch --workspace --all-features --json --output-path coverage-branches.json --ignore-filename-regex 'sqlx_live\.rs|mlx_native_receipt' - name: Enforce complete branch coverage + if: ${{ !cancelled() && steps.branch-report.outcome == 'success' }} run: python3 scripts/check_coverage.py coverage-branches.json --kind branches - name: Show exact missing branch diagnostics if: ${{ failure() && steps.branch-report.outcome == 'success' }} From 415a7cadad90ff59e6cd4799f8fe59ece5024789 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 09:39:08 +0900 Subject: [PATCH 196/296] ci(actions): absorb main hourly NIM product-development workflow --- .github/workflows/hourly-nim-product-development.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/hourly-nim-product-development.yml b/.github/workflows/hourly-nim-product-development.yml index 2561ecb26..0d14ef862 100644 --- a/.github/workflows/hourly-nim-product-development.yml +++ b/.github/workflows/hourly-nim-product-development.yml @@ -1,3 +1,4 @@ +# cwl-org-commercial-entrypoint: v1 name: Hourly Contextual Orchestrator Product Development on: @@ -8,9 +9,6 @@ on: required: false default: false type: boolean - schedule: - - cron: "47 * * * *" - concurrency: group: hourly-nim-product-development-${{ github.repository }} cancel-in-progress: false From 85c1ba42547e9df0bd7e73f37395e242ca879e91 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 09:39:09 +0900 Subject: [PATCH 197/296] ci(actions): absorb main Actions workflow fleet auditor --- scripts/actions_workflow_fleet.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/actions_workflow_fleet.py b/scripts/actions_workflow_fleet.py index fc2782d23..e5dfb8c97 100644 --- a/scripts/actions_workflow_fleet.py +++ b/scripts/actions_workflow_fleet.py @@ -25,7 +25,6 @@ ".github/workflows/ci.yml", ".github/workflows/docs-quality.yml", ".github/workflows/hourly-nim-product-development.yml", - ".github/workflows/hourly-pr-maintenance.yml", } ) _DISABLED_WORKFLOW_STATES = frozenset( From 6f6999c3a731b678e6077d3f7e89c4cf450a346d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 09:39:10 +0900 Subject: [PATCH 198/296] test(actions): absorb main Actions workflow fleet tests --- tests/quality/test_actions_workflow_fleet.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/quality/test_actions_workflow_fleet.py b/tests/quality/test_actions_workflow_fleet.py index 9255fe18e..14c055d46 100644 --- a/tests/quality/test_actions_workflow_fleet.py +++ b/tests/quality/test_actions_workflow_fleet.py @@ -1095,8 +1095,8 @@ def _happy_responses(self) -> dict[tuple[str, str], fleet.HttpResponse]: ), _workflow( 3, - ".github/workflows/hourly-pr-maintenance.yml", - name="Hourly PR Maintenance", + ".github/workflows/hourly-nim-product-development.yml", + name="Hourly Contextual Orchestrator Product Development", ), _workflow( 10, @@ -1131,10 +1131,6 @@ def _happy_responses(self) -> dict[tuple[str, str], fleet.HttpResponse]: "tree": [ {"path": ".github/workflows/ci.yml", "type": "blob"}, {"path": ".github/workflows/docs-quality.yml", "type": "blob"}, - { - "path": ".github/workflows/hourly-pr-maintenance.yml", - "type": "blob", - }, { "path": ".github/workflows/hourly-nim-product-development.yml", "type": "blob", From 910f37d8c3e3fbe93329effc133873c197e8784a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 09:39:12 +0900 Subject: [PATCH 199/296] test(actions): absorb main prepare_agent_pr_message tests --- tests/quality/test_prepare_agent_pr_message.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/quality/test_prepare_agent_pr_message.py b/tests/quality/test_prepare_agent_pr_message.py index dd7aeaa46..cccc0cc37 100644 --- a/tests/quality/test_prepare_agent_pr_message.py +++ b/tests/quality/test_prepare_agent_pr_message.py @@ -4,6 +4,7 @@ import os import stat +import sys import tempfile import unittest import unittest.mock @@ -287,8 +288,15 @@ def test_module_entrypoint_invokes_main(self) -> None: str(body), ], ): - with self.assertRaises(SystemExit) as raised: - runpy.run_module("scripts.prepare_agent_pr_message", run_name="__main__") + loaded_module = sys.modules.pop("scripts.prepare_agent_pr_message", None) + try: + with self.assertRaises(SystemExit) as raised: + runpy.run_module( + "scripts.prepare_agent_pr_message", run_name="__main__" + ) + finally: + if loaded_module is not None: + sys.modules["scripts.prepare_agent_pr_message"] = loaded_module self.assertEqual(raised.exception.code, 0) def test_read_without_nofollow_flag_when_unavailable(self) -> None: From 8ab3b05b2a7e16e5e30f8f4ab04d610c775965c6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 09:39:14 +0900 Subject: [PATCH 200/296] docs(changelog): keep longitudinal Unreleased bullets plus main workflow-centralization note --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f761d9629..34cbc4c66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,8 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang ## [Unreleased] +- Removed the repository-local hourly PR-maintenance caller now covered by the central required scheduler, retired stale workflow registrations, narrowed documentation triggers, keyed PR concurrency by fixed workflow name, repository, and pull-request number without cancelling non-PR runs, and combined line/branch coverage on one sequential runner while preserving both 100% gates and diagnostics. + - `longitudinal_core` canonicalizes exact-zero Hamaker Eq. 1a occasion-mean residuals to public `+0.0`. IEEE-754 signed zero after subtracting a canonical `+0.0` occasion mean is numerically equal to `+0.0` but bit-distinct; a zero occasion-mean deviation has no directional meaning, so exposing both encodings gave one scientific estimand two public identities. `center_occasion_mean_event_lags` now matches the CWC and `decompose_within_between` public-zero contracts. Already-centered caller-constructed pairs are unchanged. These residuals still retain between-person differences and are not within-person, RI-CLPM, or DSEM lags (Hamaker, Kuiper, & Grasman, 2015, Eq. 1a). Meredith (1993) remains unread. Mislevy (1991) remains unread (ERIC ED268155 is the 1985/86 report, not the 1991 *Psychometrika* article). - `longitudinal_core` canonicalizes exact-zero CWC lag residuals to public `+0.0`. IEEE-754 signed zero after person-mean subtraction is numerically equal to `+0.0` but bit-distinct; a zero within-person deviation has no directional meaning, so exposing both encodings gave one scientific estimand two public identities. `center_within_unit_event_lags` now matches the `decompose_within_between` public-zero contract. Already-centered caller-constructed pairs are unchanged. The crate also exposes the already-implemented unstandardised stationary within-person variance `p = -q / (2 a)` (Driver et al., 2017, Eq. 4 as `Δt → ∞`; p. 16 `asymDIFFUSION`) so standardised maps can keep cancelling `p` without leaving a dead production path. This is not DSEM, not raw-process drift, and not `asymDIFFUSIONstd`. Meredith (1993) remains unread. Mislevy (1991) remains unread (ERIC ED268155 is the 1985/86 report, not the 1991 *Psychometrika* article). @@ -454,4 +456,4 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang - Added known digest vectors, mutation detection, hostile multibyte Unicode, exact-coordinate, page-boundary, stable-error, and invalid-input regression tests for the first evidence slice. - Added strict wire round trips, unknown-field and version rejection, digest reconstruction, configured-limit, hostile JSON, and generated multilingual span tests. -The repository has not yet cut a stable implementation release, so no compare reference is published for `[Unreleased]` yet. +The repository has not yet cut a stable implementation release, so no compare reference is published for `[Unreleased]` yet. \ No newline at end of file From 042f9c9d71373e4513233d5ea589d6ea94eea8f2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 09:39:15 +0900 Subject: [PATCH 201/296] ci(actions): remove repo-local hourly PR-maintenance caller superseded by central scheduler --- .github/workflows/hourly-pr-maintenance.yml | 24 --------------------- 1 file changed, 24 deletions(-) delete mode 100644 .github/workflows/hourly-pr-maintenance.yml diff --git a/.github/workflows/hourly-pr-maintenance.yml b/.github/workflows/hourly-pr-maintenance.yml deleted file mode 100644 index aad34e766..000000000 --- a/.github/workflows/hourly-pr-maintenance.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Hourly PR Maintenance - -on: - schedule: - - cron: "11 * * * *" - workflow_dispatch: - -permissions: - contents: read - -concurrency: - group: tepp-hourly-pr-maintenance - cancel-in-progress: false - -jobs: - maintain: - name: Review, repair, revalidate, and merge - permissions: - actions: write - checks: read - contents: write - id-token: write - pull-requests: write - uses: ContextualWisdomLab/.github/.github/workflows/pr-review-merge-scheduler.yml@731af58e954901c4f1cc853231c592abb1eaf617 From ddd674f9c3ee312625c2f767b2b2a6fe03965d59 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 09:39:17 +0900 Subject: [PATCH 202/296] test(actions): remove hourly maintenance caller contracts superseded by queue hygiene --- .../quality/test_hourly_maintenance_caller.py | 49 ------------------- 1 file changed, 49 deletions(-) delete mode 100644 tests/quality/test_hourly_maintenance_caller.py diff --git a/tests/quality/test_hourly_maintenance_caller.py b/tests/quality/test_hourly_maintenance_caller.py deleted file mode 100644 index 80f4f1c2a..000000000 --- a/tests/quality/test_hourly_maintenance_caller.py +++ /dev/null @@ -1,49 +0,0 @@ -"""Contracts for the repository-local hourly maintenance caller.""" - -from __future__ import annotations - -import unittest -from pathlib import Path - - -REPOSITORY_ROOT = Path(__file__).resolve().parents[2] -CALLER_WORKFLOW = REPOSITORY_ROOT / ".github" / "workflows" / "hourly-pr-maintenance.yml" -CENTRAL_SCHEDULER_REVISION = "731af58e954901c4f1cc853231c592abb1eaf617" - - -class HourlyMaintenanceCallerContractTests(unittest.TestCase): - """Keep the caller bounded, immutable, and credential-separated.""" - - def test_caller_runs_hourly_and_pins_the_verified_central_scheduler(self) -> None: - """The repository delegates policy instead of copying mutable scheduler code.""" - - workflow = CALLER_WORKFLOW.read_text(encoding="utf-8") - - self.assertIn('cron: "11 * * * *"', workflow) - self.assertIn( - "uses: ContextualWisdomLab/.github/.github/workflows/" - f"pr-review-merge-scheduler.yml@{CENTRAL_SCHEDULER_REVISION}", - workflow, - ) - self.assertNotIn("secrets: inherit", workflow) - self.assertNotIn("COPILOT_GITHUB_TOKEN", workflow) - self.assertNotIn("NVIDIA_NIM_API_KEY", workflow) - - def test_workflow_default_is_read_only_and_only_job_permissions_are_elevated(self) -> None: - """Review and merge authority stays scoped to the reusable-workflow job.""" - - workflow = CALLER_WORKFLOW.read_text(encoding="utf-8") - default_permissions = workflow.split("concurrency:", maxsplit=1)[0] - job_permissions = workflow.split("jobs:", maxsplit=1)[1] - - self.assertIn("permissions:\n contents: read", default_permissions) - self.assertIn("actions: write", job_permissions) - self.assertIn("checks: read", job_permissions) - self.assertIn("contents: write", job_permissions) - self.assertIn("id-token: write", job_permissions) - self.assertIn("pull-requests: write", job_permissions) - self.assertIn("cancel-in-progress: false", workflow) - - -if __name__ == "__main__": # pragma: no cover - unittest.main() From 28b47e121f9da6a381c254671d41088dbb875d81 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 09:40:47 +0900 Subject: [PATCH 203/296] docs(changelog): temporarily align with main to clear merge conflict --- CHANGELOG.md | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34cbc4c66..8b495291a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,14 +40,6 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang - Removed the repository-local hourly PR-maintenance caller now covered by the central required scheduler, retired stale workflow registrations, narrowed documentation triggers, keyed PR concurrency by fixed workflow name, repository, and pull-request number without cancelling non-PR runs, and combined line/branch coverage on one sequential runner while preserving both 100% gates and diagnostics. -- `longitudinal_core` canonicalizes exact-zero Hamaker Eq. 1a occasion-mean residuals to public `+0.0`. IEEE-754 signed zero after subtracting a canonical `+0.0` occasion mean is numerically equal to `+0.0` but bit-distinct; a zero occasion-mean deviation has no directional meaning, so exposing both encodings gave one scientific estimand two public identities. `center_occasion_mean_event_lags` now matches the CWC and `decompose_within_between` public-zero contracts. Already-centered caller-constructed pairs are unchanged. These residuals still retain between-person differences and are not within-person, RI-CLPM, or DSEM lags (Hamaker, Kuiper, & Grasman, 2015, Eq. 1a). Meredith (1993) remains unread. Mislevy (1991) remains unread (ERIC ED268155 is the 1985/86 report, not the 1991 *Psychometrika* article). - -- `longitudinal_core` canonicalizes exact-zero CWC lag residuals to public `+0.0`. IEEE-754 signed zero after person-mean subtraction is numerically equal to `+0.0` but bit-distinct; a zero within-person deviation has no directional meaning, so exposing both encodings gave one scientific estimand two public identities. `center_within_unit_event_lags` now matches the `decompose_within_between` public-zero contract. Already-centered caller-constructed pairs are unchanged. The crate also exposes the already-implemented unstandardised stationary within-person variance `p = -q / (2 a)` (Driver et al., 2017, Eq. 4 as `Δt → ∞`; p. 16 `asymDIFFUSION`) so standardised maps can keep cancelling `p` without leaving a dead production path. This is not DSEM, not raw-process drift, and not `asymDIFFUSIONstd`. Meredith (1993) remains unread. Mislevy (1991) remains unread (ERIC ED268155 is the 1985/86 report, not the 1991 *Psychometrika* article). - -- `longitudinal_core` folds Draft #327 unique CWC-then-irregular residual log-rate onto the Longitudinal Modeling landing vehicle. Consecutive unit-mean-centered pairs keep typed `EventTimeInterval`. The pairwise mean of the Driver, Oud, and Voelkle (2017, Eq. 3; JSS PDF opened 2026-09-02) scalar inverse `a = ln(|later| / |earlier|) / Δt` is taken on nonzero same-sign residuals, with a stable logarithm when that ratio overflows or underflows, and an incremental mean so two finite rates whose raw sum overflows stay representable. Already-centered pairs recover `ln(0.5)`; CWC of a raw AR path does not. Curran and Bauer (2011, pp. 583–619; PMC3059070 XML opened 2026-09-02; Eq. 36) show that person-mean centering of a time-related TVC is biased for the within-person effect; `refuse_cwc_residual_log_rate_as_raw_process_drift` always fails closed. T=2 CWC is `r, −r` (empty admissible). This is not Newton LS, not DSEM, and not a second invariance crate. Voelkle et al. (2012, Eq. 7) remains previously opened lineage; the ZORA PDF was not re-opened this cycle. Meredith (1993) remains unread. Mislevy (1991) remains unread. - -- `longitudinal_core` adds event-time lagged-correlation standardization that requires lagged covariance plus both occasion-specific marginal variances, enforces the exact binary64 covariance bound, and uses bounded division order to avoid representable overflow/underflow. The invalid one-sided covariance/earlier-variance `expected_autocorrelation` API is removed; this arithmetic boundary does not claim a DSEM/ctsem/state-transition estimator. - - `event_core` adds bounded Allen interval-consistency classification, atomic path-consistency closure, contradiction/resource refusals, and an explicit dependency-error fallback without claiming unrestricted global satisfiability. - `psychometric_core` recovers the Driver, Oud, and Voelkle (2017, Table 2, p. 12 `MANIFESTTRAITVAR`; §7.1, p. 19; p. 16 `MANIFESTTRAITVARstd`; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-27T14:20Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104) scalar standardised manifest-trait variance on current main after `0ce16e8` dropped the pre-consolidation code while research notes already named the map (register items 83–84). Table 2 names `MANIFESTTRAITVAR` `Ψ_τ` the additional time-invariant variance-covariance on the measurement level and sets it `NULL` when there is no manifest trait. Equation 5 writes `Γ ~ N(τ, Ψ)` and names that covariance the manifest traits. Section 7.1 names manifest traits stable individual differences in indicator levels, distinct from process-level `TRAITVAR` `φ_ξ`. Page 16 prints standardised matrices with the suffix `std` when appropriate. The printed example on p. 16 is `discreteDRIFTstd`, not `MANIFESTTRAITVARstd`. Footnote 4 standardises using only the relevant variance, not the total. The relevant variance for that named indicator-level correlation is `MANIFESTTRAITVAR`, not process-level `TRAITVAR` and not residual `MANIFESTVAR` `θ`. The 2017-era source forms `MANIFESTTRAITVARstd` only when `MANIFESTTRAITVAR != 0`, as `solve(sqrt(diag(MANIFESTTRAITVAR) + ridging)) %&% MANIFESTTRAITVAR` when `verbose = TRUE`. OpenMx `%&%` is `t(A) %*% B %*% A`. Unlike `TRAITVARstd`, that formation adds `diag(c(ridging), n.manifest)`. The default `ridging = FALSE` adds 0, not `0.0001`; that ridge is a numerical hack and is not this exact map. The scalar correlation is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR`. Form strictly positive `ψ` first, then `1 / √ψ`, then `(1 / √ψ) ψ (1 / √ψ)`. Unstandardised `MANIFESTTRAITVAR` is defined for a zero trait; standardised `MANIFESTTRAITVAR` is not. Zero `MANIFESTTRAITVAR` skips forming `MANIFESTTRAITVARstd` in the 2017-era source and fails closed here. Indicator-level trait variance is an event-time structural quantity, so a non-event clock fails closed. `MANIFESTTRAITVAR` does not require stable `a < 0`. Distinct positive `ψ` recover the same 1. `trait / trait = 1` is `TRAITVARstd` and recovers the same number and remains a distinct named quantity. `θ` is `MANIFESTVAR` and is measurement error, not this correlation. Meredith (1993) remains unread (web search 2026-08-27T14:20Z: Springer/Cambridge Core paywalled; Unpaywall historically `is_oa: false`; Springer `content/pdf` is an HTML stub). Mislevy (1991, *Psychometrika, 56*, 177–196) remains unread on the same terms (DOI `10.1007/bf02294457`). Still not a Kalman filter, not a matrix `expm`, not ESEM estimation, not DSEM, and not ctsem estimation. @@ -456,4 +448,4 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang - Added known digest vectors, mutation detection, hostile multibyte Unicode, exact-coordinate, page-boundary, stable-error, and invalid-input regression tests for the first evidence slice. - Added strict wire round trips, unknown-field and version rejection, digest reconstruction, configured-limit, hostile JSON, and generated multilingual span tests. -The repository has not yet cut a stable implementation release, so no compare reference is published for `[Unreleased]` yet. \ No newline at end of file +The repository has not yet cut a stable implementation release, so no compare reference is published for `[Unreleased]` yet. From dc5823b0845dbc8c782977b536d9480aead82b94 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 09:40:49 +0900 Subject: [PATCH 204/296] docs(changelog): restore longitudinal Unreleased bullets atop main workflow-centralization note --- CHANGELOG.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b495291a..34cbc4c66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,14 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang - Removed the repository-local hourly PR-maintenance caller now covered by the central required scheduler, retired stale workflow registrations, narrowed documentation triggers, keyed PR concurrency by fixed workflow name, repository, and pull-request number without cancelling non-PR runs, and combined line/branch coverage on one sequential runner while preserving both 100% gates and diagnostics. +- `longitudinal_core` canonicalizes exact-zero Hamaker Eq. 1a occasion-mean residuals to public `+0.0`. IEEE-754 signed zero after subtracting a canonical `+0.0` occasion mean is numerically equal to `+0.0` but bit-distinct; a zero occasion-mean deviation has no directional meaning, so exposing both encodings gave one scientific estimand two public identities. `center_occasion_mean_event_lags` now matches the CWC and `decompose_within_between` public-zero contracts. Already-centered caller-constructed pairs are unchanged. These residuals still retain between-person differences and are not within-person, RI-CLPM, or DSEM lags (Hamaker, Kuiper, & Grasman, 2015, Eq. 1a). Meredith (1993) remains unread. Mislevy (1991) remains unread (ERIC ED268155 is the 1985/86 report, not the 1991 *Psychometrika* article). + +- `longitudinal_core` canonicalizes exact-zero CWC lag residuals to public `+0.0`. IEEE-754 signed zero after person-mean subtraction is numerically equal to `+0.0` but bit-distinct; a zero within-person deviation has no directional meaning, so exposing both encodings gave one scientific estimand two public identities. `center_within_unit_event_lags` now matches the `decompose_within_between` public-zero contract. Already-centered caller-constructed pairs are unchanged. The crate also exposes the already-implemented unstandardised stationary within-person variance `p = -q / (2 a)` (Driver et al., 2017, Eq. 4 as `Δt → ∞`; p. 16 `asymDIFFUSION`) so standardised maps can keep cancelling `p` without leaving a dead production path. This is not DSEM, not raw-process drift, and not `asymDIFFUSIONstd`. Meredith (1993) remains unread. Mislevy (1991) remains unread (ERIC ED268155 is the 1985/86 report, not the 1991 *Psychometrika* article). + +- `longitudinal_core` folds Draft #327 unique CWC-then-irregular residual log-rate onto the Longitudinal Modeling landing vehicle. Consecutive unit-mean-centered pairs keep typed `EventTimeInterval`. The pairwise mean of the Driver, Oud, and Voelkle (2017, Eq. 3; JSS PDF opened 2026-09-02) scalar inverse `a = ln(|later| / |earlier|) / Δt` is taken on nonzero same-sign residuals, with a stable logarithm when that ratio overflows or underflows, and an incremental mean so two finite rates whose raw sum overflows stay representable. Already-centered pairs recover `ln(0.5)`; CWC of a raw AR path does not. Curran and Bauer (2011, pp. 583–619; PMC3059070 XML opened 2026-09-02; Eq. 36) show that person-mean centering of a time-related TVC is biased for the within-person effect; `refuse_cwc_residual_log_rate_as_raw_process_drift` always fails closed. T=2 CWC is `r, −r` (empty admissible). This is not Newton LS, not DSEM, and not a second invariance crate. Voelkle et al. (2012, Eq. 7) remains previously opened lineage; the ZORA PDF was not re-opened this cycle. Meredith (1993) remains unread. Mislevy (1991) remains unread. + +- `longitudinal_core` adds event-time lagged-correlation standardization that requires lagged covariance plus both occasion-specific marginal variances, enforces the exact binary64 covariance bound, and uses bounded division order to avoid representable overflow/underflow. The invalid one-sided covariance/earlier-variance `expected_autocorrelation` API is removed; this arithmetic boundary does not claim a DSEM/ctsem/state-transition estimator. + - `event_core` adds bounded Allen interval-consistency classification, atomic path-consistency closure, contradiction/resource refusals, and an explicit dependency-error fallback without claiming unrestricted global satisfiability. - `psychometric_core` recovers the Driver, Oud, and Voelkle (2017, Table 2, p. 12 `MANIFESTTRAITVAR`; §7.1, p. 19; p. 16 `MANIFESTTRAITVARstd`; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-27T14:20Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104) scalar standardised manifest-trait variance on current main after `0ce16e8` dropped the pre-consolidation code while research notes already named the map (register items 83–84). Table 2 names `MANIFESTTRAITVAR` `Ψ_τ` the additional time-invariant variance-covariance on the measurement level and sets it `NULL` when there is no manifest trait. Equation 5 writes `Γ ~ N(τ, Ψ)` and names that covariance the manifest traits. Section 7.1 names manifest traits stable individual differences in indicator levels, distinct from process-level `TRAITVAR` `φ_ξ`. Page 16 prints standardised matrices with the suffix `std` when appropriate. The printed example on p. 16 is `discreteDRIFTstd`, not `MANIFESTTRAITVARstd`. Footnote 4 standardises using only the relevant variance, not the total. The relevant variance for that named indicator-level correlation is `MANIFESTTRAITVAR`, not process-level `TRAITVAR` and not residual `MANIFESTVAR` `θ`. The 2017-era source forms `MANIFESTTRAITVARstd` only when `MANIFESTTRAITVAR != 0`, as `solve(sqrt(diag(MANIFESTTRAITVAR) + ridging)) %&% MANIFESTTRAITVAR` when `verbose = TRUE`. OpenMx `%&%` is `t(A) %*% B %*% A`. Unlike `TRAITVARstd`, that formation adds `diag(c(ridging), n.manifest)`. The default `ridging = FALSE` adds 0, not `0.0001`; that ridge is a numerical hack and is not this exact map. The scalar correlation is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR`. Form strictly positive `ψ` first, then `1 / √ψ`, then `(1 / √ψ) ψ (1 / √ψ)`. Unstandardised `MANIFESTTRAITVAR` is defined for a zero trait; standardised `MANIFESTTRAITVAR` is not. Zero `MANIFESTTRAITVAR` skips forming `MANIFESTTRAITVARstd` in the 2017-era source and fails closed here. Indicator-level trait variance is an event-time structural quantity, so a non-event clock fails closed. `MANIFESTTRAITVAR` does not require stable `a < 0`. Distinct positive `ψ` recover the same 1. `trait / trait = 1` is `TRAITVARstd` and recovers the same number and remains a distinct named quantity. `θ` is `MANIFESTVAR` and is measurement error, not this correlation. Meredith (1993) remains unread (web search 2026-08-27T14:20Z: Springer/Cambridge Core paywalled; Unpaywall historically `is_oa: false`; Springer `content/pdf` is an HTML stub). Mislevy (1991, *Psychometrika, 56*, 177–196) remains unread on the same terms (DOI `10.1007/bf02294457`). Still not a Kalman filter, not a matrix `expm`, not ESEM estimation, not DSEM, and not ctsem estimation. @@ -448,4 +456,4 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang - Added known digest vectors, mutation detection, hostile multibyte Unicode, exact-coordinate, page-boundary, stable-error, and invalid-input regression tests for the first evidence slice. - Added strict wire round trips, unknown-field and version rejection, digest reconstruction, configured-limit, hostile JSON, and generated multilingual span tests. -The repository has not yet cut a stable implementation release, so no compare reference is published for `[Unreleased]` yet. +The repository has not yet cut a stable implementation release, so no compare reference is published for `[Unreleased]` yet. \ No newline at end of file From f9476a8e2d4a4ddba437549c39ac1f50ff4357b8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 00:45:06 +0000 Subject: [PATCH 205/296] fix(longitudinal_core): rustfmt sources and contracts for discrete-drift CI --- crates/longitudinal_core/src/association.rs | 23 ++++--------------- crates/longitudinal_core/src/component.rs | 10 ++------ crates/longitudinal_core/src/diffusion.rs | 9 +++----- .../longitudinal_core/src/discrete_drift.rs | 4 +--- .../src/irregular_residual.rs | 7 ++---- .../src/stable_irregular_rate.rs | 12 ++++++---- .../src/temporal_association.rs | 7 +----- ...nent_rmse_permutation_identity_contract.rs | 4 +--- ...mponent_rmse_truth_permutation_contract.rs | 4 +--- .../tests/cwc_irregular_residual_contract.rs | 13 +++++++---- .../tests/mean_rounding_contract.rs | 5 +++- .../tests/standardised_diffusion_contract.rs | 8 ++++--- .../standardised_discrete_drift_contract.rs | 9 +++----- 13 files changed, 45 insertions(+), 70 deletions(-) diff --git a/crates/longitudinal_core/src/association.rs b/crates/longitudinal_core/src/association.rs index 692ebd938..f8f9cd55c 100644 --- a/crates/longitudinal_core/src/association.rs +++ b/crates/longitudinal_core/src/association.rs @@ -64,13 +64,10 @@ fn covariance_binary_bound_relation( positive_binary_components(covariance_magnitude); let (earlier_significand, earlier_exponent) = positive_binary_components(earlier_total_variance); - let (later_significand, later_exponent) = - positive_binary_components(later_total_variance); + let (later_significand, later_exponent) = positive_binary_components(later_total_variance); - let covariance_square = - u128::from(covariance_significand) * u128::from(covariance_significand); - let variance_product = - u128::from(earlier_significand) * u128::from(later_significand); + let covariance_square = u128::from(covariance_significand) * u128::from(covariance_significand); + let variance_product = u128::from(earlier_significand) * u128::from(later_significand); let covariance_square_exponent = covariance_exponent * 2; let variance_product_exponent = earlier_exponent + later_exponent; let within = scaled_integer_leq( @@ -240,21 +237,11 @@ mod tests { #[test] fn exact_binary_bound_accepts_extreme_and_subnormal_boundaries() { assert_eq!( - recover_event_time_lagged_correlation( - f64::MAX, - f64::MAX, - f64::MAX, - event_time(1.0), - ), + recover_event_time_lagged_correlation(f64::MAX, f64::MAX, f64::MAX, event_time(1.0),), Ok(1.0) ); assert_eq!( - recover_event_time_lagged_correlation( - -f64::MAX, - f64::MAX, - f64::MAX, - event_time(1.0), - ), + recover_event_time_lagged_correlation(-f64::MAX, f64::MAX, f64::MAX, event_time(1.0),), Ok(-1.0) ); let minimum_subnormal = f64::from_bits(1); diff --git a/crates/longitudinal_core/src/component.rs b/crates/longitudinal_core/src/component.rs index 59a6a7ada..90a52731a 100644 --- a/crates/longitudinal_core/src/component.rs +++ b/crates/longitudinal_core/src/component.rs @@ -86,8 +86,7 @@ fn add_scaled_square( *scaled_sum_squares = residual_ratio * residual_ratio; } else if residual_scale > *scale { let ratio = *scale / residual_scale; - *scaled_sum_squares = - *scaled_sum_squares * ratio * ratio + residual_ratio * residual_ratio; + *scaled_sum_squares = *scaled_sum_squares * ratio * ratio + residual_ratio * residual_ratio; *scale = residual_scale; } else { let ratio = residual_scale / *scale; @@ -162,12 +161,7 @@ pub fn component_root_mean_square_error( let residual = decided_row.value() - truth_row.value(); if residual.is_finite() { - add_scaled_square( - &mut scale, - &mut scaled_sum_squares, - residual.abs(), - 1.0, - ); + add_scaled_square(&mut scale, &mut scaled_sum_squares, residual.abs(), 1.0); } else { let endpoint_scale = truth_row.value().abs().max(decided_row.value().abs()); let normalized_residual = diff --git a/crates/longitudinal_core/src/diffusion.rs b/crates/longitudinal_core/src/diffusion.rs index 70987d8fc..b086f5da3 100644 --- a/crates/longitudinal_core/src/diffusion.rs +++ b/crates/longitudinal_core/src/diffusion.rs @@ -213,12 +213,9 @@ mod tests { assert_eq!(continuous_overflow, 0.5); let interval = EventTimeInterval::new(1.0).expect("unit event interval"); - let discrete_underflow = recover_event_time_standardised_discrete_diffusion( - minimum_subnormal, - -1.0, - interval, - ) - .expect("Q_delta/p cancels the unrepresentable stationary intermediate"); + let discrete_underflow = + recover_event_time_standardised_discrete_diffusion(minimum_subnormal, -1.0, interval) + .expect("Q_delta/p cancels the unrepresentable stationary intermediate"); let underflow_truth = -(-2.0_f64).exp_m1(); assert!((discrete_underflow - underflow_truth).abs() <= f64::EPSILON); diff --git a/crates/longitudinal_core/src/discrete_drift.rs b/crates/longitudinal_core/src/discrete_drift.rs index 1ce54ba40..99aa4fe10 100644 --- a/crates/longitudinal_core/src/discrete_drift.rs +++ b/crates/longitudinal_core/src/discrete_drift.rs @@ -1,8 +1,6 @@ //! Event-time standardisation for scalar continuous-time drift. -use crate::{ - EventTimeInterval, LongitudinalError, stationary::validate_stationary_process_inputs, -}; +use crate::{EventTimeInterval, LongitudinalError, stationary::validate_stationary_process_inputs}; /// Recover the scalar p. 16 `discreteDRIFTstd` on event time. /// diff --git a/crates/longitudinal_core/src/irregular_residual.rs b/crates/longitudinal_core/src/irregular_residual.rs index d811b8527..cdd910bae 100644 --- a/crates/longitudinal_core/src/irregular_residual.rs +++ b/crates/longitudinal_core/src/irregular_residual.rs @@ -441,12 +441,9 @@ pub(crate) fn driver_same_sign_log_rate( let log_ratio = if later_magnitude >= earlier_magnitude && later_magnitude <= earlier_magnitude * 2.0 { - let relative_loss_from_later = - (later_magnitude - earlier_magnitude) / later_magnitude; + let relative_loss_from_later = (later_magnitude - earlier_magnitude) / later_magnitude; -(-relative_loss_from_later).ln_1p() - } else if earlier_magnitude > later_magnitude - && earlier_magnitude <= later_magnitude * 2.0 - { + } else if earlier_magnitude > later_magnitude && earlier_magnitude <= later_magnitude * 2.0 { let relative_change_from_earlier = (later_magnitude - earlier_magnitude) / earlier_magnitude; relative_change_from_earlier.ln_1p() diff --git a/crates/longitudinal_core/src/stable_irregular_rate.rs b/crates/longitudinal_core/src/stable_irregular_rate.rs index 80944f92f..3350ae8f2 100644 --- a/crates/longitudinal_core/src/stable_irregular_rate.rs +++ b/crates/longitudinal_core/src/stable_irregular_rate.rs @@ -5,7 +5,11 @@ //! module is only the crate-public facade and must not carry a second //! floating-point implementation. -pub use crate::irregular_residual::{ - recover_centered_irregular_residual_log_rate, - recover_within_unit_irregular_residual_log_rate, -}; +/// Mean exact scalar log-rate on already-centered residuals with stable count weighting. +/// +/// Re-exports [`crate::irregular_residual::recover_centered_irregular_residual_log_rate`]. +pub use crate::irregular_residual::recover_centered_irregular_residual_log_rate; +/// Pairwise-mean exact log-rate after CWC with stable count weighting. +/// +/// Re-exports [`crate::irregular_residual::recover_within_unit_irregular_residual_log_rate`]. +pub use crate::irregular_residual::recover_within_unit_irregular_residual_log_rate; diff --git a/crates/longitudinal_core/src/temporal_association.rs b/crates/longitudinal_core/src/temporal_association.rs index 3a96f5fff..b6aa1eae5 100644 --- a/crates/longitudinal_core/src/temporal_association.rs +++ b/crates/longitudinal_core/src/temporal_association.rs @@ -65,12 +65,7 @@ mod tests { fn public_boundary_does_not_report_underflowed_nonzero_correlation_as_zero() { let interval = EventTimeInterval::new(1.0).expect("valid event time"); assert_eq!( - recover_event_time_lagged_correlation( - f64::from_bits(1), - f64::MAX, - f64::MAX, - interval, - ), + recover_event_time_lagged_correlation(f64::from_bits(1), f64::MAX, f64::MAX, interval,), Err(LongitudinalError::InvalidTemporalAssociationInput) ); } diff --git a/crates/longitudinal_core/tests/component_rmse_permutation_identity_contract.rs b/crates/longitudinal_core/tests/component_rmse_permutation_identity_contract.rs index ff3ec21e2..f278ee6f3 100644 --- a/crates/longitudinal_core/tests/component_rmse_permutation_identity_contract.rs +++ b/crates/longitudinal_core/tests/component_rmse_permutation_identity_contract.rs @@ -1,6 +1,4 @@ -use longitudinal_core::{ - ComponentLevel, ComponentValue, component_root_mean_square_error, -}; +use longitudinal_core::{ComponentLevel, ComponentValue, component_root_mean_square_error}; #[test] fn component_rmse_aligns_by_scientific_identity_not_row_position() { diff --git a/crates/longitudinal_core/tests/component_rmse_truth_permutation_contract.rs b/crates/longitudinal_core/tests/component_rmse_truth_permutation_contract.rs index e7385ad03..c28e3aa15 100644 --- a/crates/longitudinal_core/tests/component_rmse_truth_permutation_contract.rs +++ b/crates/longitudinal_core/tests/component_rmse_truth_permutation_contract.rs @@ -1,6 +1,4 @@ -use longitudinal_core::{ - ComponentLevel, ComponentValue, component_root_mean_square_error, -}; +use longitudinal_core::{ComponentLevel, ComponentValue, component_root_mean_square_error}; #[test] fn component_rmse_is_bit_identical_under_truth_row_permutation() { diff --git a/crates/longitudinal_core/tests/cwc_irregular_residual_contract.rs b/crates/longitudinal_core/tests/cwc_irregular_residual_contract.rs index 2bc7a59d2..7dc76e796 100644 --- a/crates/longitudinal_core/tests/cwc_irregular_residual_contract.rs +++ b/crates/longitudinal_core/tests/cwc_irregular_residual_contract.rs @@ -58,7 +58,10 @@ fn cwc_mean_is_deterministic_under_input_row_permutation() { let expected = center_within_unit_event_lags(&canonical).expect("canonical ordering"); let reordered = center_within_unit_event_lags(&shuffled).expect("shuffled ordering"); - assert_eq!(reordered, expected, "CWC output must not depend on input row order"); + assert_eq!( + reordered, expected, + "CWC output must not depend on input row order" + ); } #[test] @@ -98,9 +101,11 @@ fn cwc_mean_survives_overflowing_raw_sum_when_centered_values_are_finite() { let pairs = center_within_unit_event_lags(&rows) .expect("finite CWC residuals must not be rejected because a raw sum overflows"); assert_eq!(pairs.len(), 3); - assert!(pairs.iter().all(|pair| { - pair.earlier_residual().is_finite() && pair.later_residual().is_finite() - })); + assert!( + pairs.iter().all(|pair| { + pair.earlier_residual().is_finite() && pair.later_residual().is_finite() + }) + ); } #[test] diff --git a/crates/longitudinal_core/tests/mean_rounding_contract.rs b/crates/longitudinal_core/tests/mean_rounding_contract.rs index 2903f5fc1..1aa92518d 100644 --- a/crates/longitudinal_core/tests/mean_rounding_contract.rs +++ b/crates/longitudinal_core/tests/mean_rounding_contract.rs @@ -24,7 +24,10 @@ fn cwc_mean_rounds_halfway_subnormal_to_even_after_normalization() { (-f64::from_bits(7)).to_bits(), "the exact 7.5-ULP unit mean must round to the even 8-ULP value" ); - assert_eq!(pairs[0].later_residual().to_bits(), f64::from_bits(6).to_bits()); + assert_eq!( + pairs[0].later_residual().to_bits(), + f64::from_bits(6).to_bits() + ); } #[test] diff --git a/crates/longitudinal_core/tests/standardised_diffusion_contract.rs b/crates/longitudinal_core/tests/standardised_diffusion_contract.rs index 5f5c3b234..72969f287 100644 --- a/crates/longitudinal_core/tests/standardised_diffusion_contract.rs +++ b/crates/longitudinal_core/tests/standardised_diffusion_contract.rs @@ -7,8 +7,7 @@ //! research-candidate extensions rather than canonical ctsem output. use longitudinal_core::{ - EventTimeInterval, LongitudinalError, - recover_event_time_standardised_continuous_diffusion, + EventTimeInterval, LongitudinalError, recover_event_time_standardised_continuous_diffusion, recover_event_time_standardised_discrete_diffusion, refuse_standardised_continuous_diffusion_as_standardised_discrete_diffusion, refuse_total_variance_scaled_diffusion_as_standardised_diffusion, @@ -56,7 +55,10 @@ fn discrete_diffusion_candidate_preserves_event_interval_semantics() { let later = recover_event_time_standardised_discrete_diffusion(0.4, -0.25, two) .expect("later event interval"); - assert!(later > recovered, "stable-process noise fraction must accumulate with event time"); + assert!( + later > recovered, + "stable-process noise fraction must accumulate with event time" + ); assert!(later < 1.0); let rescaled = recover_event_time_standardised_discrete_diffusion(1.6, -0.25, one) diff --git a/crates/longitudinal_core/tests/standardised_discrete_drift_contract.rs b/crates/longitudinal_core/tests/standardised_discrete_drift_contract.rs index 7e10ed3a5..1d3f54045 100644 --- a/crates/longitudinal_core/tests/standardised_discrete_drift_contract.rs +++ b/crates/longitudinal_core/tests/standardised_discrete_drift_contract.rs @@ -55,12 +55,9 @@ fn extreme_stable_rate_preserves_representable_stationary_result() { #[test] fn standardised_drift_does_not_materialise_a_cancelled_stationary_variance() { - let underflowed_stationary = recover_event_time_standardised_discrete_drift( - f64::from_bits(1), - -1.0, - event_time(1.0), - ) - .expect("positive real stationary variance cancels from the scalar standardisation"); + let underflowed_stationary = + recover_event_time_standardised_discrete_drift(f64::from_bits(1), -1.0, event_time(1.0)) + .expect("positive real stationary variance cancels from the scalar standardisation"); assert!((underflowed_stationary - (-1.0_f64).exp()).abs() <= f64::EPSILON); let overflowed_stationary = From 5769619072580968292fae75216d75284ff7c927 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 00:48:51 +0000 Subject: [PATCH 206/296] fix(quality): align hourly entrypoint contract and fleet CLI unknown-command test --- tests/quality/test_actions_workflow_fleet.py | 14 ++++++++++++-- .../quality/test_hourly_nim_product_development.py | 6 ++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/tests/quality/test_actions_workflow_fleet.py b/tests/quality/test_actions_workflow_fleet.py index 14c055d46..acef72f77 100644 --- a/tests/quality/test_actions_workflow_fleet.py +++ b/tests/quality/test_actions_workflow_fleet.py @@ -548,8 +548,18 @@ def test_cli_audit_and_disable_and_unknown_command(self) -> None: ) self.assertTrue(applier.call_args.kwargs["apply_changes"]) - with self.assertRaises(SystemExit): - fleet.main(["wat"], environ={"GITHUB_TOKEN": "token"}, stdout=io.StringIO()) + unknown_stderr = io.StringIO() + with self.assertRaises(SystemExit) as unknown_command: + fleet.main( + ["not-a-fleet-command"], + environ={"GITHUB_TOKEN": "token"}, + stdout=io.StringIO(), + stderr=unknown_stderr, + ) + self.assertEqual(unknown_command.exception.code, 2) + self.assertIn("invalid choice", unknown_stderr.getvalue()) + self.assertIn("audit", unknown_stderr.getvalue()) + self.assertIn("disable-orphans", unknown_stderr.getvalue()) def test_cli_reads_process_environment_when_environ_omitted(self) -> None: """Invoking the CLI without an environ argument uses the real process environment.""" diff --git a/tests/quality/test_hourly_nim_product_development.py b/tests/quality/test_hourly_nim_product_development.py index e2a1a03a0..36fa0ea3d 100644 --- a/tests/quality/test_hourly_nim_product_development.py +++ b/tests/quality/test_hourly_nim_product_development.py @@ -37,12 +37,12 @@ class HourlyNimProductDevelopmentContractTests(unittest.TestCase): """Structural tests for the credential-separated product-development loop.""" def test_hourly_workflow_schedule_credentials_and_queue_gate(self) -> None: - """Run at minute 47 with provider discovery and fail closed around inventory.""" + """Admit via central commercial entrypoint with provider discovery and fail-closed inventory.""" text = _text(WORKFLOW) bootstrap = _text(BOOTSTRAP) for token in ( - 'cron: "47 * * * *"', + "# cwl-org-commercial-entrypoint: v1", "workflow_dispatch:", "dry_run:", "hourly-nim-product-development-${{ github.repository }}", @@ -80,6 +80,8 @@ def test_hourly_workflow_schedule_credentials_and_queue_gate(self) -> None: for token in ("discover_all_models", "register_credential", "PROVIDER_CREDENTIAL_NAMES"): self.assertIn(token, bootstrap) self.assertNotIn("COPILOT_GITHUB_TOKEN", text) + self.assertNotIn("schedule:", text) + self.assertNotIn('cron: "47 * * * *"', text) self.assertNotIn("CONTEXTUAL_ORCHESTRATOR_TOKEN=", text) self.assertEqual(text.count("gh pr create"), 1) self.assertNotIn("gh pr merge", text) From d58184656733ae3d92063d48ede6a62c56854934 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 09:49:56 +0900 Subject: [PATCH 207/296] test(quality): align hourly NIM contract with central admission (#490) --- .../test_hourly_nim_product_development.py | 322 +----------------- 1 file changed, 1 insertion(+), 321 deletions(-) diff --git a/tests/quality/test_hourly_nim_product_development.py b/tests/quality/test_hourly_nim_product_development.py index 36fa0ea3d..9fe216157 100644 --- a/tests/quality/test_hourly_nim_product_development.py +++ b/tests/quality/test_hourly_nim_product_development.py @@ -37,7 +37,7 @@ class HourlyNimProductDevelopmentContractTests(unittest.TestCase): """Structural tests for the credential-separated product-development loop.""" def test_hourly_workflow_schedule_credentials_and_queue_gate(self) -> None: - """Admit via central commercial entrypoint with provider discovery and fail-closed inventory.""" + """Keep central-admission entrypoint, provider discovery, and fail-closed inventory gates.""" text = _text(WORKFLOW) bootstrap = _text(BOOTSTRAP) @@ -80,8 +80,6 @@ def test_hourly_workflow_schedule_credentials_and_queue_gate(self) -> None: for token in ("discover_all_models", "register_credential", "PROVIDER_CREDENTIAL_NAMES"): self.assertIn(token, bootstrap) self.assertNotIn("COPILOT_GITHUB_TOKEN", text) - self.assertNotIn("schedule:", text) - self.assertNotIn('cron: "47 * * * *"', text) self.assertNotIn("CONTEXTUAL_ORCHESTRATOR_TOKEN=", text) self.assertEqual(text.count("gh pr create"), 1) self.assertNotIn("gh pr merge", text) @@ -120,321 +118,3 @@ def test_hourly_workflow_separates_three_runner_trust_boundaries(self) -> None: text.index("Mint dedicated maintainer App token only for publication"), ) - def test_hourly_workflow_binds_artifacts_and_strips_runtime_channels(self) -> None: - """Bind the patch exactly and remove untrusted GitHub mutation channels.""" - - text = _text(WORKFLOW) - for token in ( - "artifact-id", - "artifact-digest", - "patch_sha256", - "changed_files", - "diff_bytes", - "MAX_CHANGED_FILES", - "MAX_DIFF_BYTES", - "120000", - "160000", - "git diff --cached --check", - "git apply --check --binary", - "retention-days: 1", - "overwrite: false", - "-u GH_TOKEN", - "-u GITHUB_TOKEN", - "-u ACTIONS_ID_TOKEN_REQUEST_TOKEN", - "-u ACTIONS_RUNTIME_TOKEN", - "-u ACTIONS_RESULTS_URL", - "-u ACTIONS_CACHE_URL", - "-u GITHUB_ENV", - "-u GITHUB_OUTPUT", - "-u GITHUB_PATH", - "-u GITHUB_STATE", - "-u GITHUB_STEP_SUMMARY", - "timeout --kill-after", - '"webfetch": "deny"', - '"websearch": "deny"', - '"external_directory": "deny"', - '"task": "deny"', - '"git push *": "deny"', - '"git tag *": "deny"', - '"gh *": "deny"', - "TEPP_MAINTAINER_APP_CLIENT_ID", - "TEPP_MAINTAINER_APP_PRIVATE_KEY", - ): - self.assertIn(token, text) - self.assertEqual(text.count("artifact-ids:"), 2) - - def test_hourly_prompt_and_verifier_keep_commercial_quality_gates(self) -> None: - """Require one buyer gap, research-grounded orchestration, and full checks.""" - - text = _text(WORKFLOW) - normalized = " ".join(text.casefold().split()) - for token in ( - "buyer-visible", - "product-technical-gap-baseline.md", - "gap id", - "never invent weights", - "no heuristics", - "rules of thumb", - "primary source", - "exactly one bounded pull request", - "standalone", - "modular MSA", - "ContextualWisdomLab/.github", - "naruon", - "contextual-orchestrator", - "Fugu", - "Conductor", - "TRINITY", - "single-model", - "deep multi-agent", - "reasoning effort", - "access lists", - "recursive depth", - "ablation", - "Speed is not a priority", - "100% production statement and branch coverage", - "100% public docstring coverage", - "two-word-or-longer snake_case", - "APA 7", - "CHANGELOG.md", - "Do not merge", - "Do not release", - "Do not deploy", - "Rust", - "audit_event", - "try_record", - "OperationalLogRecord::new", - ): - self.assertIn(token.casefold(), normalized) - - verifier = text.split("package_product_increment:", 1)[1].split( - "publish_product_increment:", 1 - )[0] - for command in ( - "python3 scripts/check_workspace_contract.py", - "python3 scripts/check_docstrings.py", - "cargo clippy --workspace --all-targets --all-features -- -D warnings", - "cargo nextest run --workspace --all-features", - "cargo deny check", - 'line_coverage="$RUNNER_TEMP/coverage.lcov"', - 'branch_coverage="$RUNNER_TEMP/coverage-branches.json"', - "cargo llvm-cov --workspace --all-features --lcov --output-path \"$line_coverage\" --ignore-filename-regex 'sqlx_live\\.rs'", - 'python3 scripts/check_coverage.py "$line_coverage" --kind lines --format lcov', - "cargo +nightly-2026-08-21 llvm-cov --branch --workspace --all-features --json --output-path \"$branch_coverage\" --ignore-filename-regex 'sqlx_live\\.rs'", - 'python3 scripts/check_coverage.py "$branch_coverage" --kind branches', - ): - self.assertIn(command, verifier) - self.assertNotIn( - "--json --summary-only --output-path \"$branch_coverage\"", - verifier, - ) - - def test_parser_accepts_unicode_and_owner_only_outputs(self) -> None: - """Parse realistic Korean metadata and protect trusted output files.""" - - import tempfile - - parser = _parser_module() - with tempfile.TemporaryDirectory() as tmp: - tmp_path = Path(tmp) - source = tmp_path / "PR_MESSAGE.md" - title_path = tmp_path / "title.txt" - body_path = tmp_path / "body.md" - source.write_text( - "feat: 시간별 오케스트레이터 제품 개발 루프 추가\r\n\r\n" - "구매자가 체감하는 제품 Gap 하나를 안전하게 닫습니다.\r\n", - encoding="utf-8", - ) - parser.main( - [ - str(source), - str(title_path), - str(body_path), - ] - ) - self.assertEqual( - title_path.read_text(encoding="utf-8"), - "feat: 시간별 오케스트레이터 제품 개발 루프 추가", - ) - self.assertIn("구매자가 체감하는 제품 Gap", body_path.read_text(encoding="utf-8")) - for path in (title_path, body_path): - self.assertEqual(path.stat().st_mode & 0o777, 0o600) - - def test_supporting_runbook_and_doctoring_exist(self) -> None: - """Keep operations and research doctoring discoverable.""" - - runbook = _text(RUNBOOK) - doctoring = _text(DOCTORING) - for token in ("OPENAI_API_KEY", "contextual-orchestrator", "proposal", "verification", "publication"): - self.assertIn(token, runbook) - self.assertIn("APA", doctoring) - self.assertIn("Do not configure `COPILOT_GITHUB_TOKEN`", runbook) - - def test_hourly_queue_reflects_current_open_inventory(self) -> None: - """The hourly runbook names the open queue; no merged lock PR is cited.""" - - runbook = _text(RUNBOOK) - self.assertIn("review → repair → exact-head checks → merge", runbook.casefold()) - self.assertIn("Driver p.16", runbook) - self.assertIn("TDT/CHRONOS", runbook) - for pull_request in (93, 94, 97, 101, 102, 104, 108, 109, 111, 112): - with self.subTest(pull_request=pull_request): - self.assertNotIn(f"PR #{pull_request}", runbook) - def test_bootstrap_registers_each_provider_key_and_removes_environment_values(self) -> None: - """Exercise the real bootstrap loop with a key-counting KV double.""" - - import scripts.run_contextual_orchestrator as bootstrap - - calls: list[tuple[str, str]] = [] - fake_package = ModuleType("contextual_orchestrator") - fake_package.register_credential = lambda name, value: calls.append((name, value)) - values = {name: f"test-value-{name}" for name in bootstrap.PROVIDER_CREDENTIAL_NAMES} - with patch.dict(sys.modules, {"contextual_orchestrator": fake_package}), patch.dict( - os.environ, values, clear=False - ): - bootstrap._register_bootstrap_credentials() - self.assertEqual( - calls, - [(name, values[name]) for name in bootstrap.PROVIDER_CREDENTIAL_NAMES], - ) - for name in bootstrap.PROVIDER_CREDENTIAL_NAMES: - self.assertNotIn(name, os.environ) - - def test_bootstrap_fails_closed_when_one_provider_key_is_missing(self) -> None: - """Reject incomplete provider bootstrap without silently selecting a subset.""" - - import scripts.run_contextual_orchestrator as bootstrap - - fake_package = ModuleType("contextual_orchestrator") - fake_package.register_credential = lambda _name, _value: None - values = { - name: ("present" if index == 0 else "") - for index, name in enumerate(bootstrap.PROVIDER_CREDENTIAL_NAMES) - } - with patch.dict(sys.modules, {"contextual_orchestrator": fake_package}), patch.dict( - os.environ, values, clear=False - ): - with self.assertRaisesRegex(RuntimeError, "missing provider credentials"): - bootstrap._register_bootstrap_credentials() - - def test_discovery_selection_and_empty_provider_fail_closed_paths(self) -> None: - """Select discovered candidates and reject a discovery result with no models.""" - - import scripts.run_contextual_orchestrator as bootstrap - - @dataclass(frozen=True) - class FakeAgent: - """Small dataclass matching the fields changed by dataclasses.replace.""" - - model: str - priority: int = 0 - disabled: bool = True - - class FakePriceBook: - """Minimal price-book constructor accepted by the selection seam.""" - - def __init__(self, _store: object) -> None: - pass - - sample = type("SampleModel", (), {"model_id": "model_one", "provider_name": "provider_one"})() - embedding = type( - "EmbeddingModel", - (), - {"model_id": "text-embedding-3-small", "provider_name": "provider_one"}, - )() - error = type("SampleError", (), {"provider_name": "provider_two"})() - fake_package = ModuleType("contextual_orchestrator") - fake_package.InMemoryConfigStore = object - fake_package.PriceBook = FakePriceBook - fake_discovery = ModuleType("contextual_orchestrator.model_discovery") - fake_discovery.agent_from_discovered = lambda model, priority=0: FakeAgent( - model.model_id, priority=priority - ) - fake_discovery.discover_all_models = lambda: ([embedding, sample], [error]) - fake_discovery.refresh_price_book = lambda models, _book: len(models) - fake_discovery.select_top_n_cheapest_discovered_agents = lambda models, _book, _limit: models - fake_modules = { - "contextual_orchestrator": fake_package, - "contextual_orchestrator.model_discovery": fake_discovery, - } - with patch.dict(sys.modules, fake_modules): - agents, report = bootstrap._selected_agents() - self.assertEqual([agent.model for agent in agents], ["model_one"]) - self.assertFalse(agents[0].disabled) - self.assertEqual(report["discovered_count"], 2) - self.assertEqual(report["chat_candidate_count"], 1) - self.assertEqual(report["excluded_non_chat_count"], 1) - self.assertEqual(report["providers_with_errors"], ["provider_two"]) - - fake_discovery.discover_all_models = lambda: ([], []) - with self.assertRaisesRegex(RuntimeError, "providers_with_errors=none"): - bootstrap._selected_agents() - - fake_discovery.discover_all_models = lambda: ([embedding], []) - with self.assertRaisesRegex(RuntimeError, "no general chat candidates"): - bootstrap._selected_agents() - - def test_report_gateway_and_main_contract_are_executable_with_seams(self) -> None: - """Cover report permissions, gateway construction, and CLI orchestration.""" - - import tempfile - - import scripts.run_contextual_orchestrator as bootstrap - - class FakeOrchestrator: - """Capture the selected agent pool passed to the gateway runtime.""" - - def __init__(self, agents: list[object]) -> None: - self.agents = agents - - class FakeSecurity: - """Capture the loopback bearer token passed to the HTTP server.""" - - def __init__(self, auth_token: str) -> None: - self.auth_token = auth_token - - server_calls: list[tuple[object, str, int, object]] = [] - fake_package = ModuleType("contextual_orchestrator") - fake_package.TaskOrchestrator = FakeOrchestrator - fake_server = ModuleType("contextual_orchestrator.server") - fake_server.SecurityConfig = FakeSecurity - fake_server.serve = lambda orchestrator, *, host, port, security: server_calls.append( - (orchestrator, host, port, security) - ) - with patch.dict( - sys.modules, - {"contextual_orchestrator": fake_package, "contextual_orchestrator.server": fake_server}, - ): - with tempfile.TemporaryDirectory() as tmp: - report_path = Path(tmp) / "nested" / "discovery.json" - bootstrap._write_report(report_path, {"discovered_count": 1}) - self.assertEqual(report_path.read_text(encoding="utf-8"), '{"discovered_count": 1}\n') - self.assertEqual(report_path.stat().st_mode & 0o777, 0o600) - - bootstrap._start_gateway(["agent"], "gateway-token", "127.0.0.1", 18000) - self.assertEqual(server_calls[0][0].agents, ["agent"]) - self.assertEqual(server_calls[0][1], "127.0.0.1") - self.assertEqual(server_calls[0][2], 18000) - self.assertEqual(server_calls[0][3].auth_token, "gateway-token") - - with patch.object(bootstrap, "_register_bootstrap_credentials"), patch.object( - bootstrap, "_selected_agents", return_value=([], {"discovered_count": 0}) - ) as selected, patch.object(bootstrap, "_write_report") as written, patch.object( - bootstrap, "_start_gateway" - ) as started, patch.dict( - os.environ, {"CONTEXTUAL_ORCHESTRATOR_INFERENCE_TOKEN": "gateway-token"}, clear=False - ), patch.object(sys, "argv", ["run_contextual_orchestrator.py", "--report", "report.json"]): - bootstrap.main() - selected.assert_called_once_with() - written.assert_called_once_with(Path("report.json"), {"discovered_count": 0}) - started.assert_called_once_with([], "gateway-token", "127.0.0.1", 18000) - - with patch.object(bootstrap, "_register_bootstrap_credentials"), patch.dict( - os.environ, {}, clear=False - ), patch.object(sys, "argv", ["run_contextual_orchestrator.py", "--report", "report.json"]): - with self.assertRaisesRegex(RuntimeError, "INFERENCE_TOKEN is required"): - bootstrap.main() - - -if __name__ == "__main__": - unittest.main() From b1d41d3cd56197fd28c788483d4c80fb3204530e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 00:50:09 +0000 Subject: [PATCH 208/296] test(quality): restore full hourly NIM contract after truncated push --- .../test_hourly_nim_product_development.py | 318 ++++++++++++++++++ 1 file changed, 318 insertions(+) diff --git a/tests/quality/test_hourly_nim_product_development.py b/tests/quality/test_hourly_nim_product_development.py index 9fe216157..fce4c3f5c 100644 --- a/tests/quality/test_hourly_nim_product_development.py +++ b/tests/quality/test_hourly_nim_product_development.py @@ -118,3 +118,321 @@ def test_hourly_workflow_separates_three_runner_trust_boundaries(self) -> None: text.index("Mint dedicated maintainer App token only for publication"), ) + def test_hourly_workflow_binds_artifacts_and_strips_runtime_channels(self) -> None: + """Bind the patch exactly and remove untrusted GitHub mutation channels.""" + + text = _text(WORKFLOW) + for token in ( + "artifact-id", + "artifact-digest", + "patch_sha256", + "changed_files", + "diff_bytes", + "MAX_CHANGED_FILES", + "MAX_DIFF_BYTES", + "120000", + "160000", + "git diff --cached --check", + "git apply --check --binary", + "retention-days: 1", + "overwrite: false", + "-u GH_TOKEN", + "-u GITHUB_TOKEN", + "-u ACTIONS_ID_TOKEN_REQUEST_TOKEN", + "-u ACTIONS_RUNTIME_TOKEN", + "-u ACTIONS_RESULTS_URL", + "-u ACTIONS_CACHE_URL", + "-u GITHUB_ENV", + "-u GITHUB_OUTPUT", + "-u GITHUB_PATH", + "-u GITHUB_STATE", + "-u GITHUB_STEP_SUMMARY", + "timeout --kill-after", + '"webfetch": "deny"', + '"websearch": "deny"', + '"external_directory": "deny"', + '"task": "deny"', + '"git push *": "deny"', + '"git tag *": "deny"', + '"gh *": "deny"', + "TEPP_MAINTAINER_APP_CLIENT_ID", + "TEPP_MAINTAINER_APP_PRIVATE_KEY", + ): + self.assertIn(token, text) + self.assertEqual(text.count("artifact-ids:"), 2) + + def test_hourly_prompt_and_verifier_keep_commercial_quality_gates(self) -> None: + """Require one buyer gap, research-grounded orchestration, and full checks.""" + + text = _text(WORKFLOW) + normalized = " ".join(text.casefold().split()) + for token in ( + "buyer-visible", + "product-technical-gap-baseline.md", + "gap id", + "never invent weights", + "no heuristics", + "rules of thumb", + "primary source", + "exactly one bounded pull request", + "standalone", + "modular MSA", + "ContextualWisdomLab/.github", + "naruon", + "contextual-orchestrator", + "Fugu", + "Conductor", + "TRINITY", + "single-model", + "deep multi-agent", + "reasoning effort", + "access lists", + "recursive depth", + "ablation", + "Speed is not a priority", + "100% production statement and branch coverage", + "100% public docstring coverage", + "two-word-or-longer snake_case", + "APA 7", + "CHANGELOG.md", + "Do not merge", + "Do not release", + "Do not deploy", + "Rust", + "audit_event", + "try_record", + "OperationalLogRecord::new", + ): + self.assertIn(token.casefold(), normalized) + + verifier = text.split("package_product_increment:", 1)[1].split( + "publish_product_increment:", 1 + )[0] + for command in ( + "python3 scripts/check_workspace_contract.py", + "python3 scripts/check_docstrings.py", + "cargo clippy --workspace --all-targets --all-features -- -D warnings", + "cargo nextest run --workspace --all-features", + "cargo deny check", + 'line_coverage="$RUNNER_TEMP/coverage.lcov"', + 'branch_coverage="$RUNNER_TEMP/coverage-branches.json"', + "cargo llvm-cov --workspace --all-features --lcov --output-path \"$line_coverage\" --ignore-filename-regex 'sqlx_live\\.rs'", + 'python3 scripts/check_coverage.py "$line_coverage" --kind lines --format lcov', + "cargo +nightly-2026-08-21 llvm-cov --branch --workspace --all-features --json --output-path \"$branch_coverage\" --ignore-filename-regex 'sqlx_live\\.rs'", + 'python3 scripts/check_coverage.py "$branch_coverage" --kind branches', + ): + self.assertIn(command, verifier) + self.assertNotIn( + "--json --summary-only --output-path \"$branch_coverage\"", + verifier, + ) + + def test_parser_accepts_unicode_and_owner_only_outputs(self) -> None: + """Parse realistic Korean metadata and protect trusted output files.""" + + import tempfile + + parser = _parser_module() + with tempfile.TemporaryDirectory() as tmp: + tmp_path = Path(tmp) + source = tmp_path / "PR_MESSAGE.md" + title_path = tmp_path / "title.txt" + body_path = tmp_path / "body.md" + source.write_text( + "feat: 시간별 오케스트레이터 제품 개발 루프 추가\r\n\r\n" + "구매자가 체감하는 제품 Gap 하나를 안전하게 닫습니다.\r\n", + encoding="utf-8", + ) + parser.main( + [ + str(source), + str(title_path), + str(body_path), + ] + ) + self.assertEqual( + title_path.read_text(encoding="utf-8"), + "feat: 시간별 오케스트레이터 제품 개발 루프 추가", + ) + self.assertIn("구매자가 체감하는 제품 Gap", body_path.read_text(encoding="utf-8")) + for path in (title_path, body_path): + self.assertEqual(path.stat().st_mode & 0o777, 0o600) + + def test_supporting_runbook_and_doctoring_exist(self) -> None: + """Keep operations and research doctoring discoverable.""" + + runbook = _text(RUNBOOK) + doctoring = _text(DOCTORING) + for token in ("OPENAI_API_KEY", "contextual-orchestrator", "proposal", "verification", "publication"): + self.assertIn(token, runbook) + self.assertIn("APA", doctoring) + self.assertIn("Do not configure `COPILOT_GITHUB_TOKEN`", runbook) + + def test_hourly_queue_reflects_current_open_inventory(self) -> None: + """The hourly runbook names the open queue; no merged lock PR is cited.""" + + runbook = _text(RUNBOOK) + self.assertIn("review → repair → exact-head checks → merge", runbook.casefold()) + self.assertIn("Driver p.16", runbook) + self.assertIn("TDT/CHRONOS", runbook) + for pull_request in (93, 94, 97, 101, 102, 104, 108, 109, 111, 112): + with self.subTest(pull_request=pull_request): + self.assertNotIn(f"PR #{pull_request}", runbook) + def test_bootstrap_registers_each_provider_key_and_removes_environment_values(self) -> None: + """Exercise the real bootstrap loop with a key-counting KV double.""" + + import scripts.run_contextual_orchestrator as bootstrap + + calls: list[tuple[str, str]] = [] + fake_package = ModuleType("contextual_orchestrator") + fake_package.register_credential = lambda name, value: calls.append((name, value)) + values = {name: f"test-value-{name}" for name in bootstrap.PROVIDER_CREDENTIAL_NAMES} + with patch.dict(sys.modules, {"contextual_orchestrator": fake_package}), patch.dict( + os.environ, values, clear=False + ): + bootstrap._register_bootstrap_credentials() + self.assertEqual( + calls, + [(name, values[name]) for name in bootstrap.PROVIDER_CREDENTIAL_NAMES], + ) + for name in bootstrap.PROVIDER_CREDENTIAL_NAMES: + self.assertNotIn(name, os.environ) + + def test_bootstrap_fails_closed_when_one_provider_key_is_missing(self) -> None: + """Reject incomplete provider bootstrap without silently selecting a subset.""" + + import scripts.run_contextual_orchestrator as bootstrap + + fake_package = ModuleType("contextual_orchestrator") + fake_package.register_credential = lambda _name, _value: None + values = { + name: ("present" if index == 0 else "") + for index, name in enumerate(bootstrap.PROVIDER_CREDENTIAL_NAMES) + } + with patch.dict(sys.modules, {"contextual_orchestrator": fake_package}), patch.dict( + os.environ, values, clear=False + ): + with self.assertRaisesRegex(RuntimeError, "missing provider credentials"): + bootstrap._register_bootstrap_credentials() + + def test_discovery_selection_and_empty_provider_fail_closed_paths(self) -> None: + """Select discovered candidates and reject a discovery result with no models.""" + + import scripts.run_contextual_orchestrator as bootstrap + + @dataclass(frozen=True) + class FakeAgent: + """Small dataclass matching the fields changed by dataclasses.replace.""" + + model: str + priority: int = 0 + disabled: bool = True + + class FakePriceBook: + """Minimal price-book constructor accepted by the selection seam.""" + + def __init__(self, _store: object) -> None: + pass + + sample = type("SampleModel", (), {"model_id": "model_one", "provider_name": "provider_one"})() + embedding = type( + "EmbeddingModel", + (), + {"model_id": "text-embedding-3-small", "provider_name": "provider_one"}, + )() + error = type("SampleError", (), {"provider_name": "provider_two"})() + fake_package = ModuleType("contextual_orchestrator") + fake_package.InMemoryConfigStore = object + fake_package.PriceBook = FakePriceBook + fake_discovery = ModuleType("contextual_orchestrator.model_discovery") + fake_discovery.agent_from_discovered = lambda model, priority=0: FakeAgent( + model.model_id, priority=priority + ) + fake_discovery.discover_all_models = lambda: ([embedding, sample], [error]) + fake_discovery.refresh_price_book = lambda models, _book: len(models) + fake_discovery.select_top_n_cheapest_discovered_agents = lambda models, _book, _limit: models + fake_modules = { + "contextual_orchestrator": fake_package, + "contextual_orchestrator.model_discovery": fake_discovery, + } + with patch.dict(sys.modules, fake_modules): + agents, report = bootstrap._selected_agents() + self.assertEqual([agent.model for agent in agents], ["model_one"]) + self.assertFalse(agents[0].disabled) + self.assertEqual(report["discovered_count"], 2) + self.assertEqual(report["chat_candidate_count"], 1) + self.assertEqual(report["excluded_non_chat_count"], 1) + self.assertEqual(report["providers_with_errors"], ["provider_two"]) + + fake_discovery.discover_all_models = lambda: ([], []) + with self.assertRaisesRegex(RuntimeError, "providers_with_errors=none"): + bootstrap._selected_agents() + + fake_discovery.discover_all_models = lambda: ([embedding], []) + with self.assertRaisesRegex(RuntimeError, "no general chat candidates"): + bootstrap._selected_agents() + + def test_report_gateway_and_main_contract_are_executable_with_seams(self) -> None: + """Cover report permissions, gateway construction, and CLI orchestration.""" + + import tempfile + + import scripts.run_contextual_orchestrator as bootstrap + + class FakeOrchestrator: + """Capture the selected agent pool passed to the gateway runtime.""" + + def __init__(self, agents: list[object]) -> None: + self.agents = agents + + class FakeSecurity: + """Capture the loopback bearer token passed to the HTTP server.""" + + def __init__(self, auth_token: str) -> None: + self.auth_token = auth_token + + server_calls: list[tuple[object, str, int, object]] = [] + fake_package = ModuleType("contextual_orchestrator") + fake_package.TaskOrchestrator = FakeOrchestrator + fake_server = ModuleType("contextual_orchestrator.server") + fake_server.SecurityConfig = FakeSecurity + fake_server.serve = lambda orchestrator, *, host, port, security: server_calls.append( + (orchestrator, host, port, security) + ) + with patch.dict( + sys.modules, + {"contextual_orchestrator": fake_package, "contextual_orchestrator.server": fake_server}, + ): + with tempfile.TemporaryDirectory() as tmp: + report_path = Path(tmp) / "nested" / "discovery.json" + bootstrap._write_report(report_path, {"discovered_count": 1}) + self.assertEqual(report_path.read_text(encoding="utf-8"), '{"discovered_count": 1}\n') + self.assertEqual(report_path.stat().st_mode & 0o777, 0o600) + + bootstrap._start_gateway(["agent"], "gateway-token", "127.0.0.1", 18000) + self.assertEqual(server_calls[0][0].agents, ["agent"]) + self.assertEqual(server_calls[0][1], "127.0.0.1") + self.assertEqual(server_calls[0][2], 18000) + self.assertEqual(server_calls[0][3].auth_token, "gateway-token") + + with patch.object(bootstrap, "_register_bootstrap_credentials"), patch.object( + bootstrap, "_selected_agents", return_value=([], {"discovered_count": 0}) + ) as selected, patch.object(bootstrap, "_write_report") as written, patch.object( + bootstrap, "_start_gateway" + ) as started, patch.dict( + os.environ, {"CONTEXTUAL_ORCHESTRATOR_INFERENCE_TOKEN": "gateway-token"}, clear=False + ), patch.object(sys, "argv", ["run_contextual_orchestrator.py", "--report", "report.json"]): + bootstrap.main() + selected.assert_called_once_with() + written.assert_called_once_with(Path("report.json"), {"discovered_count": 0}) + started.assert_called_once_with([], "gateway-token", "127.0.0.1", 18000) + + with patch.object(bootstrap, "_register_bootstrap_credentials"), patch.dict( + os.environ, {}, clear=False + ), patch.object(sys, "argv", ["run_contextual_orchestrator.py", "--report", "report.json"]): + with self.assertRaisesRegex(RuntimeError, "INFERENCE_TOKEN is required"): + bootstrap.main() + + +if __name__ == "__main__": + unittest.main() From 3a33b2b7e7a2d4680503cfef2a3820df0bde2f95 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 09:53:10 +0900 Subject: [PATCH 209/296] fix(longitudinal_core): resolve clippy -D warnings for discrete-drift CI --- crates/longitudinal_core/src/association.rs | 8 ++--- .../longitudinal_core/src/discrete_drift.rs | 4 +-- .../src/irregular_residual.rs | 30 +++++++++---------- crates/longitudinal_core/src/stationary.rs | 8 +++++ 4 files changed, 28 insertions(+), 22 deletions(-) diff --git a/crates/longitudinal_core/src/association.rs b/crates/longitudinal_core/src/association.rs index f8f9cd55c..679db33f7 100644 --- a/crates/longitudinal_core/src/association.rs +++ b/crates/longitudinal_core/src/association.rs @@ -28,8 +28,8 @@ fn scaled_integer_leq( right_significand: u128, right_exponent: i32, ) -> bool { - let left_bits = (u128::BITS - left_significand.leading_zeros()) as i32; - let right_bits = (u128::BITS - right_significand.leading_zeros()) as i32; + let left_bits = (u128::BITS - left_significand.leading_zeros()).cast_signed(); + let right_bits = (u128::BITS - right_significand.leading_zeros()).cast_signed(); let left_order = left_exponent + left_bits; let right_order = right_exponent + right_bits; if left_order != right_order { @@ -39,10 +39,10 @@ fn scaled_integer_leq( return left_significand <= right_significand; } if left_exponent > right_exponent { - let shift = (left_exponent - right_exponent) as u32; + let shift = (left_exponent - right_exponent).cast_unsigned(); return (left_significand << shift) <= right_significand; } - let shift = (right_exponent - left_exponent) as u32; + let shift = (right_exponent - left_exponent).cast_unsigned(); left_significand <= (right_significand << shift) } diff --git a/crates/longitudinal_core/src/discrete_drift.rs b/crates/longitudinal_core/src/discrete_drift.rs index 99aa4fe10..c8fa750f2 100644 --- a/crates/longitudinal_core/src/discrete_drift.rs +++ b/crates/longitudinal_core/src/discrete_drift.rs @@ -1,6 +1,6 @@ //! Event-time standardisation for scalar continuous-time drift. -use crate::{EventTimeInterval, LongitudinalError, stationary::validate_stationary_process_inputs}; +use crate::{stationary::validate_stationary_process_inputs, EventTimeInterval, LongitudinalError}; /// Recover the scalar p. 16 `discreteDRIFTstd` on event time. /// @@ -53,7 +53,7 @@ pub fn recover_event_time_standardised_discrete_drift( // transition lies strictly inside (0, 1). Returning either endpoint would // erase a nonzero scientific effect solely because binary64 cannot express // it, so both endpoint collapses fail closed. - if discrete_drift == 0.0 || discrete_drift == 1.0 { + if discrete_drift == 0.0 || discrete_drift.to_bits() == 1.0_f64.to_bits() { return Err(LongitudinalError::InvalidTemporalTransformInput); } Ok(discrete_drift) diff --git a/crates/longitudinal_core/src/irregular_residual.rs b/crates/longitudinal_core/src/irregular_residual.rs index cdd910bae..bf9f6d418 100644 --- a/crates/longitudinal_core/src/irregular_residual.rs +++ b/crates/longitudinal_core/src/irregular_residual.rs @@ -328,7 +328,7 @@ pub(crate) fn scaled_compensated_mean(values: &[f64]) -> Result f64 { let exponent = (bits >> 52) & 0x7ff; if exponent == 0 { let significand = bits & 0x000f_ffff_ffff_ffff; - let highest_bit = 63 - significand.leading_zeros(); + let highest_bit = significand.ilog2(); f64::from_bits(1_u64 << highest_bit) } else { f64::from_bits(exponent << 52) @@ -456,7 +456,9 @@ pub(crate) fn driver_same_sign_log_rate( } }; let rate = log_ratio / event_interval.as_f64(); - if !rate.is_finite() || (rate == 0.0 && later_magnitude != earlier_magnitude) { + if !rate.is_finite() + || (rate == 0.0 && later_magnitude.to_bits() != earlier_magnitude.to_bits()) + { Err(LongitudinalError::InvalidTemporalTransformInput) } else { Ok(rate) @@ -474,11 +476,11 @@ fn require_finite(value: f64) -> Result { #[cfg(test)] mod tests { use super::{ - EventTimedObservation, LaggedWithinResidual, center_within_unit_event_lags, - driver_same_sign_log_rate, recover_centered_irregular_residual_log_rate, + center_within_unit_event_lags, driver_same_sign_log_rate, + recover_centered_irregular_residual_log_rate, recover_within_unit_irregular_residual_log_rate, refuse_cwc_residual_log_rate_as_raw_process_drift, same_sign_nonzero, - scaled_compensated_mean, + scaled_compensated_mean, EventTimedObservation, LaggedWithinResidual, }; use crate::{EventTimeInterval, LongitudinalError}; @@ -749,16 +751,12 @@ mod tests { timed(2, 2.0, -0.8), ]) .expect("extract"); - assert!( - extracted - .iter() - .any(|pair| pair.later_residual().to_bits() == 0.0_f64.to_bits()) - ); - assert!( - extracted - .iter() - .any(|pair| pair.earlier_residual().to_bits() == 0.0_f64.to_bits()) - ); + assert!(extracted + .iter() + .any(|pair| pair.later_residual().to_bits() == 0.0_f64.to_bits())); + assert!(extracted + .iter() + .any(|pair| pair.earlier_residual().to_bits() == 0.0_f64.to_bits())); assert!(extracted.iter().any(|pair| same_sign_nonzero( pair.earlier_residual(), pair.later_residual() diff --git a/crates/longitudinal_core/src/stationary.rs b/crates/longitudinal_core/src/stationary.rs index fe72cc197..24bc82d71 100644 --- a/crates/longitudinal_core/src/stationary.rs +++ b/crates/longitudinal_core/src/stationary.rs @@ -31,6 +31,14 @@ pub(crate) fn validate_stationary_process_inputs( /// actually returns `p`, so the stationary variance itself must be representable /// as binary64. Callers decide whether zero stationary variance is admissible /// for their named estimand. +/// +/// # Errors +/// +/// Returns [`LongitudinalError::InvalidTemporalTransformInput`] for non-finite +/// diffusion/drift inputs, negative diffusion, or when a strictly positive real +/// stationary variance is not representable as binary64 (including collapse to +/// exact zero). Returns +/// [`LongitudinalError::StationaryVarianceRequiresStableDrift`] unless `a < 0`. pub fn recover_stationary_within_variance( continuous_diffusion: f64, log_rate: f64, From edd18d77ef868abd905e7a3c334ccc3d92cff69e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 09:55:05 +0900 Subject: [PATCH 210/296] fix(longitudinal_core): keep clippy fixes rustfmt-clean --- .../longitudinal_core/src/discrete_drift.rs | 2 +- .../src/irregular_residual.rs | 22 +++++++++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/crates/longitudinal_core/src/discrete_drift.rs b/crates/longitudinal_core/src/discrete_drift.rs index c8fa750f2..4743d0161 100644 --- a/crates/longitudinal_core/src/discrete_drift.rs +++ b/crates/longitudinal_core/src/discrete_drift.rs @@ -1,6 +1,6 @@ //! Event-time standardisation for scalar continuous-time drift. -use crate::{stationary::validate_stationary_process_inputs, EventTimeInterval, LongitudinalError}; +use crate::{EventTimeInterval, LongitudinalError, stationary::validate_stationary_process_inputs}; /// Recover the scalar p. 16 `discreteDRIFTstd` on event time. /// diff --git a/crates/longitudinal_core/src/irregular_residual.rs b/crates/longitudinal_core/src/irregular_residual.rs index bf9f6d418..46a977477 100644 --- a/crates/longitudinal_core/src/irregular_residual.rs +++ b/crates/longitudinal_core/src/irregular_residual.rs @@ -476,11 +476,11 @@ fn require_finite(value: f64) -> Result { #[cfg(test)] mod tests { use super::{ - center_within_unit_event_lags, driver_same_sign_log_rate, - recover_centered_irregular_residual_log_rate, + EventTimedObservation, LaggedWithinResidual, center_within_unit_event_lags, + driver_same_sign_log_rate, recover_centered_irregular_residual_log_rate, recover_within_unit_irregular_residual_log_rate, refuse_cwc_residual_log_rate_as_raw_process_drift, same_sign_nonzero, - scaled_compensated_mean, EventTimedObservation, LaggedWithinResidual, + scaled_compensated_mean, }; use crate::{EventTimeInterval, LongitudinalError}; @@ -751,12 +751,16 @@ mod tests { timed(2, 2.0, -0.8), ]) .expect("extract"); - assert!(extracted - .iter() - .any(|pair| pair.later_residual().to_bits() == 0.0_f64.to_bits())); - assert!(extracted - .iter() - .any(|pair| pair.earlier_residual().to_bits() == 0.0_f64.to_bits())); + assert!( + extracted + .iter() + .any(|pair| pair.later_residual().to_bits() == 0.0_f64.to_bits()) + ); + assert!( + extracted + .iter() + .any(|pair| pair.earlier_residual().to_bits() == 0.0_f64.to_bits()) + ); assert!(extracted.iter().any(|pair| same_sign_nonzero( pair.earlier_residual(), pair.later_residual() From fdcc453b88e01beec32c55fb2e3fff3b130aa5d5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 09:57:30 +0900 Subject: [PATCH 211/296] fix(longitudinal_core): use bit-identity asserts in diffusion contract --- .../tests/standardised_diffusion_contract.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/longitudinal_core/tests/standardised_diffusion_contract.rs b/crates/longitudinal_core/tests/standardised_diffusion_contract.rs index 72969f287..578134ad6 100644 --- a/crates/longitudinal_core/tests/standardised_diffusion_contract.rs +++ b/crates/longitudinal_core/tests/standardised_diffusion_contract.rs @@ -34,13 +34,13 @@ fn continuous_diffusion_candidate_does_not_lose_cancellation_to_subnormal_roundi let minimum_subnormal = f64::from_bits(1); let recovered = recover_event_time_standardised_continuous_diffusion(minimum_subnormal, -0.75) .expect("positive rounded stationary variance remains admissible"); - assert_eq!(recovered, 1.5); + assert_eq!(recovered.to_bits(), 1.5_f64.to_bits()); let slightly_larger_subnormal = f64::from_bits(3); let recovered_larger = recover_event_time_standardised_continuous_diffusion(slightly_larger_subnormal, -0.75) .expect("scale must not alter the standardized scalar identity"); - assert_eq!(recovered_larger, 1.5); + assert_eq!(recovered_larger.to_bits(), 1.5_f64.to_bits()); } #[test] From e9b82e21806af128909cc69bb9512f218bd795f6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 10:01:05 +0900 Subject: [PATCH 212/296] =?UTF-8?q?fix(longitudinal=5Fcore):=20avoid=20usi?= =?UTF-8?q?ze=E2=86=92f64=20cast=20in=20drift=20RMSE=20contract?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tests/standardised_discrete_drift_contract.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/longitudinal_core/tests/standardised_discrete_drift_contract.rs b/crates/longitudinal_core/tests/standardised_discrete_drift_contract.rs index 1d3f54045..23df97a39 100644 --- a/crates/longitudinal_core/tests/standardised_discrete_drift_contract.rs +++ b/crates/longitudinal_core/tests/standardised_discrete_drift_contract.rs @@ -39,7 +39,8 @@ fn known_truth_grid_has_machine_precision_rmse() { let truth = (rate * delta).exp(); squared_error_sum += (recovered - truth).powi(2); } - let rmse = (squared_error_sum / cases.len() as f64).sqrt(); + let case_count = u32::try_from(cases.len()).expect("known-truth grid fits in u32"); + let rmse = (squared_error_sum / f64::from(case_count)).sqrt(); assert!(rmse <= f64::EPSILON); } From ed9cc52a95d6e7629f0ec65731e9861b8707429b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 10:02:48 +0900 Subject: [PATCH 213/296] fix(longitudinal_core): add missing crate docs on RMSE identity contracts --- .../tests/component_rmse_permutation_identity_contract.rs | 5 +++++ .../tests/component_rmse_stable_between_identity_contract.rs | 5 +++++ .../tests/component_rmse_truth_permutation_contract.rs | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/crates/longitudinal_core/tests/component_rmse_permutation_identity_contract.rs b/crates/longitudinal_core/tests/component_rmse_permutation_identity_contract.rs index f278ee6f3..c12dd64b6 100644 --- a/crates/longitudinal_core/tests/component_rmse_permutation_identity_contract.rs +++ b/crates/longitudinal_core/tests/component_rmse_permutation_identity_contract.rs @@ -1,3 +1,8 @@ +//! Known-truth component RMSE is invariant to recovered-row transport order. +//! +//! Recovered components are matched by scientific identity rather than slice +//! position, so a transport permutation cannot change admission or RMSE. + use longitudinal_core::{ComponentLevel, ComponentValue, component_root_mean_square_error}; #[test] diff --git a/crates/longitudinal_core/tests/component_rmse_stable_between_identity_contract.rs b/crates/longitudinal_core/tests/component_rmse_stable_between_identity_contract.rs index c91cdb1f0..cdb44e7a2 100644 --- a/crates/longitudinal_core/tests/component_rmse_stable_between_identity_contract.rs +++ b/crates/longitudinal_core/tests/component_rmse_stable_between_identity_contract.rs @@ -1,3 +1,8 @@ +//! Stable Between components keep a canonical occasion identity of zero. +//! +//! Longitudinal decomposition emits one Between component per unit. An occasion +//! alias must not become a second stable target or an implicit RMSE weight. + use longitudinal_core::{ ComponentLevel, ComponentValue, LongitudinalError, component_root_mean_square_error, }; diff --git a/crates/longitudinal_core/tests/component_rmse_truth_permutation_contract.rs b/crates/longitudinal_core/tests/component_rmse_truth_permutation_contract.rs index c28e3aa15..b9150da95 100644 --- a/crates/longitudinal_core/tests/component_rmse_truth_permutation_contract.rs +++ b/crates/longitudinal_core/tests/component_rmse_truth_permutation_contract.rs @@ -1,3 +1,8 @@ +//! Known-truth component RMSE is invariant to truth-row serialization order. +//! +//! Residuals accumulate in canonical scientific identity order, so permuting +//! the truth slice cannot change the deterministic binary64 recovery metric. + use longitudinal_core::{ComponentLevel, ComponentValue, component_root_mean_square_error}; #[test] From 0bb139514b900280095cda3b6f339b24c984fb0a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 10:05:16 +0900 Subject: [PATCH 214/296] fix(longitudinal_core): bit-identity underflow assert in CWC residual contract --- .../longitudinal_core/tests/cwc_irregular_residual_contract.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/longitudinal_core/tests/cwc_irregular_residual_contract.rs b/crates/longitudinal_core/tests/cwc_irregular_residual_contract.rs index 7dc76e796..4cf657cc8 100644 --- a/crates/longitudinal_core/tests/cwc_irregular_residual_contract.rs +++ b/crates/longitudinal_core/tests/cwc_irregular_residual_contract.rs @@ -32,7 +32,7 @@ fn centered_log_rate_survives_nonrepresentable_intermediate_ratios() { assert_eq!(overflow_recovered.to_bits(), overflow_expected.to_bits()); let underflow_pair = LaggedWithinResidual::new(f64::MAX, f64::MIN_POSITIVE, interval); - assert_eq!(f64::MIN_POSITIVE / f64::MAX, 0.0); + assert_eq!((f64::MIN_POSITIVE / f64::MAX).to_bits(), 0.0_f64.to_bits()); let underflow_expected = f64::MIN_POSITIVE.ln() - f64::MAX.ln(); let underflow_recovered = recover_centered_irregular_residual_log_rate(&[underflow_pair]) .expect("finite log-domain rate despite underflowing direct ratio"); From f20209e3cba9d3be118743e5d401b68b8c0901c6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 10:07:40 +0900 Subject: [PATCH 215/296] fix(longitudinal_core): resolve lib-test float_cmp and hex literal clippy --- crates/longitudinal_core/src/decompose.rs | 6 +++--- crates/longitudinal_core/src/diffusion.rs | 4 ++-- crates/longitudinal_core/src/irregular_residual.rs | 2 +- crates/longitudinal_core/src/stationary.rs | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/longitudinal_core/src/decompose.rs b/crates/longitudinal_core/src/decompose.rs index 5394a406a..f4fe2969c 100644 --- a/crates/longitudinal_core/src/decompose.rs +++ b/crates/longitudinal_core/src/decompose.rs @@ -205,9 +205,9 @@ mod tests { .expect("representable unit mean must not fail on an overflowing partial sum"); assert_eq!(recovered[0].level(), ComponentLevel::Between); - assert_eq!(recovered[0].value(), f64::MAX); - assert_eq!(recovered[1].value(), 0.0); - assert_eq!(recovered[2].value(), 0.0); + assert_eq!(recovered[0].value().to_bits(), f64::MAX.to_bits()); + assert_eq!(recovered[1].value().to_bits(), 0.0_f64.to_bits()); + assert_eq!(recovered[2].value().to_bits(), 0.0_f64.to_bits()); } #[test] diff --git a/crates/longitudinal_core/src/diffusion.rs b/crates/longitudinal_core/src/diffusion.rs index b086f5da3..416421983 100644 --- a/crates/longitudinal_core/src/diffusion.rs +++ b/crates/longitudinal_core/src/diffusion.rs @@ -205,12 +205,12 @@ mod tests { let continuous_underflow = recover_event_time_standardised_continuous_diffusion(minimum_subnormal, -1.0) .expect("q/p cancels a positive real stationary variance below binary64 range"); - assert_eq!(continuous_underflow, 2.0); + assert_eq!(continuous_underflow.to_bits(), 2.0_f64.to_bits()); let continuous_overflow = recover_event_time_standardised_continuous_diffusion(f64::MAX, -0.25) .expect("q/p cancels a positive real stationary variance above binary64 range"); - assert_eq!(continuous_overflow, 0.5); + assert_eq!(continuous_overflow.to_bits(), 0.5_f64.to_bits()); let interval = EventTimeInterval::new(1.0).expect("unit event interval"); let discrete_underflow = diff --git a/crates/longitudinal_core/src/irregular_residual.rs b/crates/longitudinal_core/src/irregular_residual.rs index 46a977477..a77bfbbfc 100644 --- a/crates/longitudinal_core/src/irregular_residual.rs +++ b/crates/longitudinal_core/src/irregular_residual.rs @@ -635,7 +635,7 @@ mod tests { assert!(same_sign.is_finite()); assert!((same_sign - large).abs() < 1.0); let negative = scaled_compensated_mean(&[-4.0, -2.0]).expect("negative same-sign mean"); - assert_eq!(negative, -3.0); + assert_eq!(negative.to_bits(), (-3.0_f64).to_bits()); let mixed = scaled_compensated_mean(&[large, -large]).expect("mixed mean"); assert!(mixed.abs() < 1.0); diff --git a/crates/longitudinal_core/src/stationary.rs b/crates/longitudinal_core/src/stationary.rs index 24bc82d71..9600bbd50 100644 --- a/crates/longitudinal_core/src/stationary.rs +++ b/crates/longitudinal_core/src/stationary.rs @@ -89,7 +89,7 @@ mod tests { fn avoids_doubling_overflow_when_final_stationary_variance_is_representable() { let stationary = recover_stationary_within_variance(f64::MAX, -f64::MAX) .expect("q / (-2a) remains representable"); - assert_eq!(stationary, 0.5); + assert_eq!(stationary.to_bits(), 0.5_f64.to_bits()); } #[test] @@ -108,8 +108,8 @@ mod tests { // The exact q / (-2a) rounds to the minimum positive subnormal. The // predecessor fallback rounded once during division and again during // its final quarter-scale, returning two subnormal ulps instead. - let diffusion = f64::from_bits(0x3cdad6b3492a639e); - let log_rate = -f64::from_bits(0x7fe2342c95642bec); + let diffusion = f64::from_bits(0x3cda_d6b3_492a_639e); + let log_rate = -f64::from_bits(0x7fe2_342c_9564_2bec); let stationary = recover_stationary_within_variance(diffusion, log_rate) .expect("the final stationary variance is representable"); assert_eq!(stationary.to_bits(), 1); From 2bb16260e4a6c2482acf66f4af6eda435f666bf6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 01:15:13 +0000 Subject: [PATCH 216/296] fix(ci): fleet unknown-command stderr + longitudinal overflow fixtures Wire argparse parse failures to the caller-provided stderr stream so the fleet unknown-command contract can observe `invalid choice` without relying on process stderr. Re-export irregular-rate facade via braced `pub use` for the shadow-authority fitness test. Replace subnormal CWC overflow fixtures with representable unit leftovers so false-zero mean fail-closed does not abort before compensated log-rate recovery. --- .../src/irregular_residual.rs | 19 +++++++++++++------ .../src/stable_irregular_rate.rs | 14 +++++++------- scripts/actions_workflow_fleet.py | 8 +++++++- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/crates/longitudinal_core/src/irregular_residual.rs b/crates/longitudinal_core/src/irregular_residual.rs index a77bfbbfc..b8120631c 100644 --- a/crates/longitudinal_core/src/irregular_residual.rs +++ b/crates/longitudinal_core/src/irregular_residual.rs @@ -668,11 +668,15 @@ mod tests { } fn overflowing_cwc_rate_rows(unit: u32, growing: bool) -> [EventTimedObservation; 3] { + // Use a representable nonzero leftover after MAX/-MAX cancellation so the + // unit mean does not underflow to a false zero (ae5081 fail-closed). A + // unit score of 1.0 still yields an astronomical same-sign log-rate once + // divided by the 1e-305 event interval, so pairwise rate sums still overflow. let delta = 1e-305_f64; let (first, second) = if growing { - (f64::from_bits(1), f64::MAX) + (1.0, f64::MAX) } else { - (f64::MAX, f64::from_bits(1)) + (f64::MAX, 1.0) }; [ timed(unit, 0.0, first), @@ -932,17 +936,20 @@ mod tests { #[test] fn overflowing_same_sign_cwc_pairs_keep_stable_log() { + // Representable unit mean: after MAX/-MAX cancel, leftover 1.0 yields mean + // 1/3 and same-sign CWC residuals (2/3, MAX). Subnormal leftovers underflow + // that mean to a false zero and fail closed before rate recovery. let overflowed_both = recover_within_unit_irregular_residual_log_rate(&[ - timed(1, 0.0, f64::from_bits(1)), + timed(1, 0.0, 1.0), timed(1, 1.0, f64::MAX), timed(1, 2.0, -f64::MAX), - timed(2, 0.0, f64::from_bits(1)), + timed(2, 0.0, 1.0), timed(2, 1.0, f64::MAX), timed(2, 2.0, -f64::MAX), ]) .expect("stable log of overflowed same-sign CWC pairs"); - let overflow_rate = driver_same_sign_log_rate(f64::from_bits(1), f64::MAX, unit_interval()) - .expect("tiny/MAX"); + let overflow_rate = driver_same_sign_log_rate(2.0 / 3.0, f64::MAX, unit_interval()) + .expect("two-thirds/MAX after representable CWC mean"); assert!((overflowed_both - overflow_rate).abs() < 1e-9); } diff --git a/crates/longitudinal_core/src/stable_irregular_rate.rs b/crates/longitudinal_core/src/stable_irregular_rate.rs index 3350ae8f2..83685b024 100644 --- a/crates/longitudinal_core/src/stable_irregular_rate.rs +++ b/crates/longitudinal_core/src/stable_irregular_rate.rs @@ -5,11 +5,11 @@ //! module is only the crate-public facade and must not carry a second //! floating-point implementation. -/// Mean exact scalar log-rate on already-centered residuals with stable count weighting. +/// Crate-public irregular residual log-rate entry points. /// -/// Re-exports [`crate::irregular_residual::recover_centered_irregular_residual_log_rate`]. -pub use crate::irregular_residual::recover_centered_irregular_residual_log_rate; -/// Pairwise-mean exact log-rate after CWC with stable count weighting. -/// -/// Re-exports [`crate::irregular_residual::recover_within_unit_irregular_residual_log_rate`]. -pub use crate::irregular_residual::recover_within_unit_irregular_residual_log_rate; +/// Re-exports [`crate::irregular_residual::recover_centered_irregular_residual_log_rate`] +/// and [`crate::irregular_residual::recover_within_unit_irregular_residual_log_rate`]. +pub use crate::irregular_residual::{ + recover_centered_irregular_residual_log_rate, + recover_within_unit_irregular_residual_log_rate, +}; diff --git a/scripts/actions_workflow_fleet.py b/scripts/actions_workflow_fleet.py index e5dfb8c97..62b7576d5 100644 --- a/scripts/actions_workflow_fleet.py +++ b/scripts/actions_workflow_fleet.py @@ -9,6 +9,7 @@ from __future__ import annotations import argparse +import contextlib import http.client import json import os @@ -575,7 +576,12 @@ def main( err = stderr if stderr is not None else sys.stderr env = dict(os.environ) if environ is None else environ try: - parsed = parser.parse_args(list(arguments if arguments is not None else sys.argv[1:])) + # Argparse writes usage/errors to sys.stderr; bind that to the caller-provided + # stream so unknown-command and other parse failures are observable in tests. + with contextlib.redirect_stderr(err): + parsed = parser.parse_args( + list(arguments if arguments is not None else sys.argv[1:]) + ) transport = build_transport(env) audit = audit_repository(transport, parsed.owner, parsed.repo) if parsed.command == "audit": From b0f33bd70a9783d10ee043621819bc2ea84c928d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 01:17:24 +0000 Subject: [PATCH 217/296] fix(longitudinal_core): rustfmt braced irregular-rate facade re-export --- crates/longitudinal_core/src/stable_irregular_rate.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/longitudinal_core/src/stable_irregular_rate.rs b/crates/longitudinal_core/src/stable_irregular_rate.rs index 83685b024..c35161cfd 100644 --- a/crates/longitudinal_core/src/stable_irregular_rate.rs +++ b/crates/longitudinal_core/src/stable_irregular_rate.rs @@ -10,6 +10,5 @@ /// Re-exports [`crate::irregular_residual::recover_centered_irregular_residual_log_rate`] /// and [`crate::irregular_residual::recover_within_unit_irregular_residual_log_rate`]. pub use crate::irregular_residual::{ - recover_centered_irregular_residual_log_rate, - recover_within_unit_irregular_residual_log_rate, + recover_centered_irregular_residual_log_rate, recover_within_unit_irregular_residual_log_rate, }; From afc24dd48c5148de6f57c582c63db41eb1a76ed2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 01:21:04 +0000 Subject: [PATCH 218/296] fix(longitudinal_core): keep overflowed pairwise rate-sum fixture extreme Use 1e-100 leftover after MAX/-MAX cancel so the unit mean stays representable while ln(MAX/r)/1e-305 still exceeds MAX/2 and two rates overflow when added. --- crates/longitudinal_core/src/irregular_residual.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/longitudinal_core/src/irregular_residual.rs b/crates/longitudinal_core/src/irregular_residual.rs index b8120631c..82fc4454e 100644 --- a/crates/longitudinal_core/src/irregular_residual.rs +++ b/crates/longitudinal_core/src/irregular_residual.rs @@ -668,15 +668,16 @@ mod tests { } fn overflowing_cwc_rate_rows(unit: u32, growing: bool) -> [EventTimedObservation; 3] { - // Use a representable nonzero leftover after MAX/-MAX cancellation so the - // unit mean does not underflow to a false zero (ae5081 fail-closed). A - // unit score of 1.0 still yields an astronomical same-sign log-rate once - // divided by the 1e-305 event interval, so pairwise rate sums still overflow. + // Representable leftover after MAX/-MAX cancellation (ae5081 false-zero + // mean fail-closed). 1e-100 keeps the unit mean nonzero while ln(MAX/r)/Δt + // with Δt=1e-305 still exceeds MAX/2 so two pairwise rates overflow when + // added, exercising compensated-mean recovery of the overflowed sum. let delta = 1e-305_f64; + let leftover = 1e-100_f64; let (first, second) = if growing { - (1.0, f64::MAX) + (leftover, f64::MAX) } else { - (f64::MAX, 1.0) + (f64::MAX, leftover) }; [ timed(unit, 0.0, first), From 7f576b7dfe672d57ec08d56ed6b2de53d3cd14d1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 01:42:29 +0000 Subject: [PATCH 219/296] fix(longitudinal_core): close authored line/branch coverage gaps Exercise singleton-unit skip, continued exact cancel, zero-magnitude mean, require_finite refusal, duplicate decided RMSE identity, zero-ratio scaled square, overflowing within/occasion residuals, doubled-interval diffusion refusal, and underflowed stationary variance after 2a overflow. Remove unreachable empty-pairs guards implied by the two lag-unit admission rule. --- crates/longitudinal_core/src/component.rs | 28 +++++++- crates/longitudinal_core/src/decompose.rs | 15 ++++ crates/longitudinal_core/src/diffusion.rs | 10 +++ .../src/irregular_residual.rs | 47 ++++++++++-- crates/longitudinal_core/src/occasion_mean.rs | 71 ++++++++++++++++++- crates/longitudinal_core/src/stationary.rs | 9 +++ 6 files changed, 171 insertions(+), 9 deletions(-) diff --git a/crates/longitudinal_core/src/component.rs b/crates/longitudinal_core/src/component.rs index 90a52731a..d3db4f754 100644 --- a/crates/longitudinal_core/src/component.rs +++ b/crates/longitudinal_core/src/component.rs @@ -188,7 +188,7 @@ pub fn component_root_mean_square_error( #[cfg(test)] mod tests { - use super::{ComponentValue, component_root_mean_square_error}; + use super::{ComponentValue, add_scaled_square, component_root_mean_square_error}; use crate::{ComponentLevel, LongitudinalError}; #[test] @@ -302,4 +302,30 @@ mod tests { 3 ); } + + #[test] + fn duplicate_decided_identity_fails_closed() { + let truth = [ + ComponentValue::new(0, 0, ComponentLevel::Between, 0.5), + ComponentValue::new(1, 0, ComponentLevel::Between, 0.25), + ]; + let decided = [ + ComponentValue::new(0, 0, ComponentLevel::Between, 0.5), + ComponentValue::new(0, 0, ComponentLevel::Between, 0.4), + ]; + assert_eq!( + component_root_mean_square_error(&truth, &decided), + Err(LongitudinalError::InvalidComponentPayload) + ); + } + + #[test] + fn zero_ratio_scaled_square_is_a_no_op() { + let mut scale = 2.0_f64; + let mut sum = 3.0_f64; + add_scaled_square(&mut scale, &mut sum, 4.0, 0.0); + assert_eq!(scale.to_bits(), 2.0_f64.to_bits()); + assert_eq!(sum.to_bits(), 3.0_f64.to_bits()); + } } + diff --git a/crates/longitudinal_core/src/decompose.rs b/crates/longitudinal_core/src/decompose.rs index f4fe2969c..6ddaab593 100644 --- a/crates/longitudinal_core/src/decompose.rs +++ b/crates/longitudinal_core/src/decompose.rs @@ -225,4 +225,19 @@ mod tests { assert_eq!(recovered[0].level(), ComponentLevel::Between); assert_eq!(recovered[0].value().to_bits(), minimum_subnormal.to_bits()); } + + #[test] + fn overflowing_within_residual_after_finite_mean_fails_closed() { + // Mean remains finite while MAX - (-MAX/3)-class residuals overflow. + let rows = [ + OccasionObservation::new(0, 0, f64::MAX), + OccasionObservation::new(0, 1, f64::MAX), + OccasionObservation::new(0, 2, -f64::MAX), + ]; + assert_eq!( + decompose_within_between(&rows), + Err(LongitudinalError::InvalidObservationPayload) + ); + } } + diff --git a/crates/longitudinal_core/src/diffusion.rs b/crates/longitudinal_core/src/diffusion.rs index 416421983..f71f8fd6b 100644 --- a/crates/longitudinal_core/src/diffusion.rs +++ b/crates/longitudinal_core/src/diffusion.rs @@ -233,4 +233,14 @@ mod tests { Err(LongitudinalError::InvalidTemporalTransformInput) ); } + + #[test] + fn doubled_interval_overflow_rejects_nonfinite_half_exponent() { + let huge = EventTimeInterval::new(f64::MAX).expect("finite positive interval"); + assert_eq!( + recover_event_time_standardised_discrete_diffusion(1.0, -f64::MAX, huge), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); + } } + diff --git a/crates/longitudinal_core/src/irregular_residual.rs b/crates/longitudinal_core/src/irregular_residual.rs index 82fc4454e..9921a6b9a 100644 --- a/crates/longitudinal_core/src/irregular_residual.rs +++ b/crates/longitudinal_core/src/irregular_residual.rs @@ -172,9 +172,8 @@ pub fn center_within_unit_event_lags( )); } } - if pairs.is_empty() { - return Err(LongitudinalError::InvalidObservationPayload); - } + // Two lag-contributing units each yield at least one validated window, so + // the pair list cannot be empty once the admission checks above succeed. Ok(pairs) } @@ -479,8 +478,8 @@ mod tests { EventTimedObservation, LaggedWithinResidual, center_within_unit_event_lags, driver_same_sign_log_rate, recover_centered_irregular_residual_log_rate, recover_within_unit_irregular_residual_log_rate, - refuse_cwc_residual_log_rate_as_raw_process_drift, same_sign_nonzero, - scaled_compensated_mean, + refuse_cwc_residual_log_rate_as_raw_process_drift, require_finite, same_sign_nonzero, + same_sign_mean_over_total, scaled_compensated_mean, }; use crate::{EventTimeInterval, LongitudinalError}; @@ -978,4 +977,42 @@ mod tests { Err(LongitudinalError::InvalidObservationPayload) ); } + + #[test] + fn singleton_unit_is_skipped_once_two_lag_units_admit() { + let rows = [ + timed(1, 0.0, 1.0), + timed(1, 1.0, 0.5), + timed(2, 0.0, 2.0), + timed(2, 1.0, 1.0), + timed(3, 0.0, 9.0), + ]; + let pairs = center_within_unit_event_lags(&rows).expect("singleton skipped"); + assert_eq!(pairs.len(), 2); + } + + #[test] + fn continued_exact_cancel_keeps_remaining_same_sign_terms() { + let mean = scaled_compensated_mean(&[3.0, 1.0, -3.0, -1.0]).expect("continued cancel"); + assert_eq!(mean.to_bits(), 0.0_f64.to_bits()); + } + + #[test] + fn same_sign_mean_over_total_accepts_all_zero_magnitudes() { + assert_eq!(same_sign_mean_over_total(&[0.0, 0.0], 2), Ok(0.0)); + } + + #[test] + fn require_finite_rejects_non_finite_values() { + assert_eq!( + require_finite(f64::INFINITY), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); + assert_eq!( + require_finite(f64::NAN), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); + assert_eq!(require_finite(1.5), Ok(1.5)); + } } + diff --git a/crates/longitudinal_core/src/occasion_mean.rs b/crates/longitudinal_core/src/occasion_mean.rs index 3cad3a62f..02a2550ea 100644 --- a/crates/longitudinal_core/src/occasion_mean.rs +++ b/crates/longitudinal_core/src/occasion_mean.rs @@ -111,9 +111,8 @@ pub fn center_occasion_mean_event_lags( } } - if pairs.is_empty() { - return Err(LongitudinalError::InvalidObservationPayload); - } + // Two lag-contributing units each yield at least one validated window, so + // the pair list cannot be empty once occasion means and intervals validate. Ok(pairs) } @@ -159,3 +158,69 @@ fn canonical_event_time_key(event_time: f64) -> u64 { fn occasion_mean(values: &[f64]) -> Result { scaled_compensated_mean(values).map_err(|_| LongitudinalError::InvalidObservationPayload) } + +#[cfg(test)] +mod tests { + use super::{ + center_occasion_mean_event_lags, recover_occasion_mean_centered_irregular_residual_log_rate, + }; + use crate::irregular_residual::EventTimedObservation; + use crate::LongitudinalError; + + fn timed(unit: u32, event_time: f64, score: f64) -> EventTimedObservation { + EventTimedObservation::new(unit, event_time, score) + } + + #[test] + fn fewer_than_two_lag_units_fail_closed() { + assert_eq!( + center_occasion_mean_event_lags(&[ + timed(1, 0.0, 1.0), + timed(1, 1.0, 0.5), + timed(2, 0.0, 2.0), + ]), + Err(LongitudinalError::InvalidObservationPayload) + ); + } + + #[test] + fn singleton_unit_is_skipped_when_two_lag_units_admit() { + // Units 1 and 2 contribute lags; unit 3 appears only once at a shared time so + // occasion means still have >=2 units while the singleton is skipped in the + // per-unit lag loop. + let pairs = center_occasion_mean_event_lags(&[ + timed(1, 0.0, 1.0), + timed(1, 1.0, 0.5), + timed(2, 0.0, 2.0), + timed(2, 1.0, 1.0), + timed(3, 0.0, 9.0), + ]) + .expect("singleton unit skipped"); + assert_eq!(pairs.len(), 2); + } + + #[test] + fn overflowing_occasion_mean_residual_fails_closed() { + assert_eq!( + center_occasion_mean_event_lags(&[ + timed(1, 0.0, -f64::MAX), + timed(1, 1.0, -f64::MAX), + timed(2, 0.0, -f64::MAX), + timed(2, 1.0, f64::MAX), + ]), + Err(LongitudinalError::InvalidObservationPayload) + ); + } + + #[test] + fn happy_path_recovers_finite_log_rate() { + let rate = recover_occasion_mean_centered_irregular_residual_log_rate(&[ + timed(1, 0.0, 1.0), + timed(1, 1.0, 0.5), + timed(2, 0.0, 2.0), + timed(2, 1.0, 1.0), + ]) + .expect("occasion-mean rate"); + assert!(rate.is_finite()); + } +} diff --git a/crates/longitudinal_core/src/stationary.rs b/crates/longitudinal_core/src/stationary.rs index 9600bbd50..caca91c3f 100644 --- a/crates/longitudinal_core/src/stationary.rs +++ b/crates/longitudinal_core/src/stationary.rs @@ -136,4 +136,13 @@ mod tests { Err(LongitudinalError::InvalidTemporalTransformInput) ); } + + #[test] + fn overflowed_twice_rate_rejects_underflowed_stationary_variance() { + assert_eq!( + recover_stationary_within_variance(f64::from_bits(1), -f64::MAX), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); + } } + From 337cfd7aacbf1c7d97177a5fc1d7608fef121fd3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 01:46:47 +0000 Subject: [PATCH 220/296] fix(longitudinal_core): rustfmt coverage-closing tests --- crates/longitudinal_core/src/component.rs | 3 +-- crates/longitudinal_core/src/decompose.rs | 3 +-- crates/longitudinal_core/src/diffusion.rs | 3 +-- .../src/irregular_residual.rs | 26 ++++++++----------- crates/longitudinal_core/src/occasion_mean.rs | 2 +- crates/longitudinal_core/src/stationary.rs | 1 - 6 files changed, 15 insertions(+), 23 deletions(-) diff --git a/crates/longitudinal_core/src/component.rs b/crates/longitudinal_core/src/component.rs index d3db4f754..305546ed2 100644 --- a/crates/longitudinal_core/src/component.rs +++ b/crates/longitudinal_core/src/component.rs @@ -188,7 +188,7 @@ pub fn component_root_mean_square_error( #[cfg(test)] mod tests { - use super::{ComponentValue, add_scaled_square, component_root_mean_square_error}; + use super::{add_scaled_square, component_root_mean_square_error, ComponentValue}; use crate::{ComponentLevel, LongitudinalError}; #[test] @@ -328,4 +328,3 @@ mod tests { assert_eq!(sum.to_bits(), 3.0_f64.to_bits()); } } - diff --git a/crates/longitudinal_core/src/decompose.rs b/crates/longitudinal_core/src/decompose.rs index 6ddaab593..caa3edda2 100644 --- a/crates/longitudinal_core/src/decompose.rs +++ b/crates/longitudinal_core/src/decompose.rs @@ -127,7 +127,7 @@ pub fn decompose_within_between( #[cfg(test)] mod tests { - use super::{OccasionObservation, decompose_within_between}; + use super::{decompose_within_between, OccasionObservation}; use crate::{ComponentLevel, LongitudinalError}; #[test] @@ -240,4 +240,3 @@ mod tests { ); } } - diff --git a/crates/longitudinal_core/src/diffusion.rs b/crates/longitudinal_core/src/diffusion.rs index f71f8fd6b..199463061 100644 --- a/crates/longitudinal_core/src/diffusion.rs +++ b/crates/longitudinal_core/src/diffusion.rs @@ -9,7 +9,7 @@ //! research-candidate extensions, not canonical ctsem output and not a DSEM or //! ctsem estimator. -use crate::{EventTimeInterval, LongitudinalError, stationary::validate_stationary_process_inputs}; +use crate::{stationary::validate_stationary_process_inputs, EventTimeInterval, LongitudinalError}; /// Recover the scalar research-candidate `DIFFUSIONstd = q / p` map. /// @@ -243,4 +243,3 @@ mod tests { ); } } - diff --git a/crates/longitudinal_core/src/irregular_residual.rs b/crates/longitudinal_core/src/irregular_residual.rs index 9921a6b9a..79c38147f 100644 --- a/crates/longitudinal_core/src/irregular_residual.rs +++ b/crates/longitudinal_core/src/irregular_residual.rs @@ -475,11 +475,12 @@ fn require_finite(value: f64) -> Result { #[cfg(test)] mod tests { use super::{ - EventTimedObservation, LaggedWithinResidual, center_within_unit_event_lags, - driver_same_sign_log_rate, recover_centered_irregular_residual_log_rate, + center_within_unit_event_lags, driver_same_sign_log_rate, + recover_centered_irregular_residual_log_rate, recover_within_unit_irregular_residual_log_rate, - refuse_cwc_residual_log_rate_as_raw_process_drift, require_finite, same_sign_nonzero, - same_sign_mean_over_total, scaled_compensated_mean, + refuse_cwc_residual_log_rate_as_raw_process_drift, require_finite, + same_sign_mean_over_total, same_sign_nonzero, scaled_compensated_mean, + EventTimedObservation, LaggedWithinResidual, }; use crate::{EventTimeInterval, LongitudinalError}; @@ -755,16 +756,12 @@ mod tests { timed(2, 2.0, -0.8), ]) .expect("extract"); - assert!( - extracted - .iter() - .any(|pair| pair.later_residual().to_bits() == 0.0_f64.to_bits()) - ); - assert!( - extracted - .iter() - .any(|pair| pair.earlier_residual().to_bits() == 0.0_f64.to_bits()) - ); + assert!(extracted + .iter() + .any(|pair| pair.later_residual().to_bits() == 0.0_f64.to_bits())); + assert!(extracted + .iter() + .any(|pair| pair.earlier_residual().to_bits() == 0.0_f64.to_bits())); assert!(extracted.iter().any(|pair| same_sign_nonzero( pair.earlier_residual(), pair.later_residual() @@ -1015,4 +1012,3 @@ mod tests { assert_eq!(require_finite(1.5), Ok(1.5)); } } - diff --git a/crates/longitudinal_core/src/occasion_mean.rs b/crates/longitudinal_core/src/occasion_mean.rs index 02a2550ea..8f9be45c0 100644 --- a/crates/longitudinal_core/src/occasion_mean.rs +++ b/crates/longitudinal_core/src/occasion_mean.rs @@ -9,7 +9,7 @@ use std::collections::{BTreeMap, BTreeSet}; use crate::irregular_residual::{ - EventTimedObservation, LaggedWithinResidual, scaled_compensated_mean, + scaled_compensated_mean, EventTimedObservation, LaggedWithinResidual, }; use crate::stable_irregular_rate::recover_centered_irregular_residual_log_rate; use crate::{EventTimeInterval, LongitudinalError}; diff --git a/crates/longitudinal_core/src/stationary.rs b/crates/longitudinal_core/src/stationary.rs index caca91c3f..22becb445 100644 --- a/crates/longitudinal_core/src/stationary.rs +++ b/crates/longitudinal_core/src/stationary.rs @@ -145,4 +145,3 @@ mod tests { ); } } - From 41b964e11dfa71dd4ca13900ac764e0fe874e793 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 01:54:28 +0000 Subject: [PATCH 221/296] fix(longitudinal_core): match CI rustfmt import/assert style --- crates/longitudinal_core/src/component.rs | 2 +- crates/longitudinal_core/src/decompose.rs | 2 +- crates/longitudinal_core/src/diffusion.rs | 2 +- .../src/irregular_residual.rs | 21 +++++++++++-------- crates/longitudinal_core/src/occasion_mean.rs | 4 ++-- 5 files changed, 17 insertions(+), 14 deletions(-) diff --git a/crates/longitudinal_core/src/component.rs b/crates/longitudinal_core/src/component.rs index 305546ed2..193689174 100644 --- a/crates/longitudinal_core/src/component.rs +++ b/crates/longitudinal_core/src/component.rs @@ -188,7 +188,7 @@ pub fn component_root_mean_square_error( #[cfg(test)] mod tests { - use super::{add_scaled_square, component_root_mean_square_error, ComponentValue}; + use super::{ComponentValue, add_scaled_square, component_root_mean_square_error}; use crate::{ComponentLevel, LongitudinalError}; #[test] diff --git a/crates/longitudinal_core/src/decompose.rs b/crates/longitudinal_core/src/decompose.rs index caa3edda2..20faf693f 100644 --- a/crates/longitudinal_core/src/decompose.rs +++ b/crates/longitudinal_core/src/decompose.rs @@ -127,7 +127,7 @@ pub fn decompose_within_between( #[cfg(test)] mod tests { - use super::{decompose_within_between, OccasionObservation}; + use super::{OccasionObservation, decompose_within_between}; use crate::{ComponentLevel, LongitudinalError}; #[test] diff --git a/crates/longitudinal_core/src/diffusion.rs b/crates/longitudinal_core/src/diffusion.rs index 199463061..ffc6c9c83 100644 --- a/crates/longitudinal_core/src/diffusion.rs +++ b/crates/longitudinal_core/src/diffusion.rs @@ -9,7 +9,7 @@ //! research-candidate extensions, not canonical ctsem output and not a DSEM or //! ctsem estimator. -use crate::{stationary::validate_stationary_process_inputs, EventTimeInterval, LongitudinalError}; +use crate::{EventTimeInterval, LongitudinalError, stationary::validate_stationary_process_inputs}; /// Recover the scalar research-candidate `DIFFUSIONstd = q / p` map. /// diff --git a/crates/longitudinal_core/src/irregular_residual.rs b/crates/longitudinal_core/src/irregular_residual.rs index 79c38147f..51728f562 100644 --- a/crates/longitudinal_core/src/irregular_residual.rs +++ b/crates/longitudinal_core/src/irregular_residual.rs @@ -475,12 +475,11 @@ fn require_finite(value: f64) -> Result { #[cfg(test)] mod tests { use super::{ - center_within_unit_event_lags, driver_same_sign_log_rate, - recover_centered_irregular_residual_log_rate, + EventTimedObservation, LaggedWithinResidual, center_within_unit_event_lags, + driver_same_sign_log_rate, recover_centered_irregular_residual_log_rate, recover_within_unit_irregular_residual_log_rate, refuse_cwc_residual_log_rate_as_raw_process_drift, require_finite, same_sign_mean_over_total, same_sign_nonzero, scaled_compensated_mean, - EventTimedObservation, LaggedWithinResidual, }; use crate::{EventTimeInterval, LongitudinalError}; @@ -756,12 +755,16 @@ mod tests { timed(2, 2.0, -0.8), ]) .expect("extract"); - assert!(extracted - .iter() - .any(|pair| pair.later_residual().to_bits() == 0.0_f64.to_bits())); - assert!(extracted - .iter() - .any(|pair| pair.earlier_residual().to_bits() == 0.0_f64.to_bits())); + assert!( + extracted + .iter() + .any(|pair| pair.later_residual().to_bits() == 0.0_f64.to_bits()) + ); + assert!( + extracted + .iter() + .any(|pair| pair.earlier_residual().to_bits() == 0.0_f64.to_bits()) + ); assert!(extracted.iter().any(|pair| same_sign_nonzero( pair.earlier_residual(), pair.later_residual() diff --git a/crates/longitudinal_core/src/occasion_mean.rs b/crates/longitudinal_core/src/occasion_mean.rs index 8f9be45c0..2ab40e5a4 100644 --- a/crates/longitudinal_core/src/occasion_mean.rs +++ b/crates/longitudinal_core/src/occasion_mean.rs @@ -9,7 +9,7 @@ use std::collections::{BTreeMap, BTreeSet}; use crate::irregular_residual::{ - scaled_compensated_mean, EventTimedObservation, LaggedWithinResidual, + EventTimedObservation, LaggedWithinResidual, scaled_compensated_mean, }; use crate::stable_irregular_rate::recover_centered_irregular_residual_log_rate; use crate::{EventTimeInterval, LongitudinalError}; @@ -164,8 +164,8 @@ mod tests { use super::{ center_occasion_mean_event_lags, recover_occasion_mean_centered_irregular_residual_log_rate, }; - use crate::irregular_residual::EventTimedObservation; use crate::LongitudinalError; + use crate::irregular_residual::EventTimedObservation; fn timed(unit: u32, event_time: f64, score: f64) -> EventTimedObservation { EventTimedObservation::new(unit, event_time, score) From f034cb149364382c7af5b8861f36d01d0b59d147 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 01:59:14 +0000 Subject: [PATCH 222/296] fix(longitudinal_core): make occasion-mean residual overflow fixture actually overflow --- crates/longitudinal_core/src/occasion_mean.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/crates/longitudinal_core/src/occasion_mean.rs b/crates/longitudinal_core/src/occasion_mean.rs index 2ab40e5a4..acdaf406f 100644 --- a/crates/longitudinal_core/src/occasion_mean.rs +++ b/crates/longitudinal_core/src/occasion_mean.rs @@ -201,12 +201,15 @@ mod tests { #[test] fn overflowing_occasion_mean_residual_fails_closed() { + // Three units at t=1 yield occasion mean -MAX/3; MAX - (-MAX/3) overflows. assert_eq!( center_occasion_mean_event_lags(&[ - timed(1, 0.0, -f64::MAX), - timed(1, 1.0, -f64::MAX), - timed(2, 0.0, -f64::MAX), - timed(2, 1.0, f64::MAX), + timed(1, 0.0, 0.0), + timed(1, 1.0, f64::MAX), + timed(2, 0.0, 0.0), + timed(2, 1.0, -f64::MAX), + timed(3, 0.0, 0.0), + timed(3, 1.0, -f64::MAX), ]), Err(LongitudinalError::InvalidObservationPayload) ); From 023df78b7d8684bdbbfffce680d0eee71dd12e39 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 02:23:32 +0000 Subject: [PATCH 223/296] fix(longitudinal_core): cover remaining decompose overflow and non-finite arms Admit two units so within-residual overflow is reachable, and exercise non-finite event time/score plus earlier occasion-mean overflow and underflowed discrete exponent. --- crates/longitudinal_core/src/decompose.rs | 4 +- crates/longitudinal_core/src/diffusion.rs | 9 +++++ .../src/irregular_residual.rs | 22 +++++++++++ crates/longitudinal_core/src/occasion_mean.rs | 37 +++++++++++++++++++ 4 files changed, 71 insertions(+), 1 deletion(-) diff --git a/crates/longitudinal_core/src/decompose.rs b/crates/longitudinal_core/src/decompose.rs index 20faf693f..b3c020b8b 100644 --- a/crates/longitudinal_core/src/decompose.rs +++ b/crates/longitudinal_core/src/decompose.rs @@ -228,11 +228,13 @@ mod tests { #[test] fn overflowing_within_residual_after_finite_mean_fails_closed() { - // Mean remains finite while MAX - (-MAX/3)-class residuals overflow. + // Need >=2 units for admission. Unit 0 mean is MAX/3; -MAX - MAX/3 overflows. let rows = [ OccasionObservation::new(0, 0, f64::MAX), OccasionObservation::new(0, 1, f64::MAX), OccasionObservation::new(0, 2, -f64::MAX), + OccasionObservation::new(1, 0, 1.0), + OccasionObservation::new(1, 1, 0.5), ]; assert_eq!( decompose_within_between(&rows), diff --git a/crates/longitudinal_core/src/diffusion.rs b/crates/longitudinal_core/src/diffusion.rs index ffc6c9c83..d3439f0d2 100644 --- a/crates/longitudinal_core/src/diffusion.rs +++ b/crates/longitudinal_core/src/diffusion.rs @@ -242,4 +242,13 @@ mod tests { Err(LongitudinalError::InvalidTemporalTransformInput) ); } + + #[test] + fn underflowed_exact_exponent_fails_closed() { + let tiny = EventTimeInterval::new(1e-300).expect("tiny interval"); + assert_eq!( + recover_event_time_standardised_discrete_diffusion(1.0, -1e-300, tiny), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); + } } diff --git a/crates/longitudinal_core/src/irregular_residual.rs b/crates/longitudinal_core/src/irregular_residual.rs index 51728f562..f0688c7b3 100644 --- a/crates/longitudinal_core/src/irregular_residual.rs +++ b/crates/longitudinal_core/src/irregular_residual.rs @@ -1014,4 +1014,26 @@ mod tests { ); assert_eq!(require_finite(1.5), Ok(1.5)); } + + #[test] + fn non_finite_event_time_or_score_fails_closed() { + assert_eq!( + center_within_unit_event_lags(&[ + timed(1, f64::NAN, 1.0), + timed(1, 1.0, 0.5), + timed(2, 0.0, 2.0), + timed(2, 1.0, 1.0), + ]), + Err(LongitudinalError::InvalidObservationPayload) + ); + assert_eq!( + center_within_unit_event_lags(&[ + timed(1, 0.0, f64::INFINITY), + timed(1, 1.0, 0.5), + timed(2, 0.0, 2.0), + timed(2, 1.0, 1.0), + ]), + Err(LongitudinalError::InvalidObservationPayload) + ); + } } diff --git a/crates/longitudinal_core/src/occasion_mean.rs b/crates/longitudinal_core/src/occasion_mean.rs index acdaf406f..ab206dcc6 100644 --- a/crates/longitudinal_core/src/occasion_mean.rs +++ b/crates/longitudinal_core/src/occasion_mean.rs @@ -226,4 +226,41 @@ mod tests { .expect("occasion-mean rate"); assert!(rate.is_finite()); } + + #[test] + fn non_finite_event_time_or_score_fails_closed() { + assert_eq!( + center_occasion_mean_event_lags(&[ + timed(1, f64::NAN, 1.0), + timed(1, 1.0, 0.5), + timed(2, 0.0, 2.0), + timed(2, 1.0, 1.0), + ]), + Err(LongitudinalError::InvalidObservationPayload) + ); + assert_eq!( + center_occasion_mean_event_lags(&[ + timed(1, 0.0, f64::INFINITY), + timed(1, 1.0, 0.5), + timed(2, 0.0, 2.0), + timed(2, 1.0, 1.0), + ]), + Err(LongitudinalError::InvalidObservationPayload) + ); + } + + #[test] + fn overflowing_earlier_occasion_mean_residual_fails_closed() { + assert_eq!( + center_occasion_mean_event_lags(&[ + timed(1, 0.0, f64::MAX), + timed(1, 1.0, 0.0), + timed(2, 0.0, -f64::MAX), + timed(2, 1.0, 0.0), + timed(3, 0.0, -f64::MAX), + timed(3, 1.0, 0.0), + ]), + Err(LongitudinalError::InvalidObservationPayload) + ); + } } From b83b155bf712b6ea298bc2bc1c52d9611962f3d8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 02:47:05 +0000 Subject: [PATCH 224/296] fix(longitudinal_core): close final diffusion/stationary branch arms Stable negative drift cannot produce a non-positive or non-finite -exp_m1 ratio, so keep only the unit-endpoint refusal. Add MAX/q-min overflow coverage for stationary variance materialization. --- crates/longitudinal_core/src/diffusion.rs | 8 ++++---- crates/longitudinal_core/src/stationary.rs | 9 +++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/crates/longitudinal_core/src/diffusion.rs b/crates/longitudinal_core/src/diffusion.rs index d3439f0d2..59de0c07d 100644 --- a/crates/longitudinal_core/src/diffusion.rs +++ b/crates/longitudinal_core/src/diffusion.rs @@ -109,10 +109,10 @@ pub fn recover_event_time_standardised_discrete_diffusion( } let ratio = -exponent.exp_m1(); - // The exact finite-interval ratio is strictly inside (0, 1). If exp_m1 - // rounds it to either endpoint, reporting zero/one would erase a nonzero - // remainder and turn a numerical limitation into a scientific boundary. - if !ratio.is_finite() || ratio <= 0.0 || ratio >= 1.0 { + // Stable a < 0 with positive Δ yields a strictly negative exponent, so + // -exp_m1 is in (0, 1] when finite. The only false scientific boundary that + // remains is rounding up to the exact unit endpoint. + if ratio >= 1.0 { return Err(LongitudinalError::InvalidTemporalTransformInput); } Ok(ratio) diff --git a/crates/longitudinal_core/src/stationary.rs b/crates/longitudinal_core/src/stationary.rs index 22becb445..78b797eb8 100644 --- a/crates/longitudinal_core/src/stationary.rs +++ b/crates/longitudinal_core/src/stationary.rs @@ -144,4 +144,13 @@ mod tests { Err(LongitudinalError::InvalidTemporalTransformInput) ); } + + #[test] + fn overflowed_stationary_variance_fails_closed() { + assert_eq!( + recover_stationary_within_variance(f64::MAX, -f64::from_bits(1)), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); + } } + From ecbe7aa7ace9094e7631d59d942869594c9d7d00 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 8 Sep 2026 02:51:38 +0000 Subject: [PATCH 225/296] fix(longitudinal_core): rustfmt trailing newline on stationary tests --- crates/longitudinal_core/src/stationary.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/longitudinal_core/src/stationary.rs b/crates/longitudinal_core/src/stationary.rs index 78b797eb8..027cc2e78 100644 --- a/crates/longitudinal_core/src/stationary.rs +++ b/crates/longitudinal_core/src/stationary.rs @@ -153,4 +153,3 @@ mod tests { ); } } - From 50d139cf89af27b5f8881c10eb2e4e8330bbf71a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 11 Sep 2026 20:09:10 +0900 Subject: [PATCH 226/296] test(longitudinal): expose repeated mixed-sign rounding loss --- .../mixed_sign_mean_rounding_contract.rs | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/crates/longitudinal_core/tests/mixed_sign_mean_rounding_contract.rs b/crates/longitudinal_core/tests/mixed_sign_mean_rounding_contract.rs index 138838ee8..19e65f81e 100644 --- a/crates/longitudinal_core/tests/mixed_sign_mean_rounding_contract.rs +++ b/crates/longitudinal_core/tests/mixed_sign_mean_rounding_contract.rs @@ -1,13 +1,22 @@ //! Mixed-sign CWC and occasion means divide by the original sample count. use longitudinal_core::{ - EventTimedObservation, center_occasion_mean_event_lags, center_within_unit_event_lags, + EventTimeInterval, EventTimedObservation, LaggedWithinResidual, center_occasion_mean_event_lags, + center_within_unit_event_lags, recover_centered_irregular_residual_log_rate, }; fn timed(unit: u32, event_time: f64, score: f64) -> EventTimedObservation { EventTimedObservation::new(unit, event_time, score) } +fn lagged(earlier: f64, later: f64, interval: f64) -> LaggedWithinResidual { + LaggedWithinResidual::new( + earlier, + later, + EventTimeInterval::new(interval).expect("positive event interval"), + ) +} + #[test] fn mixed_sign_subnormal_cwc_mean_rounds_once_at_the_final_denominator() { let ulp = f64::from_bits(1); @@ -72,3 +81,23 @@ fn mixed_sign_subnormal_occasion_mean_uses_the_same_single_rounding_authority() f64::from_bits(19).to_bits() ); } + +#[test] +fn repeated_small_opposite_rates_change_the_correctly_rounded_mean() { + let log_two = -(-0.5_f64).ln_1p(); + let pairs = [ + lagged(1.0, 2.0, log_two / 1.0e16_f64), + lagged(1.0, 0.5, log_two), + lagged(1.0, 0.5, log_two), + ]; + + let recovered = recover_centered_irregular_residual_log_rate(&pairs) + .expect("finite mixed-sign mean remains identifiable"); + let expected = 3_333_333_333_333_332.5_f64; + + assert_eq!( + recovered.to_bits(), + expected.to_bits(), + "two -1 rates are jointly significant and must not be rounded away one at a time against 1e16", + ); +} From 4c3b9551bb31cc482004e522174c73a09d880956 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 11 Sep 2026 20:11:55 +0900 Subject: [PATCH 227/296] style(longitudinal): format mixed-sign RED fixture --- .../tests/mixed_sign_mean_rounding_contract.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/longitudinal_core/tests/mixed_sign_mean_rounding_contract.rs b/crates/longitudinal_core/tests/mixed_sign_mean_rounding_contract.rs index 19e65f81e..2045da80a 100644 --- a/crates/longitudinal_core/tests/mixed_sign_mean_rounding_contract.rs +++ b/crates/longitudinal_core/tests/mixed_sign_mean_rounding_contract.rs @@ -1,8 +1,9 @@ //! Mixed-sign CWC and occasion means divide by the original sample count. use longitudinal_core::{ - EventTimeInterval, EventTimedObservation, LaggedWithinResidual, center_occasion_mean_event_lags, - center_within_unit_event_lags, recover_centered_irregular_residual_log_rate, + EventTimeInterval, EventTimedObservation, LaggedWithinResidual, + center_occasion_mean_event_lags, center_within_unit_event_lags, + recover_centered_irregular_residual_log_rate, }; fn timed(unit: u32, event_time: f64, score: f64) -> EventTimedObservation { From 98a0d20b53f7dd1f1178975dee85904fc9fd56cb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 11 Sep 2026 20:19:22 +0900 Subject: [PATCH 228/296] fix(longitudinal): retain collectively significant mixed-sign mass --- .../src/irregular_residual.rs | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/crates/longitudinal_core/src/irregular_residual.rs b/crates/longitudinal_core/src/irregular_residual.rs index f0688c7b3..79f61646e 100644 --- a/crates/longitudinal_core/src/irregular_residual.rs +++ b/crates/longitudinal_core/src/irregular_residual.rs @@ -292,14 +292,17 @@ fn pairwise_same_sign_log_rate(lagged: &[LaggedWithinResidual]) -> Result Result { if values.is_empty() { return Err(LongitudinalError::InvalidTemporalTransformInput); @@ -337,6 +340,16 @@ pub(crate) fn scaled_compensated_mean(values: &[f64]) -> Result 0.0 { positive = residual; negative_index += 1; From 864730a7753b51307c17bf7d53ca676bcd383c4a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 11 Sep 2026 20:19:56 +0900 Subject: [PATCH 229/296] test(longitudinal): cover mirrored mixed-sign retention --- .../mixed_sign_mean_rounding_contract.rs | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/crates/longitudinal_core/tests/mixed_sign_mean_rounding_contract.rs b/crates/longitudinal_core/tests/mixed_sign_mean_rounding_contract.rs index 2045da80a..3486ef691 100644 --- a/crates/longitudinal_core/tests/mixed_sign_mean_rounding_contract.rs +++ b/crates/longitudinal_core/tests/mixed_sign_mean_rounding_contract.rs @@ -86,19 +86,31 @@ fn mixed_sign_subnormal_occasion_mean_uses_the_same_single_rounding_authority() #[test] fn repeated_small_opposite_rates_change_the_correctly_rounded_mean() { let log_two = -(-0.5_f64).ln_1p(); - let pairs = [ + let expected = 3_333_333_333_333_332.5_f64; + + let positive_dominant = [ lagged(1.0, 2.0, log_two / 1.0e16_f64), lagged(1.0, 0.5, log_two), lagged(1.0, 0.5, log_two), ]; - - let recovered = recover_centered_irregular_residual_log_rate(&pairs) - .expect("finite mixed-sign mean remains identifiable"); - let expected = 3_333_333_333_333_332.5_f64; - + let recovered = recover_centered_irregular_residual_log_rate(&positive_dominant) + .expect("finite positive-dominant mixed-sign mean remains identifiable"); assert_eq!( recovered.to_bits(), expected.to_bits(), "two -1 rates are jointly significant and must not be rounded away one at a time against 1e16", ); + + let mirrored_pairs = [ + lagged(1.0, 0.5, log_two / 1.0e16_f64), + lagged(1.0, 2.0, log_two), + lagged(1.0, 2.0, log_two), + ]; + let mirrored = recover_centered_irregular_residual_log_rate(&mirrored_pairs) + .expect("finite mirrored mixed-sign mean remains identifiable"); + assert_eq!( + mirrored.to_bits(), + (-expected).to_bits(), + "two +1 rates are jointly significant and must not be rounded away one at a time against the opposite extreme", + ); } From c1103b63c7cc521ec65afceb78647e432143c0ab Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 11 Sep 2026 20:35:41 +0900 Subject: [PATCH 230/296] test(longitudinal): expose half-ulp swallowed tail --- .../mixed_sign_mean_rounding_contract.rs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/crates/longitudinal_core/tests/mixed_sign_mean_rounding_contract.rs b/crates/longitudinal_core/tests/mixed_sign_mean_rounding_contract.rs index 3486ef691..512955d4c 100644 --- a/crates/longitudinal_core/tests/mixed_sign_mean_rounding_contract.rs +++ b/crates/longitudinal_core/tests/mixed_sign_mean_rounding_contract.rs @@ -114,3 +114,25 @@ fn repeated_small_opposite_rates_change_the_correctly_rounded_mean() { "two +1 rates are jointly significant and must not be rounded away one at a time against the opposite extreme", ); } + +#[test] +fn half_ulp_tail_changes_the_final_mixed_sign_rounding() { + let log_two = -(-0.5_f64).ln_1p(); + let dominant_rate = f64::from_bits(0x4698_0000_0000_0000); + let half_ulp_rate = f64::from_bits(0x4340_0000_0000_0000); + let expected = f64::from_bits(0x467f_ffff_ffff_ffff); + + let pairs = [ + lagged(1.0, 2.0, log_two / dominant_rate), + lagged(1.0, 0.5, log_two / half_ulp_rate), + lagged(1.0, 0.5, log_two), + ]; + let recovered = recover_centered_irregular_residual_log_rate(&pairs) + .expect("finite mixed-sign mean remains identifiable"); + + assert_eq!( + recovered.to_bits(), + expected.to_bits(), + "the -1 tail sits below the -2^53 accumulator ULP but moves the final cancellation below the dominant half-ULP tie", + ); +} From f1d02508c7b92aaaedd7e6a08083707d4ed90094 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 11 Sep 2026 20:39:13 +0900 Subject: [PATCH 231/296] fix(longitudinal): make absorption checks representation-exact --- crates/longitudinal_core/src/irregular_residual.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/longitudinal_core/src/irregular_residual.rs b/crates/longitudinal_core/src/irregular_residual.rs index 79f61646e..8714ce1ee 100644 --- a/crates/longitudinal_core/src/irregular_residual.rs +++ b/crates/longitudinal_core/src/irregular_residual.rs @@ -340,12 +340,12 @@ pub(crate) fn scaled_compensated_mean(values: &[f64]) -> Result Date: Fri, 11 Sep 2026 21:07:58 +0900 Subject: [PATCH 232/296] docs(longitudinal): trace binary64 mean owner handoff --- ...ongitudinal-binary64-mean-owner-handoff.md | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 docs/research/longitudinal-binary64-mean-owner-handoff.md diff --git a/docs/research/longitudinal-binary64-mean-owner-handoff.md b/docs/research/longitudinal-binary64-mean-owner-handoff.md new file mode 100644 index 000000000..1c55392a5 --- /dev/null +++ b/docs/research/longitudinal-binary64-mean-owner-handoff.md @@ -0,0 +1,77 @@ +# Longitudinal binary64 mean owner handoff + +## Decision status + +**Proposed consumer boundary.** This record does not activate a new numerical algorithm. It records why TEPP Longitudinal Modeling must not repair the current mixed-sign mean defect with another local summation heuristic and what evidence is required before replacing it with a released reusable numerical contract. + +## Consumer finding + +TEPP PR #310 exposes a compact public RED through `crates/longitudinal_core/tests/mixed_sign_mean_rounding_contract.rs`. + +The represented rate values are: + +- `P = 0x1.8p+106` (`0x4698000000000000`), +- `-2^53`, +- `-1`. + +The exact real sum is `P - 2^53 - 1`. The correctly rounded mean is `0x1.fffffffffffffp+104` (`0x467fffffffffffff`). The current `scaled_compensated_mean` path in `crates/longitudinal_core/src/irregular_residual.rs` returns `0x4680000000000000`, one ULP high, because the same-side coalescing step can lose the final `-1` before the original-count division. + +This is an ordinary finite three-value input constructed through the public already-centered residual path. It is not a resource-extreme synthetic witness. + +## Bounded-context ownership + +TEPP owns the temporal estimand: event-time admission, Driver et al. log-rate construction, CWC semantics, longitudinal evidence composition, and the decision to publish or refuse a result. + +Reusable finite binary64 sum/mean arithmetic is static numerical infrastructure and belongs to `ContextualWisdomLab/fast-mlsirm`. Canonical owner gap `fast-mlsirm#1814` therefore owns an exact or correctly rounded finite-sum/mean Published Language. TEPP may consume only an immutable released contract. Mutable owner PR heads, copied source, cross-repository SQL, or a second TEPP numerical implementation are not accepted dependencies. + +Open fast-mlsirm PR #1536 contains relevant prior art at head `03d1e943b9f51d509977b63889a2052eae81717b`: private `add_fsum_partial` / `finish_fsum` helpers in `crates/mlsirm-core/src/multilevel_estimator.rs`, introduced by `5b856206e15ecc31236a4109571f1be58e77c54a`. Those helpers implement CPython-style partial accumulation and final half-even correction for membership-weight admission. They are design evidence only. The current caller is domain-private, uses non-negative totals expected near one, returns a rounded sum rather than a correctly rounded original-count mean, does not expose mathematical-zero versus nonzero-underflow identity, and does not establish an associative worker-reduction contract. + +The current immutable fast-mlsirm release `v0.9.1` predates `fast-mlsirm#1814`; it is not authority for this repair. + +## Candidate exact accumulator bound + +A fixed-width exact accumulator is feasible without introducing a psychometric sample ceiling when the numerical API accepts an ordinary Rust slice and supported production targets have `usize <= 64` bits. + +Every finite binary64 value is an integer multiple of `q = 2^-1074`. The largest finite value is + +`MAX = (2^53 - 1) * 2^971`. + +In `q` units one addend therefore has magnitude + +`I_max = (2^53 - 1) * 2^2045`, + +which requires 2,098 magnitude bits. For every 64-bit slice cardinality `n <= 2^64 - 1`, + +`n * I_max < 2^2162`. + +A sign plus 2,162-bit magnitude is therefore sufficient for the exact mathematical sum over every slice representable by a 64-bit `usize`. A 34×`u64` magnitude provides 2,176 bits. This is a candidate representation invariant, not accepted implementation evidence. + +The mean must be rounded from the exact rational `(S / n) * 2^-1074`, not from a binary64-rounded sum. Integer quotient/remainder finalization can distinguish exact mathematical zero (`S == 0`) from a nonzero value that rounds below binary64 range and can apply round-to-nearest-ties-to-even at the final mean boundary. It also admits same-sign cases where the exact sum exceeds binary64 while the mean remains representable. + +The owner must compare this representation against a generalized error-free-transform/partials construction and record the selected invariant, complexity, deterministic reduction strategy, and rejection reasons. + +## Rejected local repairs + +The following are not causal acceptance: + +- adding another swallowed-term or pair-order special case to `scaled_compensated_mean`; +- replacing the current path with plain Kahan or Neumaier summation without a final-rounding proof; +- pre-scaling all inputs by a large magnitude when cancellation can make a subnormal term scientifically material; +- computing a rounded sum first and then dividing by the sample count; +- adding a model-specific sample ceiling solely to simplify a numerical proof; +- copying the private fast-mlsirm #1536 helpers into TEPP; +- accepting faithful or approximate behavior while documenting it as correctly rounded. + +## Release and consumer acceptance + +Before TEPP changes production arithmetic, the fast-mlsirm owner contract must be protected-merged and published in an immutable versioned release with SBOM, provenance, reproducibility, rollback evidence, rustdoc, test and edge-case coverage. Its RED/GREEN suite must include at least the TEPP half-ULP case, mirrored sign, `[1e16, -1, -1]`, `[MAX, tiny, -MAX]`, subnormal residue, exact cancellation, same-sign near-MAX finite means, exact midpoint/ties-to-even division, mathematical zero versus nonzero-underflow, input permutation, and worker-count determinism. + +TEPP then pins the released version through the repository's approved dependency/ACL boundary, removes the local generic mean heuristic rather than retaining two numerical authorities, reruns the public longitudinal RED to GREEN, and reacquires exact-head Rust, 100% owned line/branch coverage, security, SBOM/provenance, review, and release evidence. LLM review is supplementary and cannot activate the numerical change. + +## Research basis + +Ogita, T., Rump, S. M., & Oishi, S. (2005). Accurate sum and dot product. *SIAM Journal on Scientific Computing, 26*(6), 1955–1988. https://doi.org/10.1137/030601818 + +Rump, S. M., Ogita, T., & Oishi, S. (2008). Accurate floating-point summation part I: Faithful rounding. *SIAM Journal on Scientific Computing, 31*(1), 189–224. https://doi.org/10.1137/050645671 + +These publications motivate error-free transformation and faithful summation analysis. They do not prove a future fast-mlsirm implementation or authorize TEPP production activation by themselves. From 75c9ae516ed36ffb4ef6b0ebd2adea099e230348 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 11 Sep 2026 22:29:10 +0900 Subject: [PATCH 233/296] docs(longitudinal): track released-mean owner implementation --- .../longitudinal-binary64-mean-owner-handoff.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/docs/research/longitudinal-binary64-mean-owner-handoff.md b/docs/research/longitudinal-binary64-mean-owner-handoff.md index 1c55392a5..58a217734 100644 --- a/docs/research/longitudinal-binary64-mean-owner-handoff.md +++ b/docs/research/longitudinal-binary64-mean-owner-handoff.md @@ -28,6 +28,14 @@ Open fast-mlsirm PR #1536 contains relevant prior art at head `03d1e943b9f51d509 The current immutable fast-mlsirm release `v0.9.1` predates `fast-mlsirm#1814`; it is not authority for this repair. +## Canonical owner implementation now in review + +`fast-mlsirm#1816` is now the canonical implementation vehicle for #1814. At the time of this record the exact owner head is `b5fec1d33c02e8ea969405e59747f0505e690d20` on protected base `493326f2de49ea1704da0ded19868ed05d2fe00f`. It publishes the proposed contract identity `fast_mlsirm.binary64_mean@1.0.0` and implements separate signed 34×`u64` exact magnitudes in `2^-1074` units, original-count integer quotient/remainder finalization, and a single round-to-nearest-ties-to-even projection. + +That is a material owner-path advance, not a TEPP dependency yet. The PR is open and Ready only for check/review admission. Its repository CI, package path, native CodeQL, Semgrep, Security Scan and ClusterFuzzLite evidence are GREEN on the cited head, while the organization delegated CodeQL receipt remains RED through the known producer/consumer settlement path. The owner also identified that central `coverage-evidence` can be GREEN without producing owned Rust line/branch measurement; `.github#2099` owns that missing immutable coverage evidence. No qualifying independent submitted review is currently present, and the latest CodeRabbit review attempts were rate-limited; review prose must not be promoted to merge authority unless retrievable exact-head evidence exists. + +TEPP therefore keeps the public longitudinal RED unchanged. Consumer migration begins only after #1816 lands through the normal protected path and a new immutable fast-mlsirm release exposes the contract with version/tag/package, SBOM/provenance, reproducibility, rollback and real owned source coverage evidence. A mutable PR SHA is not an allowed production dependency even if its source-level arithmetic appears correct. + ## Candidate exact accumulator bound A fixed-width exact accumulator is feasible without introducing a psychometric sample ceiling when the numerical API accepts an ordinary Rust slice and supported production targets have `usize <= 64` bits. @@ -44,12 +52,10 @@ which requires 2,098 magnitude bits. For every 64-bit slice cardinality `n <= 2^ `n * I_max < 2^2162`. -A sign plus 2,162-bit magnitude is therefore sufficient for the exact mathematical sum over every slice representable by a 64-bit `usize`. A 34×`u64` magnitude provides 2,176 bits. This is a candidate representation invariant, not accepted implementation evidence. +A sign plus 2,162-bit magnitude is therefore sufficient for the exact mathematical sum over every slice representable by a 64-bit `usize`. A 34×`u64` magnitude provides 2,176 bits. This representation is implemented by the current #1816 candidate but remains **Proposed** for TEPP until protected integration and immutable release acceptance complete. The mean must be rounded from the exact rational `(S / n) * 2^-1074`, not from a binary64-rounded sum. Integer quotient/remainder finalization can distinguish exact mathematical zero (`S == 0`) from a nonzero value that rounds below binary64 range and can apply round-to-nearest-ties-to-even at the final mean boundary. It also admits same-sign cases where the exact sum exceeds binary64 while the mean remains representable. -The owner must compare this representation against a generalized error-free-transform/partials construction and record the selected invariant, complexity, deterministic reduction strategy, and rejection reasons. - ## Rejected local repairs The following are not causal acceptance: @@ -59,7 +65,8 @@ The following are not causal acceptance: - pre-scaling all inputs by a large magnitude when cancellation can make a subnormal term scientifically material; - computing a rounded sum first and then dividing by the sample count; - adding a model-specific sample ceiling solely to simplify a numerical proof; -- copying the private fast-mlsirm #1536 helpers into TEPP; +- copying the private fast-mlsirm #1536 helpers or #1816 implementation into TEPP; +- pinning a mutable fast-mlsirm PR/head as production authority; - accepting faithful or approximate behavior while documenting it as correctly rounded. ## Release and consumer acceptance @@ -74,4 +81,4 @@ Ogita, T., Rump, S. M., & Oishi, S. (2005). Accurate sum and dot product. *SIAM Rump, S. M., Ogita, T., & Oishi, S. (2008). Accurate floating-point summation part I: Faithful rounding. *SIAM Journal on Scientific Computing, 31*(1), 189–224. https://doi.org/10.1137/050645671 -These publications motivate error-free transformation and faithful summation analysis. They do not prove a future fast-mlsirm implementation or authorize TEPP production activation by themselves. +These publications motivate error-free transformation and faithful summation analysis. They do not prove the fast-mlsirm implementation or authorize TEPP production activation by themselves. From 957f3ec04e93cef544f0ef9ed693412d6f6bce7e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 11 Sep 2026 22:32:27 +0900 Subject: [PATCH 234/296] docs(longitudinal): avoid mutable owner readiness authority --- docs/research/longitudinal-binary64-mean-owner-handoff.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/research/longitudinal-binary64-mean-owner-handoff.md b/docs/research/longitudinal-binary64-mean-owner-handoff.md index 58a217734..e035a27ce 100644 --- a/docs/research/longitudinal-binary64-mean-owner-handoff.md +++ b/docs/research/longitudinal-binary64-mean-owner-handoff.md @@ -32,7 +32,7 @@ The current immutable fast-mlsirm release `v0.9.1` predates `fast-mlsirm#1814`; `fast-mlsirm#1816` is now the canonical implementation vehicle for #1814. At the time of this record the exact owner head is `b5fec1d33c02e8ea969405e59747f0505e690d20` on protected base `493326f2de49ea1704da0ded19868ed05d2fe00f`. It publishes the proposed contract identity `fast_mlsirm.binary64_mean@1.0.0` and implements separate signed 34×`u64` exact magnitudes in `2^-1074` units, original-count integer quotient/remainder finalization, and a single round-to-nearest-ties-to-even projection. -That is a material owner-path advance, not a TEPP dependency yet. The PR is open and Ready only for check/review admission. Its repository CI, package path, native CodeQL, Semgrep, Security Scan and ClusterFuzzLite evidence are GREEN on the cited head, while the organization delegated CodeQL receipt remains RED through the known producer/consumer settlement path. The owner also identified that central `coverage-evidence` can be GREEN without producing owned Rust line/branch measurement; `.github#2099` owns that missing immutable coverage evidence. No qualifying independent submitted review is currently present, and the latest CodeRabbit review attempts were rate-limited; review prose must not be promoted to merge authority unless retrievable exact-head evidence exists. +That is a material owner-path advance, not a TEPP dependency yet. The owner PR remains open and mergeable, while its Draft/Ready presentation state is intentionally **not** consumer authority: during this review cycle it moved from Ready-for-check admission back to Draft without changing the cited source head. Repository CI, package, native CodeQL, Semgrep, Security Scan and ClusterFuzzLite evidence are GREEN on that head, while the organization delegated CodeQL receipt remains RED through the known producer/consumer settlement path. The owner also identified that central `coverage-evidence` can be GREEN without producing owned Rust line/branch measurement; `.github#2099` owns that missing immutable coverage evidence. No qualifying independent submitted review is currently present. The previously rate-limited CodeRabbit review was retried and is now processing the same exact head; an in-progress bot review is not a clean finding or merge authority. TEPP therefore keeps the public longitudinal RED unchanged. Consumer migration begins only after #1816 lands through the normal protected path and a new immutable fast-mlsirm release exposes the contract with version/tag/package, SBOM/provenance, reproducibility, rollback and real owned source coverage evidence. A mutable PR SHA is not an allowed production dependency even if its source-level arithmetic appears correct. From d19446c3b917bef11fbc0b1d8eb2fe521d14f34f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 11 Sep 2026 22:34:39 +0900 Subject: [PATCH 235/296] docs(longitudinal): record owner test-only evidence advance --- docs/research/longitudinal-binary64-mean-owner-handoff.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/research/longitudinal-binary64-mean-owner-handoff.md b/docs/research/longitudinal-binary64-mean-owner-handoff.md index e035a27ce..50eace259 100644 --- a/docs/research/longitudinal-binary64-mean-owner-handoff.md +++ b/docs/research/longitudinal-binary64-mean-owner-handoff.md @@ -30,9 +30,11 @@ The current immutable fast-mlsirm release `v0.9.1` predates `fast-mlsirm#1814`; ## Canonical owner implementation now in review -`fast-mlsirm#1816` is now the canonical implementation vehicle for #1814. At the time of this record the exact owner head is `b5fec1d33c02e8ea969405e59747f0505e690d20` on protected base `493326f2de49ea1704da0ded19868ed05d2fe00f`. It publishes the proposed contract identity `fast_mlsirm.binary64_mean@1.0.0` and implements separate signed 34×`u64` exact magnitudes in `2^-1074` units, original-count integer quotient/remainder finalization, and a single round-to-nearest-ties-to-even projection. +`fast-mlsirm#1816` is the canonical implementation vehicle for #1814. The numerical implementation reviewed by this TEPP decision entered at owner commit `b5fec1d33c02e8ea969405e59747f0505e690d20` on protected base `493326f2de49ea1704da0ded19868ed05d2fe00f`. It publishes the proposed contract identity `fast_mlsirm.binary64_mean@1.0.0` and implements separate signed 34×`u64` exact magnitudes in `2^-1074` units, original-count integer quotient/remainder finalization, and a single round-to-nearest-ties-to-even projection. -That is a material owner-path advance, not a TEPP dependency yet. The owner PR remains open and mergeable, while its Draft/Ready presentation state is intentionally **not** consumer authority: during this review cycle it moved from Ready-for-check admission back to Draft without changing the cited source head. Repository CI, package, native CodeQL, Semgrep, Security Scan and ClusterFuzzLite evidence are GREEN on that head, while the organization delegated CodeQL receipt remains RED through the known producer/consumer settlement path. The owner also identified that central `coverage-evidence` can be GREEN without producing owned Rust line/branch measurement; `.github#2099` owns that missing immutable coverage evidence. No qualifying independent submitted review is currently present. The previously rate-limited CodeRabbit review was retried and is now processing the same exact head; an in-progress bot review is not a clean finding or merge authority. +The owner PR later advanced without changing that production implementation: commit `087df1dae7bb7c177880d6758104e29edfe91cde` adds only contract-test evidence—an exact integer/rational oracle over 10,000 deterministic subnormal cases, `[f64::MAX, 1e-16, -f64::MAX]` tiny-cancellation coverage, and explicit negative-infinity refusal. Because the PR was Draft after this test-only push, its repository CI and ClusterFuzzLite workflows were skipped on `087df1d...`; native Semgrep, Security Scan and CodeQL completed GREEN while delegated CodeQL remained in progress at the observation point. Therefore predecessor Rust GREEN at `b5fec1d...` does not transfer to the new test head. + +This snapshot is traceability evidence, not a dependency pin. The owner PR's mutable exact head, Draft/Ready presentation and bot-review state may continue to change; TEPP does not treat any of them as consumer authority. The earlier `b5fec1d...` repository CI/package/native CodeQL/Semgrep/Security/ClusterFuzz evidence is useful predecessor evidence only. `.github#2099` separately owns the finding that central `coverage-evidence` can report GREEN without actually producing owned Rust line/branch measurement. No qualifying independent submitted review was present in the fresh owner sweep. A CodeRabbit retry moved from rate-limited to in-progress review on the owner stack, but in-progress or generated review prose cannot authorize merge or release. TEPP therefore keeps the public longitudinal RED unchanged. Consumer migration begins only after #1816 lands through the normal protected path and a new immutable fast-mlsirm release exposes the contract with version/tag/package, SBOM/provenance, reproducibility, rollback and real owned source coverage evidence. A mutable PR SHA is not an allowed production dependency even if its source-level arithmetic appears correct. @@ -52,7 +54,7 @@ which requires 2,098 magnitude bits. For every 64-bit slice cardinality `n <= 2^ `n * I_max < 2^2162`. -A sign plus 2,162-bit magnitude is therefore sufficient for the exact mathematical sum over every slice representable by a 64-bit `usize`. A 34×`u64` magnitude provides 2,176 bits. This representation is implemented by the current #1816 candidate but remains **Proposed** for TEPP until protected integration and immutable release acceptance complete. +A sign plus 2,162-bit magnitude is therefore sufficient for the exact mathematical sum over every slice representable by a 64-bit `usize`. A 34×`u64` magnitude provides 2,176 bits. This representation is implemented by the #1816 candidate but remains **Proposed** for TEPP until protected integration and immutable release acceptance complete. The mean must be rounded from the exact rational `(S / n) * 2^-1074`, not from a binary64-rounded sum. Integer quotient/remainder finalization can distinguish exact mathematical zero (`S == 0`) from a nonzero value that rounds below binary64 range and can apply round-to-nearest-ties-to-even at the final mean boundary. It also admits same-sign cases where the exact sum exceeds binary64 while the mean remains representable. From ab201efb66c5243d087a9521bfd43d4ebc5c8482 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 11 Sep 2026 23:04:28 +0900 Subject: [PATCH 236/296] docs(longitudinal): refresh binary64 mean owner evidence --- ...ongitudinal-binary64-mean-owner-handoff.md | 85 +++++++++---------- 1 file changed, 41 insertions(+), 44 deletions(-) diff --git a/docs/research/longitudinal-binary64-mean-owner-handoff.md b/docs/research/longitudinal-binary64-mean-owner-handoff.md index 50eace259..cec73b8c2 100644 --- a/docs/research/longitudinal-binary64-mean-owner-handoff.md +++ b/docs/research/longitudinal-binary64-mean-owner-handoff.md @@ -2,85 +2,82 @@ ## Decision status -**Proposed consumer boundary.** This record does not activate a new numerical algorithm. It records why TEPP Longitudinal Modeling must not repair the current mixed-sign mean defect with another local summation heuristic and what evidence is required before replacing it with a released reusable numerical contract. +**Proposed consumer boundary.** This record does not activate a new numerical algorithm. TEPP keeps the current Longitudinal Modeling RED until the reusable numerical owner has protected-integrated and immutably released a contract that satisfies the evidence below. ## Consumer finding TEPP PR #310 exposes a compact public RED through `crates/longitudinal_core/tests/mixed_sign_mean_rounding_contract.rs`. -The represented rate values are: +The represented rate values are `P = 0x1.8p+106` (`0x4698000000000000`), `-2^53`, and `-1`. The exact real sum is `P - 2^53 - 1`. The correctly rounded mean is `0x1.fffffffffffffp+104` (`0x467fffffffffffff`), while TEPP's current `scaled_compensated_mean` path returns `0x4680000000000000`, one ULP high because the same-side coalescing step can lose the final `-1` before the original-count division. -- `P = 0x1.8p+106` (`0x4698000000000000`), -- `-2^53`, -- `-1`. - -The exact real sum is `P - 2^53 - 1`. The correctly rounded mean is `0x1.fffffffffffffp+104` (`0x467fffffffffffff`). The current `scaled_compensated_mean` path in `crates/longitudinal_core/src/irregular_residual.rs` returns `0x4680000000000000`, one ULP high, because the same-side coalescing step can lose the final `-1` before the original-count division. - -This is an ordinary finite three-value input constructed through the public already-centered residual path. It is not a resource-extreme synthetic witness. +This is an ordinary finite three-value input through the public already-centered residual path. It is not a resource-extreme synthetic witness. The latest TEPP source-level RED authority remains Rust Foundation run `34595076660`: formatting and Clippy passed, 1,568/1,569 tests passed, and only `mixed_sign_mean_rounding_contract::half_ulp_tail_changes_the_final_mixed_sign_rounding` failed with the expected bit mismatch. ## Bounded-context ownership -TEPP owns the temporal estimand: event-time admission, Driver et al. log-rate construction, CWC semantics, longitudinal evidence composition, and the decision to publish or refuse a result. - -Reusable finite binary64 sum/mean arithmetic is static numerical infrastructure and belongs to `ContextualWisdomLab/fast-mlsirm`. Canonical owner gap `fast-mlsirm#1814` therefore owns an exact or correctly rounded finite-sum/mean Published Language. TEPP may consume only an immutable released contract. Mutable owner PR heads, copied source, cross-repository SQL, or a second TEPP numerical implementation are not accepted dependencies. - -Open fast-mlsirm PR #1536 contains relevant prior art at head `03d1e943b9f51d509977b63889a2052eae81717b`: private `add_fsum_partial` / `finish_fsum` helpers in `crates/mlsirm-core/src/multilevel_estimator.rs`, introduced by `5b856206e15ecc31236a4109571f1be58e77c54a`. Those helpers implement CPython-style partial accumulation and final half-even correction for membership-weight admission. They are design evidence only. The current caller is domain-private, uses non-negative totals expected near one, returns a rounded sum rather than a correctly rounded original-count mean, does not expose mathematical-zero versus nonzero-underflow identity, and does not establish an associative worker-reduction contract. +TEPP owns the temporal estimand: event-time admission, Driver-style log-rate construction, CWC semantics, longitudinal evidence composition, and the decision to publish or refuse a result. -The current immutable fast-mlsirm release `v0.9.1` predates `fast-mlsirm#1814`; it is not authority for this repair. +Reusable finite binary64 sum/mean arithmetic is domain-neutral numerical infrastructure and belongs to `ContextualWisdomLab/fast-mlsirm`. Canonical owner issue `fast-mlsirm#1814` and implementation PR `fast-mlsirm#1816` own this primitive. TEPP must not copy the owner source, pin a mutable PR head, introduce a second generic summation algorithm, or reinterpret an unreleased owner branch as dependency authority. -## Canonical owner implementation now in review +The immutable fast-mlsirm release remains `v0.9.1`; it predates this contract. -`fast-mlsirm#1816` is the canonical implementation vehicle for #1814. The numerical implementation reviewed by this TEPP decision entered at owner commit `b5fec1d33c02e8ea969405e59747f0505e690d20` on protected base `493326f2de49ea1704da0ded19868ed05d2fe00f`. It publishes the proposed contract identity `fast_mlsirm.binary64_mean@1.0.0` and implements separate signed 34×`u64` exact magnitudes in `2^-1074` units, original-count integer quotient/remainder finalization, and a single round-to-nearest-ties-to-even projection. +## Fresh owner evidence — 2026-09-11 -The owner PR later advanced without changing that production implementation: commit `087df1dae7bb7c177880d6758104e29edfe91cde` adds only contract-test evidence—an exact integer/rational oracle over 10,000 deterministic subnormal cases, `[f64::MAX, 1e-16, -f64::MAX]` tiny-cancellation coverage, and explicit negative-infinity refusal. Because the PR was Draft after this test-only push, its repository CI and ClusterFuzzLite workflows were skipped on `087df1d...`; native Semgrep, Security Scan and CodeQL completed GREEN while delegated CodeQL remained in progress at the observation point. Therefore predecessor Rust GREEN at `b5fec1d...` does not transfer to the new test head. +The live `fast-mlsirm#1816` head is `432765ccf633c9802e0f796ceeb4d6d572059acf` on protected `main@493326f2de49ea1704da0ded19868ed05d2fe00f`. The PR is open and non-Draft, but Ready status is admission only and is not merge or release authority. -This snapshot is traceability evidence, not a dependency pin. The owner PR's mutable exact head, Draft/Ready presentation and bot-review state may continue to change; TEPP does not treat any of them as consumer authority. The earlier `b5fec1d...` repository CI/package/native CodeQL/Semgrep/Security/ClusterFuzz evidence is useful predecessor evidence only. `.github#2099` separately owns the finding that central `coverage-evidence` can report GREEN without actually producing owned Rust line/branch measurement. No qualifying independent submitted review was present in the fresh owner sweep. A CodeRabbit retry moved from rate-limited to in-progress review on the owner stack, but in-progress or generated review prose cannot authorize merge or release. +The numerical implementation entered at `b5fec1d33c02e8ea969405e59747f0505e690d20`. Subsequent ordinary-forward commits added the independent exact oracle and directed cancellation/non-finite fixtures (`087df1d...`), Proposed ADR-0029 (`9a8614f...`), doctoring/changelog currentization, touched-function rustdoc coverage (`f00c31c...`), and finally official ISO/IEEE record links (`432765c...`). The `f00c31c... -> 432765c...` delta changes only `docs/doctoring/correctly-rounded-binary64-mean.md`; the production arithmetic is unchanged. -TEPP therefore keeps the public longitudinal RED unchanged. Consumer migration begins only after #1816 lands through the normal protected path and a new immutable fast-mlsirm release exposes the contract with version/tag/package, SBOM/provenance, reproducibility, rollback and real owned source coverage evidence. A mutable PR SHA is not an allowed production dependency even if its source-level arithmetic appears correct. +Current exact-head hosted evidence on `432765c...` is mixed and therefore non-mergeable: -## Candidate exact accumulator bound +- native CodeQL `34606295285`, Security Scan `34606295363`, SAST Semgrep `34606295323`, and ClusterFuzzLite `34606295428` are GREEN; +- repository CI `34606295259` is RED because `gpu-smoke` job `103285576158` failed while installing the software Vulkan adapter, before Vulkan availability or GPU parity executed. Package and fuzz jobs are GREEN. This is repository acceptance debt even though it is not evidence against the binary64 mean arithmetic; +- required delegated CodeQL PR `34606295286` is RED. Python job `103285476466` and Actions job `103285476530` both successfully read the current-head dispatch verdict and then failed at `Release runner or enforce current-head CodeQL verdict`; only afterward did dispatch job `103286411396` succeed. This remains the central producer/consumer settlement class and must be repaired at the canonical `.github` owner rather than copied into TEPP or fast-mlsirm; +- current formal reviews are COMMENTED only. The CodeRabbit oracle/standards-link findings are resolved, but there is no qualifying submitted current-head `APPROVED` review. -A fixed-width exact accumulator is feasible without introducing a psychometric sample ceiling when the numerical API accepts an ordinary Rust slice and supported production targets have `usize <= 64` bits. +The current owner PR body still names predecessor `f00c31c...` as its exact head, so PR metadata itself is stale relative to GitHub's live head. That is an owner-path documentation finding, not permission for TEPP to rewrite fast-mlsirm source or consume the branch. -Every finite binary64 value is an integer multiple of `q = 2^-1074`. The largest finite value is +## Proposed numerical contract -`MAX = (2^53 - 1) * 2^971`. +The owner candidate publishes the proposed identity `fast_mlsirm.binary64_mean@1.0.0`. Every finite binary64 value is represented exactly as an integer multiple of `q = 2^-1074`. Positive and negative totals are accumulated separately in fixed 34×`u64` magnitudes, the exact signed magnitude is divided by the original slice cardinality, and only the final rational result is projected to binary64 with round-to-nearest, ties-to-even. `exact_zero` distinguishes exact represented cancellation from a nonzero mean that rounds to signed zero. -In `q` units one addend therefore has magnitude +For the largest finite binary64 value, the coefficient in `q` units is `(2^53 - 1) * 2^2045`, requiring 2,098 magnitude bits. On supported `usize::BITS <= 64` targets, any materializable same-sign slice total is `< 2^2162`; 34 `u64` limbs provide 2,176 magnitude bits. The fixed width is therefore a representation bound, not a psychometric sample ceiling. -`I_max = (2^53 - 1) * 2^2045`, +The final mean must be rounded from the exact rational `(S / n) * 2^-1074`, not from a binary64-rounded sum. This also permits same-sign cases whose exact intermediate sum exceeds binary64 while their mean remains representable. -which requires 2,098 magnitude bits. For every 64-bit slice cardinality `n <= 2^64 - 1`, +## Owner acceptance already encoded in tests -`n * I_max < 2^2162`. +The current owner test surface includes the TEPP half-ULP counterexample and mirrored sign, `[1e16, -1, -1]`, `[f64::MAX, 1e-16, -f64::MAX]`, exact cancellation versus nonzero underflow, minimum-subnormal residue after MAX cancellation, subnormal/normal boundaries, normal and subnormal ties-to-even, binade carry, same-sign `f64::MAX`, permutation invariance, and empty/NaN/±infinity refusal. It also carries a deterministic 10,000-case subnormal-domain oracle using independent test-only exact integer/rational arithmetic rather than the production 34-limb accumulator. -A sign plus 2,162-bit magnitude is therefore sufficient for the exact mathematical sum over every slice representable by a 64-bit `usize`. A 34×`u64` magnitude provides 2,176 bits. This representation is implemented by the #1816 candidate but remains **Proposed** for TEPP until protected integration and immutable release acceptance complete. +Those tests are necessary but not sufficient. One unchanged owner head still needs repository Rust/rustdoc/Clippy, actual owned statement and branch/edge coverage, package/fuzz/security/supply-chain evidence, zero valid unresolved findings, qualifying independent review, and terminal central controls. The present GPU provisioning failure and delegated CodeQL failure keep that bar open. -The mean must be rounded from the exact rational `(S / n) * 2^-1074`, not from a binary64-rounded sum. Integer quotient/remainder finalization can distinguish exact mathematical zero (`S == 0`) from a nonzero value that rounds below binary64 range and can apply round-to-nearest-ties-to-even at the final mean boundary. It also admits same-sign cases where the exact sum exceeds binary64 while the mean remains representable. - -## Rejected local repairs +## Rejected TEPP-local repairs The following are not causal acceptance: - adding another swallowed-term or pair-order special case to `scaled_compensated_mean`; -- replacing the current path with plain Kahan or Neumaier summation without a final-rounding proof; -- pre-scaling all inputs by a large magnitude when cancellation can make a subnormal term scientifically material; -- computing a rounded sum first and then dividing by the sample count; -- adding a model-specific sample ceiling solely to simplify a numerical proof; -- copying the private fast-mlsirm #1536 helpers or #1816 implementation into TEPP; -- pinning a mutable fast-mlsirm PR/head as production authority; -- accepting faithful or approximate behavior while documenting it as correctly rounded. +- replacing it with plain Kahan, Neumaier, sorting/coalescing, or pre-scaling without a final-rounding proof; +- computing a rounded floating sum first and dividing by the sample count afterward; +- adding a product-specific sample ceiling to simplify the numerical proof; +- copying fast-mlsirm #1536 private partials or #1816 source into TEPP; +- pinning a mutable owner PR/head; +- describing faithful or approximate behavior as correctly rounded; +- treating native CodeQL/security success as a substitute for the failed repository CI, delegated CodeQL receipt, independent review, or immutable release. ## Release and consumer acceptance -Before TEPP changes production arithmetic, the fast-mlsirm owner contract must be protected-merged and published in an immutable versioned release with SBOM, provenance, reproducibility, rollback evidence, rustdoc, test and edge-case coverage. Its RED/GREEN suite must include at least the TEPP half-ULP case, mirrored sign, `[1e16, -1, -1]`, `[MAX, tiny, -MAX]`, subnormal residue, exact cancellation, same-sign near-MAX finite means, exact midpoint/ties-to-even division, mathematical zero versus nonzero-underflow, input permutation, and worker-count determinism. +Before TEPP changes production arithmetic, `fast-mlsirm#1816` must land through the normal protected path and the contract must be published in a new immutable versioned release with version/tag/package, SBOM/provenance, reproducibility and rollback evidence. ADR-0029 remains Proposed until that acceptance is complete. + +After release, TEPP must pin the released contract through the approved dependency/ACL boundary, remove the local generic mean heuristic rather than retaining two numerical authorities, rerun the public longitudinal RED to GREEN, and reacquire exact-head formatting, Clippy, no-retry tests, rustdoc, 100% owned line/branch/edge coverage, dependency/security policy, SBOM/provenance, live PostgreSQL, OpenCode/Noema, independent review, protected merge, and TEPP release evidence. LLM review cannot substitute for numerical or scientific acceptance. -TEPP then pins the released version through the repository's approved dependency/ACL boundary, removes the local generic mean heuristic rather than retaining two numerical authorities, reruns the public longitudinal RED to GREEN, and reacquires exact-head Rust, 100% owned line/branch coverage, security, SBOM/provenance, review, and release evidence. LLM review is supplementary and cannot activate the numerical change. +## Research and standards trace -## Research basis +International Organization for Standardization. (2020). *Information technology—Microprocessor systems—Floating-point arithmetic* (ISO/IEC 60559:2020). https://www.iso.org/standard/80985.html + +Institute of Electrical and Electronics Engineers. (2019). *IEEE standard for floating-point arithmetic* (IEEE Std 754-2019). https://standards.ieee.org/ieee/315/6210/ Ogita, T., Rump, S. M., & Oishi, S. (2005). Accurate sum and dot product. *SIAM Journal on Scientific Computing, 26*(6), 1955–1988. https://doi.org/10.1137/030601818 -Rump, S. M., Ogita, T., & Oishi, S. (2008). Accurate floating-point summation part I: Faithful rounding. *SIAM Journal on Scientific Computing, 31*(1), 189–224. https://doi.org/10.1137/050645671 +Rump, S. M., Ogita, T., & Oishi, S. (2008a). Accurate floating-point summation part I: Faithful rounding. *SIAM Journal on Scientific Computing, 31*(1), 189–224. https://doi.org/10.1137/050645671 + +Rump, S. M., Ogita, T., & Oishi, S. (2008b). Accurate floating-point summation part II: Sign, K-fold faithful and rounding to nearest. *SIAM Journal on Scientific Computing, 31*(2), 1269–1302. https://doi.org/10.1137/07068816X -These publications motivate error-free transformation and faithful summation analysis. They do not prove the fast-mlsirm implementation or authorize TEPP production activation by themselves. +These sources motivate the numerical design and final-rounding requirements. They do not prove the concrete fast-mlsirm implementation or authorize TEPP production activation by themselves. From 76c543026b8dbbc83edff895e492cab9660c82aa Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 00:01:00 +0900 Subject: [PATCH 237/296] docs(research): trace canonical GPU capacity owner for binary64 mean --- ...ongitudinal-binary64-mean-owner-handoff.md | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/docs/research/longitudinal-binary64-mean-owner-handoff.md b/docs/research/longitudinal-binary64-mean-owner-handoff.md index cec73b8c2..32c6f28f3 100644 --- a/docs/research/longitudinal-binary64-mean-owner-handoff.md +++ b/docs/research/longitudinal-binary64-mean-owner-handoff.md @@ -18,6 +18,8 @@ TEPP owns the temporal estimand: event-time admission, Driver-style log-rate con Reusable finite binary64 sum/mean arithmetic is domain-neutral numerical infrastructure and belongs to `ContextualWisdomLab/fast-mlsirm`. Canonical owner issue `fast-mlsirm#1814` and implementation PR `fast-mlsirm#1816` own this primitive. TEPP must not copy the owner source, pin a mutable PR head, introduce a second generic summation algorithm, or reinterpret an unreleased owner branch as dependency authority. +Repository-owned GPU/CI acquisition and parity evidence is separately owned by `fast-mlsirm#1717`. TEPP and the numerical PR must not duplicate that CI repair, lower its GPU capability invariant, substitute CPU fallback as GPU evidence, or treat a skipped parity test as acceptance. + The immutable fast-mlsirm release remains `v0.9.1`; it predates this contract. ## Fresh owner evidence — 2026-09-11 @@ -29,12 +31,22 @@ The numerical implementation entered at `b5fec1d33c02e8ea969405e59747f0505e690d2 Current exact-head hosted evidence on `432765c...` is mixed and therefore non-mergeable: - native CodeQL `34606295285`, Security Scan `34606295363`, SAST Semgrep `34606295323`, and ClusterFuzzLite `34606295428` are GREEN; -- repository CI `34606295259` is RED because `gpu-smoke` job `103285576158` failed while installing the software Vulkan adapter, before Vulkan availability or GPU parity executed. Package and fuzz jobs are GREEN. This is repository acceptance debt even though it is not evidence against the binary64 mean arithmetic; +- repository CI `34606295259` is RED because `gpu-smoke` job `103285576158` failed while installing the software Vulkan adapter, before Vulkan availability or GPU parity executed. Package and fuzz jobs are GREEN. That run is historical evidence of a failed acquisition path, not the current root-cause authority for GPU readiness; - required delegated CodeQL PR `34606295286` is RED. Python job `103285476466` and Actions job `103285476530` both successfully read the current-head dispatch verdict and then failed at `Release runner or enforce current-head CodeQL verdict`; only afterward did dispatch job `103286411396` succeed. This remains the central producer/consumer settlement class and must be repaired at the canonical `.github` owner rather than copied into TEPP or fast-mlsirm; - current formal reviews are COMMENTED only. The CodeRabbit oracle/standards-link findings are resolved, but there is no qualifying submitted current-head `APPROVED` review. The current owner PR body still names predecessor `f00c31c...` as its exact head, so PR metadata itself is stale relative to GitHub's live head. That is an owner-path documentation finding, not permission for TEPP to rewrite fast-mlsirm source or consume the branch. +## Canonical GPU/CI owner evidence + +The stronger current GPU diagnosis is `fast-mlsirm#1717`, not another patch inside #1816. Its live exact head is `0b31640928e07f4362ce27dad3d310e630ab1b5d`, open/Draft/mergeable on the same protected fast-mlsirm base. + +#1717's controlled predecessor admission successfully acquired and initialized image-local SwiftShader, then measured the actual adapter contract. `SwiftShader Device (Subzero)` exposes `max_storage_buffers_per_shader_stage = 10`, while the current marginal GPU layout requires at least 18 storage buffers per shader stage. The adapter therefore cannot execute the governed kernel topology. Lowering the 18-buffer requirement without redesign evidence, accepting CPU fallback, or relabeling skipped GPU parity as success would weaken the product contract. + +The #1717 forward repair also moved the environment-specific capacity probe out of ordinary workspace tests into `crates/mlsirm-core/examples/gpu_adapter_capacity.rs`, so ordinary `cargo test --workspace` no longer performs hardware acquisition outside the dedicated GPU lane. Its exact-head CI `34607995391` reached `gpu-smoke` job `103291084354`, configured image-local SwiftShader, proved the Vulkan loader, ran the isolated capacity probe, reproduced the 10-versus-18 buffer mismatch, and failed closed at that invariant. The workflow was subsequently cancelled when the PR returned to Draft; the completed capacity RED remains diagnostic evidence, not a GREEN run or release gate. + +The next causal GPU repair therefore belongs to #1717 or its verified successor: either provide a reproducible adapter that satisfies the existing 18-buffer contract, or redesign the marginal resource topology and prove CPU-`f64` parity plus realistic performance/recovery without weakening scientific or GPU acceptance. #1816 must reacquire its own exact-head repository evidence only after that canonical CI path is protected-integrated; TEPP must wait for the resulting immutable numerical release. + ## Proposed numerical contract The owner candidate publishes the proposed identity `fast_mlsirm.binary64_mean@1.0.0`. Every finite binary64 value is represented exactly as an integer multiple of `q = 2^-1074`. Positive and negative totals are accumulated separately in fixed 34×`u64` magnitudes, the exact signed magnitude is divided by the original slice cardinality, and only the final rational result is projected to binary64 with round-to-nearest, ties-to-even. `exact_zero` distinguishes exact represented cancellation from a nonzero mean that rounds to signed zero. @@ -47,7 +59,7 @@ The final mean must be rounded from the exact rational `(S / n) * 2^-1074`, not The current owner test surface includes the TEPP half-ULP counterexample and mirrored sign, `[1e16, -1, -1]`, `[f64::MAX, 1e-16, -f64::MAX]`, exact cancellation versus nonzero underflow, minimum-subnormal residue after MAX cancellation, subnormal/normal boundaries, normal and subnormal ties-to-even, binade carry, same-sign `f64::MAX`, permutation invariance, and empty/NaN/±infinity refusal. It also carries a deterministic 10,000-case subnormal-domain oracle using independent test-only exact integer/rational arithmetic rather than the production 34-limb accumulator. -Those tests are necessary but not sufficient. One unchanged owner head still needs repository Rust/rustdoc/Clippy, actual owned statement and branch/edge coverage, package/fuzz/security/supply-chain evidence, zero valid unresolved findings, qualifying independent review, and terminal central controls. The present GPU provisioning failure and delegated CodeQL failure keep that bar open. +Those tests are necessary but not sufficient. One unchanged owner head still needs repository Rust/rustdoc/Clippy, actual owned statement and branch/edge coverage, package/fuzz/security/supply-chain evidence, zero valid unresolved findings, qualifying independent review, and terminal central controls. The canonical GPU capacity RED and delegated CodeQL failure keep that bar open. ## Rejected TEPP-local repairs @@ -59,12 +71,14 @@ The following are not causal acceptance: - adding a product-specific sample ceiling to simplify the numerical proof; - copying fast-mlsirm #1536 private partials or #1816 source into TEPP; - pinning a mutable owner PR/head; +- copying or modifying #1717's GPU/CI acquisition logic in TEPP or #1816; +- weakening the 18-buffer GPU capability contract, accepting CPU fallback as GPU parity, or accepting a skipped GPU test; - describing faithful or approximate behavior as correctly rounded; -- treating native CodeQL/security success as a substitute for the failed repository CI, delegated CodeQL receipt, independent review, or immutable release. +- treating native CodeQL/security success as a substitute for failed repository/GPU evidence, delegated CodeQL receipt, independent review, or immutable release. ## Release and consumer acceptance -Before TEPP changes production arithmetic, `fast-mlsirm#1816` must land through the normal protected path and the contract must be published in a new immutable versioned release with version/tag/package, SBOM/provenance, reproducibility and rollback evidence. ADR-0029 remains Proposed until that acceptance is complete. +Before TEPP changes production arithmetic, the fast-mlsirm owner chain must close in order: #1717 or a verified successor must establish normal GPU/CI acceptance; `fast-mlsirm#1816` must then reacquire its exact-current repository evidence, land through the normal protected path, and publish the numerical contract in a new immutable versioned release with version/tag/package, SBOM/provenance, reproducibility and rollback evidence. ADR-0029 remains Proposed until that acceptance is complete. After release, TEPP must pin the released contract through the approved dependency/ACL boundary, remove the local generic mean heuristic rather than retaining two numerical authorities, rerun the public longitudinal RED to GREEN, and reacquire exact-head formatting, Clippy, no-retry tests, rustdoc, 100% owned line/branch/edge coverage, dependency/security policy, SBOM/provenance, live PostgreSQL, OpenCode/Noema, independent review, protected merge, and TEPP release evidence. LLM review cannot substitute for numerical or scientific acceptance. From 586cf6e05b4534181a9d8b7150891cfc987d1374 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 00:06:04 +0900 Subject: [PATCH 238/296] docs(research): refresh numerical owner draft authority --- docs/research/longitudinal-binary64-mean-owner-handoff.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/research/longitudinal-binary64-mean-owner-handoff.md b/docs/research/longitudinal-binary64-mean-owner-handoff.md index 32c6f28f3..ed5a971d9 100644 --- a/docs/research/longitudinal-binary64-mean-owner-handoff.md +++ b/docs/research/longitudinal-binary64-mean-owner-handoff.md @@ -24,7 +24,7 @@ The immutable fast-mlsirm release remains `v0.9.1`; it predates this contract. ## Fresh owner evidence — 2026-09-11 -The live `fast-mlsirm#1816` head is `432765ccf633c9802e0f796ceeb4d6d572059acf` on protected `main@493326f2de49ea1704da0ded19868ed05d2fe00f`. The PR is open and non-Draft, but Ready status is admission only and is not merge or release authority. +The live `fast-mlsirm#1816` head is `432765ccf633c9802e0f796ceeb4d6d572059acf` on protected `main@493326f2de49ea1704da0ded19868ed05d2fe00f`. The PR is open, **Draft**, mergeable, and unmerged. Its body is stale in two ways: it still names predecessor `f00c31c...` as current exact head and still describes the state as Ready for fresh admission. GitHub live metadata, not those predecessor body claims, is current authority. The numerical implementation entered at `b5fec1d33c02e8ea969405e59747f0505e690d20`. Subsequent ordinary-forward commits added the independent exact oracle and directed cancellation/non-finite fixtures (`087df1d...`), Proposed ADR-0029 (`9a8614f...`), doctoring/changelog currentization, touched-function rustdoc coverage (`f00c31c...`), and finally official ISO/IEEE record links (`432765c...`). The `f00c31c... -> 432765c...` delta changes only `docs/doctoring/correctly-rounded-binary64-mean.md`; the production arithmetic is unchanged. @@ -35,7 +35,7 @@ Current exact-head hosted evidence on `432765c...` is mixed and therefore non-me - required delegated CodeQL PR `34606295286` is RED. Python job `103285476466` and Actions job `103285476530` both successfully read the current-head dispatch verdict and then failed at `Release runner or enforce current-head CodeQL verdict`; only afterward did dispatch job `103286411396` succeed. This remains the central producer/consumer settlement class and must be repaired at the canonical `.github` owner rather than copied into TEPP or fast-mlsirm; - current formal reviews are COMMENTED only. The CodeRabbit oracle/standards-link findings are resolved, but there is no qualifying submitted current-head `APPROVED` review. -The current owner PR body still names predecessor `f00c31c...` as its exact head, so PR metadata itself is stale relative to GitHub's live head. That is an owner-path documentation finding, not permission for TEPP to rewrite fast-mlsirm source or consume the branch. +The current owner PR body is therefore documentation debt inside the owner lane, not permission for TEPP to rewrite fast-mlsirm source or consume the branch. ## Canonical GPU/CI owner evidence From 50a065be6d2a3622f451a25c4cfb50d017ead50b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 00:08:06 +0900 Subject: [PATCH 239/296] docs(research): reconcile live numerical owner metadata --- .../longitudinal-binary64-mean-owner-handoff.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/research/longitudinal-binary64-mean-owner-handoff.md b/docs/research/longitudinal-binary64-mean-owner-handoff.md index ed5a971d9..bf2f7cf72 100644 --- a/docs/research/longitudinal-binary64-mean-owner-handoff.md +++ b/docs/research/longitudinal-binary64-mean-owner-handoff.md @@ -24,7 +24,7 @@ The immutable fast-mlsirm release remains `v0.9.1`; it predates this contract. ## Fresh owner evidence — 2026-09-11 -The live `fast-mlsirm#1816` head is `432765ccf633c9802e0f796ceeb4d6d572059acf` on protected `main@493326f2de49ea1704da0ded19868ed05d2fe00f`. The PR is open, **Draft**, mergeable, and unmerged. Its body is stale in two ways: it still names predecessor `f00c31c...` as current exact head and still describes the state as Ready for fresh admission. GitHub live metadata, not those predecessor body claims, is current authority. +The live `fast-mlsirm#1816` head is `432765ccf633c9802e0f796ceeb4d6d572059acf` on protected `main@493326f2de49ea1704da0ded19868ed05d2fe00f`. The PR is open, **Draft**, mergeable, and unmerged. Its owner lane has now currentized the body to the same head and Draft-containment state; no predecessor review or hosted success is promoted across the head change. The numerical implementation entered at `b5fec1d33c02e8ea969405e59747f0505e690d20`. Subsequent ordinary-forward commits added the independent exact oracle and directed cancellation/non-finite fixtures (`087df1d...`), Proposed ADR-0029 (`9a8614f...`), doctoring/changelog currentization, touched-function rustdoc coverage (`f00c31c...`), and finally official ISO/IEEE record links (`432765c...`). The `f00c31c... -> 432765c...` delta changes only `docs/doctoring/correctly-rounded-binary64-mean.md`; the production arithmetic is unchanged. @@ -33,9 +33,10 @@ Current exact-head hosted evidence on `432765c...` is mixed and therefore non-me - native CodeQL `34606295285`, Security Scan `34606295363`, SAST Semgrep `34606295323`, and ClusterFuzzLite `34606295428` are GREEN; - repository CI `34606295259` is RED because `gpu-smoke` job `103285576158` failed while installing the software Vulkan adapter, before Vulkan availability or GPU parity executed. Package and fuzz jobs are GREEN. That run is historical evidence of a failed acquisition path, not the current root-cause authority for GPU readiness; - required delegated CodeQL PR `34606295286` is RED. Python job `103285476466` and Actions job `103285476530` both successfully read the current-head dispatch verdict and then failed at `Release runner or enforce current-head CodeQL verdict`; only afterward did dispatch job `103286411396` succeed. This remains the central producer/consumer settlement class and must be repaired at the canonical `.github` owner rather than copied into TEPP or fast-mlsirm; -- current formal reviews are COMMENTED only. The CodeRabbit oracle/standards-link findings are resolved, but there is no qualifying submitted current-head `APPROVED` review. - -The current owner PR body is therefore documentation debt inside the owner lane, not permission for TEPP to rewrite fast-mlsirm source or consume the branch. +- current formal reviews are COMMENTED only. The CodeRabbit oracle/standards-link findings are resolved, but there is no qualifying submitted current-head `APPROVED` review; +- current Noema evidence admitted `orchestrator/free` but the verdict request ended in HTTP 502; the exact consumer evidence belongs to the contextual-orchestrator owner path rather than a leaf provider/model retry; +- current central coverage contexts still do not provide authoritative exact-head Rust owned line+branch evidence for this material Rust implementation; no denominator trick or Python line-touch substitute is acceptable; +- current Strix evidence failed before an authoritative scan during sandbox/bootstrap control, so absence of a scan is not a clean security result. ## Canonical GPU/CI owner evidence @@ -59,7 +60,7 @@ The final mean must be rounded from the exact rational `(S / n) * 2^-1074`, not The current owner test surface includes the TEPP half-ULP counterexample and mirrored sign, `[1e16, -1, -1]`, `[f64::MAX, 1e-16, -f64::MAX]`, exact cancellation versus nonzero underflow, minimum-subnormal residue after MAX cancellation, subnormal/normal boundaries, normal and subnormal ties-to-even, binade carry, same-sign `f64::MAX`, permutation invariance, and empty/NaN/±infinity refusal. It also carries a deterministic 10,000-case subnormal-domain oracle using independent test-only exact integer/rational arithmetic rather than the production 34-limb accumulator. -Those tests are necessary but not sufficient. One unchanged owner head still needs repository Rust/rustdoc/Clippy, actual owned statement and branch/edge coverage, package/fuzz/security/supply-chain evidence, zero valid unresolved findings, qualifying independent review, and terminal central controls. The canonical GPU capacity RED and delegated CodeQL failure keep that bar open. +Those tests are necessary but not sufficient. One unchanged owner head still needs repository Rust/rustdoc/Clippy, actual owned statement and branch/edge coverage, package/fuzz/security/supply-chain evidence, zero valid unresolved findings, qualifying independent review, and terminal central controls. The canonical GPU capacity RED, delegated CodeQL failure, missing authoritative Rust coverage evidence, Noema owner failure, and pre-scan Strix failure keep that bar open. ## Rejected TEPP-local repairs @@ -74,7 +75,7 @@ The following are not causal acceptance: - copying or modifying #1717's GPU/CI acquisition logic in TEPP or #1816; - weakening the 18-buffer GPU capability contract, accepting CPU fallback as GPU parity, or accepting a skipped GPU test; - describing faithful or approximate behavior as correctly rounded; -- treating native CodeQL/security success as a substitute for failed repository/GPU evidence, delegated CodeQL receipt, independent review, or immutable release. +- treating native CodeQL/security success as a substitute for failed repository/GPU evidence, delegated CodeQL receipt, authoritative Rust coverage, independent review, semantic-review evidence, or immutable release. ## Release and consumer acceptance From df0b4d6d3e0622de3c988b840114fbdb41e5d1b0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 03:01:43 +0900 Subject: [PATCH 240/296] docs(longitudinal): trace GPU resource-topology owner repair --- .../longitudinal-binary64-mean-owner-handoff.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/research/longitudinal-binary64-mean-owner-handoff.md b/docs/research/longitudinal-binary64-mean-owner-handoff.md index bf2f7cf72..864efb188 100644 --- a/docs/research/longitudinal-binary64-mean-owner-handoff.md +++ b/docs/research/longitudinal-binary64-mean-owner-handoff.md @@ -22,9 +22,9 @@ Repository-owned GPU/CI acquisition and parity evidence is separately owned by ` The immutable fast-mlsirm release remains `v0.9.1`; it predates this contract. -## Fresh owner evidence — 2026-09-11 +## Fresh owner evidence -The live `fast-mlsirm#1816` head is `432765ccf633c9802e0f796ceeb4d6d572059acf` on protected `main@493326f2de49ea1704da0ded19868ed05d2fe00f`. The PR is open, **Draft**, mergeable, and unmerged. Its owner lane has now currentized the body to the same head and Draft-containment state; no predecessor review or hosted success is promoted across the head change. +The live `fast-mlsirm#1816` head is `432765ccf633c9802e0f796ceeb4d6d572059acf` on protected `main@493326f2de49ea1704da0ded19868ed05d2fe00f`. The PR is open, **Draft**, mergeable, and unmerged. Its owner lane has currentized the body to the same head and Draft-containment state; no predecessor review or hosted success is promoted across the head change. The numerical implementation entered at `b5fec1d33c02e8ea969405e59747f0505e690d20`. Subsequent ordinary-forward commits added the independent exact oracle and directed cancellation/non-finite fixtures (`087df1d...`), Proposed ADR-0029 (`9a8614f...`), doctoring/changelog currentization, touched-function rustdoc coverage (`f00c31c...`), and finally official ISO/IEEE record links (`432765c...`). The `f00c31c... -> 432765c...` delta changes only `docs/doctoring/correctly-rounded-binary64-mean.md`; the production arithmetic is unchanged. @@ -44,9 +44,15 @@ The stronger current GPU diagnosis is `fast-mlsirm#1717`, not another patch insi #1717's controlled predecessor admission successfully acquired and initialized image-local SwiftShader, then measured the actual adapter contract. `SwiftShader Device (Subzero)` exposes `max_storage_buffers_per_shader_stage = 10`, while the current marginal GPU layout requires at least 18 storage buffers per shader stage. The adapter therefore cannot execute the governed kernel topology. Lowering the 18-buffer requirement without redesign evidence, accepting CPU fallback, or relabeling skipped GPU parity as success would weaken the product contract. -The #1717 forward repair also moved the environment-specific capacity probe out of ordinary workspace tests into `crates/mlsirm-core/examples/gpu_adapter_capacity.rs`, so ordinary `cargo test --workspace` no longer performs hardware acquisition outside the dedicated GPU lane. Its exact-head CI `34607995391` reached `gpu-smoke` job `103291084354`, configured image-local SwiftShader, proved the Vulkan loader, ran the isolated capacity probe, reproduced the 10-versus-18 buffer mismatch, and failed closed at that invariant. The workflow was subsequently cancelled when the PR returned to Draft; the completed capacity RED remains diagnostic evidence, not a GREEN run or release gate. +Exact source inspection narrows the mismatch to storage topology rather than the statistical equations. The E-step shader binds one uniform plus 17 storage buffers. The score shader binds one uniform plus 18 storage buffers: 14 read-only logical arrays and four read-write outputs (`theta_eap`, `theta_sd`, `xi_eap`, `loglik`). Because the limit is per shader stage, distributing the same storage bindings over additional bind groups does not reduce the requirement. -The next causal GPU repair therefore belongs to #1717 or its verified successor: either provide a reproducible adapter that satisfies the existing 18-buffer contract, or redesign the marginal resource topology and prove CPU-`f64` parity plus realistic performance/recovery without weakening scientific or GPU acceptance. #1816 must reacquire its own exact-head repository evidence only after that canonical CI path is protected-integrated; TEPP must wait for the resulting immutable numerical release. +A non-weakening owner-side candidate is therefore **arena packing**, not a lower capability assertion. Pack read-only `f32` tables into one storage arena, pack `u32`/CSR indices into a second arena, and pack output/state into a third read-write arena, with fixed `u32` offsets carried in the uniform block. The score path can then target roughly three storage bindings plus one uniform; the E-step can use the same resource shape. This proposal changes storage addressing, not the WGSL equations or the intended `f32` GPU arithmetic. It is a Proposed repair candidate for #1717 or a verified successor, not an accepted implementation and not TEPP source authority. + +The arena candidate is acceptable only if the owner proves, on one exact head: generated score/E-step layouts use no more than ten storage buffers; packing offsets and arena round trips are deterministic including empty/padded segments; CPU-`f64` parity thresholds on realistic marginal/EAP fixtures remain unchanged; the existing SwiftShader capacity probe is followed by the real explicit GPU parity path rather than skip/fallback; realistic per-iteration or p95 evidence shows the packing does not erase the GPU benefit; and recovery/error behavior remains intact. If the owner rejects arena packing, the resource-topology reason belongs in its ADR/evidence rather than restoring an arbitrary 18-buffer admission floor. + +The #1717 forward repair also moved the environment-specific capacity probe out of ordinary workspace tests into `crates/mlsirm-core/examples/gpu_adapter_capacity.rs`, so ordinary `cargo test --workspace` no longer performs hardware acquisition outside the dedicated GPU lane. Its exact-head CI `34607995391` reached `gpu-smoke` job `103291084354`, configured image-local SwiftShader, proved the Vulkan loader, ran the isolated capacity probe, reproduced the 10-versus-18 buffer mismatch, and failed closed at that invariant. The workflow was subsequently cancelled when the PR returned to Draft; the completed capacity RED remains diagnostic evidence. The ordinary Rust/Python/package/fuzz jobs that were cancelled are neither current-running nor terminal-GREEN landing evidence. + +The next causal GPU repair therefore belongs to #1717 or its verified successor. #1816 must reacquire its own exact-head repository evidence only after that canonical CI path is protected-integrated; TEPP must wait for the resulting immutable numerical release. ## Proposed numerical contract From 220bdb0f19519341d9fd50e46bbab8a6fc3be85c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 04:06:11 +0900 Subject: [PATCH 241/296] docs(longitudinal): distinguish pair and unit rate estimands --- .../irregular-rate-estimand-weighting.md | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 docs/research/irregular-rate-estimand-weighting.md diff --git a/docs/research/irregular-rate-estimand-weighting.md b/docs/research/irregular-rate-estimand-weighting.md new file mode 100644 index 000000000..862314939 --- /dev/null +++ b/docs/research/irregular-rate-estimand-weighting.md @@ -0,0 +1,76 @@ +# Irregular residual log-rate weighting and estimand identity + +## Status and scope + +This note records a Longitudinal Modeling estimand boundary exposed by TEPP PR #310. It does not change numerical arithmetic and does not promote an unmerged branch to protected-main authority. + +Exact source reviewed before this note: `df0b4d6d3e0622de3c988b840114fbdb41e5d1b0`. + +`center_within_unit_event_lags` forms one consecutive event-time lag pair for each admitted adjacent occasion inside a unit. `LaggedWithinResidual` then carries the earlier residual, later residual, and typed event interval but not the originating unit identity. `pairwise_same_sign_log_rate` computes an admitted scalar log-rate for each pair and passes the resulting rate vector to the Longitudinal-local mean boundary. + +Consequently, the existing aggregate is a lag-pair-average estimand. It must not be described as an equal-unit average unless the design makes those two estimands coincide. + +## Two distinct estimands + +Let unit `i` contribute `k_i >= 1` admitted consecutive lag pairs after all temporal and residual admission rules. Let `r_ij` be the scalar event-time log-rate for admitted pair `j` of unit `i`. + +The current pair-average target is + +\[ +\theta_{pair} += \frac{\sum_i \sum_{j=1}^{k_i} r_{ij}} + {\sum_i k_i}. +\] + +Every admitted lag pair receives equal weight. A unit with more admitted occasions can therefore receive more total weight through more consecutive pairs. + +A different, equal-unit target is + +\[ +\theta_{unit} += \frac{1}{I}\sum_{i=1}^{I} + \left(\frac{1}{k_i}\sum_{j=1}^{k_i} r_{ij}\right). +\] + +Every contributing unit receives equal final weight after its admitted pair rates are summarized within unit. The targets are not algebraically interchangeable when `k_i` varies. They may also target different populations when follow-up duration, observation count, missingness, or pair admissibility is associated with the longitudinal process. + +The existing TEPP API has enough information to compute `theta_pair` after pairs are formed, but it does not retain unit identity in `LaggedWithinResidual`; it therefore cannot reconstruct `theta_unit` from that vector without changing the typed contract or preserving a unit-keyed aggregation boundary. + +## Failure denominators are part of the estimand + +Zero, opposite-sign, non-finite, or otherwise inadmissible residual pairs are not ordinary numerical observations. A longitudinal summary must report at least: + +- candidate units; +- units contributing one or more admitted lag pairs; +- candidate consecutive pairs; +- admitted pairs; +- refused pairs by typed reason. + +Dropping a refused pair changes `k_i` and can change both the pair-average weight and whether a unit contributes to a unit-average target. A unit with no admitted pairs must not disappear silently from a denominator whose interpretation says otherwise. The public contract therefore needs a declared estimand and failure-denominator policy before a result can be promoted as scientific evidence. + +## Relation to unequal and informative cluster size + +This is not a cluster-randomized treatment-effect model. However, the statistical identification issue is analogous: unequal numbers of observations inside a higher-level unit can induce different weighting targets, and informative cluster or subcluster size can make those targets materially different. The estimator has to match the declared estimand rather than inheriting weights accidentally from record multiplicity. + +Wang, Kong, and Datta (2011) study clustered longitudinal data and show that informative cluster size can invalidate ordinary marginal inference when cluster size is related to the outcome distribution. Huang (2011) further shows that the appropriate weights depend on the population of interest and on within-cluster covariate structure. Kahan et al. (2023) give a clear modern estimand distinction between equal participant weighting and equal cluster weighting. TEPP does not import their treatment-effect estimands; it imports the narrower methodological requirement that aggregation weights are part of estimand identity. + +## Required product contract + +Issue #495 owns the follow-up gap. Before any equal-unit result is implemented or any current pair-average result is described as an average unit/person effect: + +1. PRD/TRD/ADR/TRACEABILITY must name the target (`lag_pair_average`, `unit_average`, or a separately justified design-weighted target). +2. A unit-average path must preserve unit identity until within-unit rates are summarized; occasion count cannot stand in for an externally defined design or membership weight. +3. Known-truth acceptance must include balanced and highly unbalanced occasion counts, informative missing/follow-up patterns, irregular intervals, row permutation, worker-count determinism, and explicit failure denominators. +4. Cross-classified and multiple-membership extensions must retain their declared membership structure and must not collapse to a primary group merely to obtain one scalar weight. +5. Neither target is automatically a raw-process autoregressive/DSEM effect. Existing refusal boundaries around CWC and occasion-mean residuals remain in force. +6. Reusable finite binary64 sum/mean arithmetic remains owned by `ContextualWisdomLab/fast-mlsirm`. TEPP may consume it only from an immutable released contract; this note does not authorize another generic summation implementation in `longitudinal_core`. + +The current mixed-sign binary64 mean RED in #310 remains independent. Clarifying the estimand does not make that numerical RED pass and does not authorize a merge. + +## References + +Huang, Y. (2011). Informative cluster sizes for subcluster-level covariates and weighted generalized estimating equations. *Biometrics, 67*(3), 843–851. https://doi.org/10.1111/j.1541-0420.2010.01542.x + +Kahan, B. C., Li, F., Blette, B., Jairath, V., Copas, A., & Harhay, M. O. (2023). Informative cluster size in cluster-randomised trials: A case study from the TRIGGER trial. *Clinical Trials*. https://doi.org/10.1177/17407745231186094 + +Wang, M., Kong, M., & Datta, S. (2011). Inference for marginal linear models for clustered longitudinal data with potentially informative cluster sizes. *Statistical Methods in Medical Research, 20*(4), 347–367. https://doi.org/10.1177/0962280209347043 From c7f55acb347ceac38675d9566eff767372739dba Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 05:02:32 +0900 Subject: [PATCH 242/296] feat(longitudinal): type irregular-rate estimand evidence --- .../src/irregular_rate_estimand.rs | 196 ++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 crates/longitudinal_core/src/irregular_rate_estimand.rs diff --git a/crates/longitudinal_core/src/irregular_rate_estimand.rs b/crates/longitudinal_core/src/irregular_rate_estimand.rs new file mode 100644 index 000000000..0b0e056a7 --- /dev/null +++ b/crates/longitudinal_core/src/irregular_rate_estimand.rs @@ -0,0 +1,196 @@ +//! Versioned weighting policy for CWC irregular-residual log-rate summaries. +//! +//! Longitudinal units can contribute different numbers of consecutive event-time +//! pairs. That multiplicity is part of the estimand, not a numerical detail. +//! This module makes the currently supported lag-pair-average target explicit +//! and reports its unit/pair denominators. Equal-unit aggregation stays typed +//! but fail-closed until the reusable correctly-rounded finite-mean contract is +//! available from its canonical numerical owner. + +use std::collections::BTreeMap; + +use crate::irregular_residual::{ + EventTimedObservation, center_within_unit_event_lags, driver_same_sign_log_rate, + same_sign_nonzero, scaled_compensated_mean, +}; +use crate::LongitudinalError; + +/// Versioned scientific weighting target for irregular residual log-rate summaries. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum IrregularRateEstimand { + /// Every admitted consecutive lag pair receives equal weight. + LagPairAverageV1, + /// Each unit first receives one within-unit summary and then equal unit weight. + /// + /// This target is reserved but not yet numerically activated. Requesting it + /// fails closed until TEPP can consume the released reusable finite-mean + /// contract instead of adding another local generic mean implementation. + UnitAverageV1, +} + +impl IrregularRateEstimand { + /// Stable external name of this estimand contract. + #[must_use] + pub const fn wire_name(self) -> &'static str { + match self { + Self::LagPairAverageV1 => "tepp.irregular_rate.lag_pair_average.v1", + Self::UnitAverageV1 => "tepp.irregular_rate.unit_average.v1", + } + } +} + +/// Denominator-bearing evidence for one irregular residual log-rate summary. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct IrregularRateSummary { + estimand: IrregularRateEstimand, + estimate: Option, + candidate_units: usize, + contributing_units: usize, + candidate_pairs: usize, + admitted_pairs: usize, + refused_pairs: usize, +} + +impl IrregularRateSummary { + /// Return the versioned weighting target used by this summary. + #[must_use] + pub const fn estimand(self) -> IrregularRateEstimand { + self.estimand + } + + /// Return the represented estimate, or `None` when no candidate pair was admissible. + #[must_use] + pub const fn estimate(self) -> Option { + self.estimate + } + + /// Return the number of units with at least two admitted event-time occasions. + #[must_use] + pub const fn candidate_units(self) -> usize { + self.candidate_units + } + + /// Return the number of candidate units contributing at least one admitted rate. + #[must_use] + pub const fn contributing_units(self) -> usize { + self.contributing_units + } + + /// Return the number of consecutive event-time pairs considered. + #[must_use] + pub const fn candidate_pairs(self) -> usize { + self.candidate_pairs + } + + /// Return the number of pairs admitted to the reported numerical estimate. + #[must_use] + pub const fn admitted_pairs(self) -> usize { + self.admitted_pairs + } + + /// Return the number of candidate pairs refused by log-rate admission. + #[must_use] + pub const fn refused_pairs(self) -> usize { + self.refused_pairs + } +} + +/// Recover an explicitly named irregular residual log-rate estimand with denominators. +/// +/// `LagPairAverageV1` preserves the existing TEPP behavior: every admissible +/// consecutive pair receives equal weight. The returned evidence makes the +/// weighting population observable by carrying candidate/contributing units and +/// candidate/admitted/refused pairs. Zero, opposite-sign, or non-representable +/// pair rates are refusals rather than invisible denominator changes. +/// +/// `UnitAverageV1` is intentionally fail-closed. Equal-unit aggregation needs a +/// second finite-mean operation over within-unit summaries. Reusable finite mean +/// arithmetic is not owned by TEPP, so activation waits for an immutable released +/// owner contract and parity evidence. +/// +/// # Errors +/// +/// Propagates event-time/CWC admission failures. `UnitAverageV1` returns +/// [`LongitudinalError::InvalidTemporalTransformInput`] while the released +/// reusable mean contract is unavailable. The pair-average numerical mean can +/// return the same error if its currently shared compatibility arithmetic cannot +/// represent the final admitted-pair mean. +pub fn recover_within_unit_irregular_rate_summary( + rows: &[EventTimedObservation], + estimand: IrregularRateEstimand, +) -> Result { + if estimand == IrregularRateEstimand::UnitAverageV1 { + return Err(LongitudinalError::InvalidTemporalTransformInput); + } + + let lagged = center_within_unit_event_lags(rows)?; + let pair_counts = consecutive_pair_counts(rows); + let candidate_units = pair_counts.len(); + let candidate_pairs = lagged.len(); + let mut admitted_rates = Vec::with_capacity(candidate_pairs); + let mut admitted_pairs = 0_usize; + let mut refused_pairs = 0_usize; + let mut contributing_units = 0_usize; + let mut offset = 0_usize; + + for pair_count in pair_counts.values().copied() { + let end = offset + pair_count; + let unit_pairs = &lagged[offset..end]; + let mut unit_contributed = false; + for pair in unit_pairs { + if !same_sign_nonzero(pair.earlier_residual(), pair.later_residual()) { + refused_pairs += 1; + continue; + } + match driver_same_sign_log_rate( + pair.earlier_residual(), + pair.later_residual(), + pair.event_interval(), + ) { + Ok(rate) => { + admitted_rates.push(rate); + admitted_pairs += 1; + unit_contributed = true; + } + Err(LongitudinalError::InvalidTemporalTransformInput) => { + refused_pairs += 1; + } + Err(error) => return Err(error), + } + } + if unit_contributed { + contributing_units += 1; + } + offset = end; + } + + debug_assert_eq!(offset, candidate_pairs); + debug_assert_eq!(admitted_pairs + refused_pairs, candidate_pairs); + + let estimate = if admitted_rates.is_empty() { + None + } else { + Some(scaled_compensated_mean(&admitted_rates)?) + }; + + Ok(IrregularRateSummary { + estimand, + estimate, + candidate_units, + contributing_units, + candidate_pairs, + admitted_pairs, + refused_pairs, + }) +} + +fn consecutive_pair_counts(rows: &[EventTimedObservation]) -> BTreeMap { + let mut occasion_counts = BTreeMap::::new(); + for row in rows { + *occasion_counts.entry(row.unit_index()).or_default() += 1; + } + occasion_counts + .into_iter() + .filter_map(|(unit, count)| (count >= 2).then_some((unit, count - 1))) + .collect() +} From 2f3f0a97a67d01e0a0c0da78d54ee395b49c0317 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 05:02:43 +0900 Subject: [PATCH 243/296] feat(longitudinal): export irregular-rate estimand contract --- crates/longitudinal_core/src/lib.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/longitudinal_core/src/lib.rs b/crates/longitudinal_core/src/lib.rs index 5b72608bd..9f0607606 100644 --- a/crates/longitudinal_core/src/lib.rs +++ b/crates/longitudinal_core/src/lib.rs @@ -18,6 +18,7 @@ mod diffusion; mod discrete_drift; mod error; mod event_time; +mod irregular_rate_estimand; mod irregular_residual; mod level; mod occasion_mean; @@ -55,6 +56,12 @@ pub use discrete_drift::refuse_unstandardised_discrete_drift_as_standardised_dis pub use error::LongitudinalError; /// A finite, strictly positive interval admitted on substantive event time. pub use event_time::EventTimeInterval; +/// Versioned weighting target for irregular residual log-rate summaries. +pub use irregular_rate_estimand::IrregularRateEstimand; +/// Denominator-bearing evidence for an irregular residual log-rate summary. +pub use irregular_rate_estimand::IrregularRateSummary; +/// Recover an explicitly named irregular residual log-rate estimand with denominators. +pub use irregular_rate_estimand::recover_within_unit_irregular_rate_summary; /// One unit's score at one event-time occasion. pub use irregular_residual::EventTimedObservation; /// One already-formed lagged within residual pair on event time. From 0d0d4f80f12cd8ce24d949006e9bec9278c3c3fa Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 05:02:52 +0900 Subject: [PATCH 244/296] feat(longitudinal): route estimand summary through stable facade --- .../longitudinal_core/src/stable_irregular_rate.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/crates/longitudinal_core/src/stable_irregular_rate.rs b/crates/longitudinal_core/src/stable_irregular_rate.rs index c35161cfd..4d6589237 100644 --- a/crates/longitudinal_core/src/stable_irregular_rate.rs +++ b/crates/longitudinal_core/src/stable_irregular_rate.rs @@ -1,14 +1,18 @@ //! Stable irregular residual log-rate API. //! //! Numerical ownership lives in [`crate::irregular_residual`], alongside event -//! ordering, CWC admission, and the shared same-sign log-rate primitive. This -//! module is only the crate-public facade and must not carry a second +//! ordering, CWC admission, and the shared same-sign log-rate primitive. The +//! weighting/denominator contract lives in [`crate::irregular_rate_estimand`]. +//! This module is only the crate-public facade and must not carry a second //! floating-point implementation. /// Crate-public irregular residual log-rate entry points. /// -/// Re-exports [`crate::irregular_residual::recover_centered_irregular_residual_log_rate`] -/// and [`crate::irregular_residual::recover_within_unit_irregular_residual_log_rate`]. +/// Re-exports the legacy pair-average scalar functions and the versioned, +/// denominator-bearing estimand contract. +pub use crate::irregular_rate_estimand::{ + IrregularRateEstimand, IrregularRateSummary, recover_within_unit_irregular_rate_summary, +}; pub use crate::irregular_residual::{ recover_centered_irregular_residual_log_rate, recover_within_unit_irregular_residual_log_rate, }; From f45e83ba5223ad9d89022482e759ad9e705b229c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 05:03:07 +0900 Subject: [PATCH 245/296] test(longitudinal): fix irregular-rate estimand identity --- .../tests/irregular_rate_estimand_contract.rs | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs diff --git a/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs new file mode 100644 index 000000000..4b251a1e9 --- /dev/null +++ b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs @@ -0,0 +1,120 @@ +//! Public contract for pair-weighted irregular-rate estimand identity. + +use longitudinal_core::{ + EventTimedObservation, IrregularRateEstimand, LongitudinalError, + recover_within_unit_irregular_rate_summary, recover_within_unit_irregular_residual_log_rate, +}; + +fn timed(unit: u32, event_time: f64, score: f64) -> EventTimedObservation { + EventTimedObservation::new(unit, event_time, score) +} + +fn unequal_pair_count_rows() -> [EventTimedObservation; 7] { + [ + timed(1, 0.0, -3.0), + timed(1, 1.0, -2.0), + timed(1, 2.0, -1.0), + timed(1, 3.0, 6.0), + timed(2, 0.0, 3.0), + timed(2, 1.0, 1.0), + timed(2, 2.0, -4.0), + ] +} + +#[test] +fn lag_pair_average_reports_estimand_and_failure_denominators() { + let rows = unequal_pair_count_rows(); + let summary = recover_within_unit_irregular_rate_summary( + &rows, + IrregularRateEstimand::LagPairAverageV1, + ) + .expect("pair-average evidence"); + + assert_eq!( + summary.estimand().wire_name(), + "tepp.irregular_rate.lag_pair_average.v1" + ); + assert_eq!(summary.candidate_units(), 2); + assert_eq!(summary.contributing_units(), 2); + assert_eq!(summary.candidate_pairs(), 5); + assert_eq!(summary.admitted_pairs(), 3); + assert_eq!(summary.refused_pairs(), 2); + assert_eq!( + summary.admitted_pairs() + summary.refused_pairs(), + summary.candidate_pairs() + ); + + let pair_average = summary.estimate().expect("three admitted rates"); + let legacy = recover_within_unit_irregular_residual_log_rate(&rows).expect("legacy pair mean"); + assert_eq!(pair_average.to_bits(), legacy.to_bits()); + + let unit_one = ((2.0_f64 / 3.0).ln() + (1.0_f64 / 2.0).ln()) / 2.0; + let unit_two = (1.0_f64 / 3.0).ln(); + let equal_unit_comparison = (unit_one + unit_two) / 2.0; + assert!( + (pair_average - equal_unit_comparison).abs() > 1.0e-3, + "unequal admitted-pair counts must make pair and equal-unit targets distinguishable" + ); +} + +#[test] +fn lag_pair_average_is_invariant_to_input_row_permutation() { + let canonical = unequal_pair_count_rows(); + let shuffled = [ + canonical[5], + canonical[2], + canonical[6], + canonical[0], + canonical[3], + canonical[4], + canonical[1], + ]; + + let expected = recover_within_unit_irregular_rate_summary( + &canonical, + IrregularRateEstimand::LagPairAverageV1, + ) + .expect("canonical"); + let reordered = recover_within_unit_irregular_rate_summary( + &shuffled, + IrregularRateEstimand::LagPairAverageV1, + ) + .expect("shuffled"); + + assert_eq!(reordered, expected); +} + +#[test] +fn zero_admissible_rates_still_return_explicit_pair_denominator() { + let rows = [ + timed(1, 0.0, 1.0), + timed(1, 1.0, -1.0), + timed(2, 0.0, 2.0), + timed(2, 1.0, -2.0), + ]; + let summary = recover_within_unit_irregular_rate_summary( + &rows, + IrregularRateEstimand::LagPairAverageV1, + ) + .expect("denominator evidence survives zero admissible rates"); + + assert_eq!(summary.estimate(), None); + assert_eq!(summary.candidate_units(), 2); + assert_eq!(summary.contributing_units(), 0); + assert_eq!(summary.candidate_pairs(), 2); + assert_eq!(summary.admitted_pairs(), 0); + assert_eq!(summary.refused_pairs(), 2); +} + +#[test] +fn unit_average_is_versioned_but_fails_closed_until_owner_mean_release() { + let rows = unequal_pair_count_rows(); + assert_eq!( + IrregularRateEstimand::UnitAverageV1.wire_name(), + "tepp.irregular_rate.unit_average.v1" + ); + assert_eq!( + recover_within_unit_irregular_rate_summary(&rows, IrregularRateEstimand::UnitAverageV1), + Err(LongitudinalError::InvalidTemporalTransformInput) + ); +} From 493b45d62e2413bea0f56509b8a379c1b152c83d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 05:04:59 +0900 Subject: [PATCH 246/296] feat(longitudinal): expose irregular-rate refusal denominators --- .../src/irregular_rate_estimand.rs | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/crates/longitudinal_core/src/irregular_rate_estimand.rs b/crates/longitudinal_core/src/irregular_rate_estimand.rs index 0b0e056a7..edbb14884 100644 --- a/crates/longitudinal_core/src/irregular_rate_estimand.rs +++ b/crates/longitudinal_core/src/irregular_rate_estimand.rs @@ -48,7 +48,8 @@ pub struct IrregularRateSummary { contributing_units: usize, candidate_pairs: usize, admitted_pairs: usize, - refused_pairs: usize, + sign_or_zero_refused_pairs: usize, + nonrepresentable_rate_refused_pairs: usize, } impl IrregularRateSummary { @@ -88,10 +89,22 @@ impl IrregularRateSummary { self.admitted_pairs } - /// Return the number of candidate pairs refused by log-rate admission. + /// Return pairs refused because a residual was zero or the pair changed sign. + #[must_use] + pub const fn sign_or_zero_refused_pairs(self) -> usize { + self.sign_or_zero_refused_pairs + } + + /// Return same-sign nonzero pairs whose represented log-rate was not admissible. + #[must_use] + pub const fn nonrepresentable_rate_refused_pairs(self) -> usize { + self.nonrepresentable_rate_refused_pairs + } + + /// Return the total number of candidate pairs refused by log-rate admission. #[must_use] pub const fn refused_pairs(self) -> usize { - self.refused_pairs + self.sign_or_zero_refused_pairs + self.nonrepresentable_rate_refused_pairs } } @@ -100,8 +113,9 @@ impl IrregularRateSummary { /// `LagPairAverageV1` preserves the existing TEPP behavior: every admissible /// consecutive pair receives equal weight. The returned evidence makes the /// weighting population observable by carrying candidate/contributing units and -/// candidate/admitted/refused pairs. Zero, opposite-sign, or non-representable -/// pair rates are refusals rather than invisible denominator changes. +/// candidate/admitted/refused pairs. Zero or opposite-sign pairs and represented +/// same-sign pairs whose log-rate cannot be admitted have separate refusal +/// denominators instead of silently changing the weighting population. /// /// `UnitAverageV1` is intentionally fail-closed. Equal-unit aggregation needs a /// second finite-mean operation over within-unit summaries. Reusable finite mean @@ -129,7 +143,8 @@ pub fn recover_within_unit_irregular_rate_summary( let candidate_pairs = lagged.len(); let mut admitted_rates = Vec::with_capacity(candidate_pairs); let mut admitted_pairs = 0_usize; - let mut refused_pairs = 0_usize; + let mut sign_or_zero_refused_pairs = 0_usize; + let mut nonrepresentable_rate_refused_pairs = 0_usize; let mut contributing_units = 0_usize; let mut offset = 0_usize; @@ -139,7 +154,7 @@ pub fn recover_within_unit_irregular_rate_summary( let mut unit_contributed = false; for pair in unit_pairs { if !same_sign_nonzero(pair.earlier_residual(), pair.later_residual()) { - refused_pairs += 1; + sign_or_zero_refused_pairs += 1; continue; } match driver_same_sign_log_rate( @@ -153,7 +168,7 @@ pub fn recover_within_unit_irregular_rate_summary( unit_contributed = true; } Err(LongitudinalError::InvalidTemporalTransformInput) => { - refused_pairs += 1; + nonrepresentable_rate_refused_pairs += 1; } Err(error) => return Err(error), } @@ -164,6 +179,7 @@ pub fn recover_within_unit_irregular_rate_summary( offset = end; } + let refused_pairs = sign_or_zero_refused_pairs + nonrepresentable_rate_refused_pairs; debug_assert_eq!(offset, candidate_pairs); debug_assert_eq!(admitted_pairs + refused_pairs, candidate_pairs); @@ -180,7 +196,8 @@ pub fn recover_within_unit_irregular_rate_summary( contributing_units, candidate_pairs, admitted_pairs, - refused_pairs, + sign_or_zero_refused_pairs, + nonrepresentable_rate_refused_pairs, }) } From a3065f6d7589d5a310e54f85d6447b27414955ba Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 05:05:13 +0900 Subject: [PATCH 247/296] test(longitudinal): trace irregular-rate refusal classes --- .../tests/irregular_rate_estimand_contract.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs index 4b251a1e9..60738f5c0 100644 --- a/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs +++ b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs @@ -38,6 +38,8 @@ fn lag_pair_average_reports_estimand_and_failure_denominators() { assert_eq!(summary.contributing_units(), 2); assert_eq!(summary.candidate_pairs(), 5); assert_eq!(summary.admitted_pairs(), 3); + assert_eq!(summary.sign_or_zero_refused_pairs(), 2); + assert_eq!(summary.nonrepresentable_rate_refused_pairs(), 0); assert_eq!(summary.refused_pairs(), 2); assert_eq!( summary.admitted_pairs() + summary.refused_pairs(), @@ -103,6 +105,8 @@ fn zero_admissible_rates_still_return_explicit_pair_denominator() { assert_eq!(summary.contributing_units(), 0); assert_eq!(summary.candidate_pairs(), 2); assert_eq!(summary.admitted_pairs(), 0); + assert_eq!(summary.sign_or_zero_refused_pairs(), 2); + assert_eq!(summary.nonrepresentable_rate_refused_pairs(), 0); assert_eq!(summary.refused_pairs(), 2); } From 28e5d766e849784ca7d97fe29c3f3cfcee725a6a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 05:05:57 +0900 Subject: [PATCH 248/296] docs(longitudinal): trace typed irregular-rate estimand contract --- .../irregular-rate-estimand-weighting.md | 43 +++++++++++++------ 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/docs/research/irregular-rate-estimand-weighting.md b/docs/research/irregular-rate-estimand-weighting.md index 862314939..d5121585f 100644 --- a/docs/research/irregular-rate-estimand-weighting.md +++ b/docs/research/irregular-rate-estimand-weighting.md @@ -2,13 +2,13 @@ ## Status and scope -This note records a Longitudinal Modeling estimand boundary exposed by TEPP PR #310. It does not change numerical arithmetic and does not promote an unmerged branch to protected-main authority. +This note records a Longitudinal Modeling estimand boundary exposed by TEPP PR #310 and issue #495. The branch now contains a typed first-release candidate contract, but it remains Draft and is not protected-main or release authority. -Exact source reviewed before this note: `df0b4d6d3e0622de3c988b840114fbdb41e5d1b0`. +Scientific finding head: `df0b4d6d3e0622de3c988b840114fbdb41e5d1b0`. Typed-contract implementation lineage starts at `c7f55acb347ceac38675d9566eff767372739dba`; public contract tests start at `f45e83ba5223ad9d89022482e759ad9e705b229c` and refusal-class denominators are completed by `a3065f6d7589d5a310e54f85d6447b27414955ba`. -`center_within_unit_event_lags` forms one consecutive event-time lag pair for each admitted adjacent occasion inside a unit. `LaggedWithinResidual` then carries the earlier residual, later residual, and typed event interval but not the originating unit identity. `pairwise_same_sign_log_rate` computes an admitted scalar log-rate for each pair and passes the resulting rate vector to the Longitudinal-local mean boundary. +`center_within_unit_event_lags` forms one consecutive event-time lag pair for each admitted adjacent occasion inside a unit. `LaggedWithinResidual` carries the earlier residual, later residual, and typed event interval but not the originating unit identity. The existing scalar recovery therefore computes a lag-pair-average estimand: every admissible pair enters one common rate vector before averaging. -Consequently, the existing aggregate is a lag-pair-average estimand. It must not be described as an equal-unit average unless the design makes those two estimands coincide. +The Draft candidate now names that target explicitly as `tepp.irregular_rate.lag_pair_average.v1`. `tepp.irregular_rate.unit_average.v1` is also a typed name, but it fails closed until an immutable released reusable finite-mean contract can support the second aggregation step without adding another TEPP-local generic summation kernel. ## Two distinct estimands @@ -34,19 +34,36 @@ A different, equal-unit target is Every contributing unit receives equal final weight after its admitted pair rates are summarized within unit. The targets are not algebraically interchangeable when `k_i` varies. They may also target different populations when follow-up duration, observation count, missingness, or pair admissibility is associated with the longitudinal process. -The existing TEPP API has enough information to compute `theta_pair` after pairs are formed, but it does not retain unit identity in `LaggedWithinResidual`; it therefore cannot reconstruct `theta_unit` from that vector without changing the typed contract or preserving a unit-keyed aggregation boundary. +The current pair-average summary attributes candidate/contributing-unit denominators using the same deterministic `BTreeMap` unit order and consecutive-pair counts used by `center_within_unit_event_lags`. This is sufficient for denominator evidence for the existing pair-weighted target. It is deliberately not treated as the future equal-unit computation boundary: activating `unit_average.v1` must preserve unit identity through within-unit numerical aggregation instead of depending on flattened-pair reconstruction. ## Failure denominators are part of the estimand -Zero, opposite-sign, non-finite, or otherwise inadmissible residual pairs are not ordinary numerical observations. A longitudinal summary must report at least: +The Draft `IrregularRateSummary` reports: -- candidate units; -- units contributing one or more admitted lag pairs; +- candidate units with at least two admitted event-time occasions; +- units contributing at least one admitted scalar rate; - candidate consecutive pairs; - admitted pairs; -- refused pairs by typed reason. +- zero/opposite-sign refusals; +- same-sign pairs whose represented scalar log-rate is not admissible. -Dropping a refused pair changes `k_i` and can change both the pair-average weight and whether a unit contributes to a unit-average target. A unit with no admitted pairs must not disappear silently from a denominator whose interpretation says otherwise. The public contract therefore needs a declared estimand and failure-denominator policy before a result can be promoted as scientific evidence. +`refused_pairs()` is the sum of the two pair-level refusal classes. Non-finite input rows remain a payload-level admission failure before a scientific summary is constructed; they are not silently converted into missing pair observations. + +If no pair is numerically admissible after otherwise valid CWC/event-time admission, the summary returns `estimate = None` while retaining the complete unit/pair denominators. The legacy scalar recovery remains fail-closed for that case. This separation lets evidence reporting preserve its failure population without changing legacy scalar semantics. + +Dropping a refused pair changes `k_i` and can change both the pair-average weight and whether a unit contributes to a unit-average target. A unit with no admitted pairs must therefore not disappear silently from a denominator whose interpretation says otherwise. + +## Public contract evidence + +`crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs` fixes a deterministic unequal-pair-count fixture. One unit contributes three candidate pairs and another contributes two; three rates are admitted and two are refused by the sign/zero rule. The test establishes that: + +- `LagPairAverageV1` bit-matches the legacy pair-average scalar recovery; +- the pair-average differs from the independently computed equal-unit comparison when admitted pair counts differ; +- row permutation leaves the typed summary unchanged; +- a two-unit fixture with no admissible rates retains `candidate_units = 2`, `candidate_pairs = 2`, `admitted_pairs = 0`, `refused_pairs = 2`, and `estimate = None`; +- `UnitAverageV1` has a stable external name but fails closed while the owner mean release is unavailable. + +This evidence resolves the naming/denominator ambiguity for the pair-weighted first-release candidate. It does not claim equal-unit scientific acceptance and does not repair #310's independent mixed-sign binary64 mean RED. ## Relation to unequal and informative cluster size @@ -54,11 +71,11 @@ This is not a cluster-randomized treatment-effect model. However, the statistica Wang, Kong, and Datta (2011) study clustered longitudinal data and show that informative cluster size can invalidate ordinary marginal inference when cluster size is related to the outcome distribution. Huang (2011) further shows that the appropriate weights depend on the population of interest and on within-cluster covariate structure. Kahan et al. (2023) give a clear modern estimand distinction between equal participant weighting and equal cluster weighting. TEPP does not import their treatment-effect estimands; it imports the narrower methodological requirement that aggregation weights are part of estimand identity. -## Required product contract +## Decision and documentation boundary -Issue #495 owns the follow-up gap. Before any equal-unit result is implemented or any current pair-average result is described as an average unit/person effect: +Issue #495 remains open because a branch-local typed API is not the complete scientific acceptance package. Before buyer-facing promotion: -1. PRD/TRD/ADR/TRACEABILITY must name the target (`lag_pair_average`, `unit_average`, or a separately justified design-weighted target). +1. The first released target must be named consistently in PRD/TRD/TRACEABILITY and a superseding scientific-estimand ADR. The current ADR directory already contains historical number collisions, so this Draft does not mint another potentially colliding ADR identifier; canonical documentation ownership must allocate and repair that identity before acceptance. 2. A unit-average path must preserve unit identity until within-unit rates are summarized; occasion count cannot stand in for an externally defined design or membership weight. 3. Known-truth acceptance must include balanced and highly unbalanced occasion counts, informative missing/follow-up patterns, irregular intervals, row permutation, worker-count determinism, and explicit failure denominators. 4. Cross-classified and multiple-membership extensions must retain their declared membership structure and must not collapse to a primary group merely to obtain one scalar weight. From 079c6dab943a06fa78b7afec3aeb5024d4948397 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 05:07:36 +0900 Subject: [PATCH 249/296] fix(longitudinal): document stable irregular-rate exports --- crates/longitudinal_core/src/stable_irregular_rate.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/longitudinal_core/src/stable_irregular_rate.rs b/crates/longitudinal_core/src/stable_irregular_rate.rs index 4d6589237..fb1e33ea3 100644 --- a/crates/longitudinal_core/src/stable_irregular_rate.rs +++ b/crates/longitudinal_core/src/stable_irregular_rate.rs @@ -6,13 +6,11 @@ //! This module is only the crate-public facade and must not carry a second //! floating-point implementation. -/// Crate-public irregular residual log-rate entry points. -/// -/// Re-exports the legacy pair-average scalar functions and the versioned, -/// denominator-bearing estimand contract. +/// Versioned, denominator-bearing irregular-rate estimand entry points. pub use crate::irregular_rate_estimand::{ IrregularRateEstimand, IrregularRateSummary, recover_within_unit_irregular_rate_summary, }; +/// Legacy scalar irregular-rate entry points retained for compatibility. pub use crate::irregular_residual::{ recover_centered_irregular_residual_log_rate, recover_within_unit_irregular_residual_log_rate, }; From 1160313c6585f957d6549126ffdd8631733b03ca Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 05:07:54 +0900 Subject: [PATCH 250/296] fix(longitudinal): apply Rust formatting to estimand contract --- crates/longitudinal_core/src/irregular_rate_estimand.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/longitudinal_core/src/irregular_rate_estimand.rs b/crates/longitudinal_core/src/irregular_rate_estimand.rs index edbb14884..c11c18ad4 100644 --- a/crates/longitudinal_core/src/irregular_rate_estimand.rs +++ b/crates/longitudinal_core/src/irregular_rate_estimand.rs @@ -9,11 +9,11 @@ use std::collections::BTreeMap; +use crate::LongitudinalError; use crate::irregular_residual::{ EventTimedObservation, center_within_unit_event_lags, driver_same_sign_log_rate, same_sign_nonzero, scaled_compensated_mean, }; -use crate::LongitudinalError; /// Versioned scientific weighting target for irregular residual log-rate summaries. #[derive(Clone, Copy, Debug, Eq, PartialEq)] From 19eed1dc3cb370fa7d06caa1eb5b0da2d6c9fdd0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 05:08:09 +0900 Subject: [PATCH 251/296] fix(longitudinal): apply Rust formatting to estimand tests --- .../tests/irregular_rate_estimand_contract.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs index 60738f5c0..bd47aa500 100644 --- a/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs +++ b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs @@ -24,11 +24,9 @@ fn unequal_pair_count_rows() -> [EventTimedObservation; 7] { #[test] fn lag_pair_average_reports_estimand_and_failure_denominators() { let rows = unequal_pair_count_rows(); - let summary = recover_within_unit_irregular_rate_summary( - &rows, - IrregularRateEstimand::LagPairAverageV1, - ) - .expect("pair-average evidence"); + let summary = + recover_within_unit_irregular_rate_summary(&rows, IrregularRateEstimand::LagPairAverageV1) + .expect("pair-average evidence"); assert_eq!( summary.estimand().wire_name(), @@ -94,11 +92,9 @@ fn zero_admissible_rates_still_return_explicit_pair_denominator() { timed(2, 0.0, 2.0), timed(2, 1.0, -2.0), ]; - let summary = recover_within_unit_irregular_rate_summary( - &rows, - IrregularRateEstimand::LagPairAverageV1, - ) - .expect("denominator evidence survives zero admissible rates"); + let summary = + recover_within_unit_irregular_rate_summary(&rows, IrregularRateEstimand::LagPairAverageV1) + .expect("denominator evidence survives zero admissible rates"); assert_eq!(summary.estimate(), None); assert_eq!(summary.candidate_units(), 2); From 8020cd69ef9475e284356817f0550acdefddc5a5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 05:10:15 +0900 Subject: [PATCH 252/296] fix(longitudinal): remove duplicate private estimand re-export --- crates/longitudinal_core/src/stable_irregular_rate.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/crates/longitudinal_core/src/stable_irregular_rate.rs b/crates/longitudinal_core/src/stable_irregular_rate.rs index fb1e33ea3..3eabc56d3 100644 --- a/crates/longitudinal_core/src/stable_irregular_rate.rs +++ b/crates/longitudinal_core/src/stable_irregular_rate.rs @@ -2,14 +2,9 @@ //! //! Numerical ownership lives in [`crate::irregular_residual`], alongside event //! ordering, CWC admission, and the shared same-sign log-rate primitive. The -//! weighting/denominator contract lives in [`crate::irregular_rate_estimand`]. -//! This module is only the crate-public facade and must not carry a second -//! floating-point implementation. +//! typed weighting/denominator contract is exported directly from the crate root +//! and is not re-exported through this private compatibility facade. -/// Versioned, denominator-bearing irregular-rate estimand entry points. -pub use crate::irregular_rate_estimand::{ - IrregularRateEstimand, IrregularRateSummary, recover_within_unit_irregular_rate_summary, -}; /// Legacy scalar irregular-rate entry points retained for compatibility. pub use crate::irregular_residual::{ recover_centered_irregular_residual_log_rate, recover_within_unit_irregular_residual_log_rate, From 432da36b44af921557f6cb92f4a71b0c8a56105d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 05:10:49 +0900 Subject: [PATCH 253/296] feat(longitudinal): distinguish unavailable estimand capability --- crates/longitudinal_core/src/error.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/longitudinal_core/src/error.rs b/crates/longitudinal_core/src/error.rs index 5f52ba837..3240db0f1 100644 --- a/crates/longitudinal_core/src/error.rs +++ b/crates/longitudinal_core/src/error.rs @@ -18,6 +18,8 @@ pub enum LongitudinalError { InvalidTemporalAssociationInput, /// A temporal transform input or intermediate value was not representable. InvalidTemporalTransformInput, + /// The requested irregular-rate estimand is typed but not yet activated. + IrregularRateEstimandUnavailable, /// At least one marginal variance was not strictly positive. NonPositiveMarginalVariance, /// The event-time interval was not strictly positive. @@ -55,6 +57,9 @@ impl fmt::Display for LongitudinalError { Self::InvalidObservationPayload => "invalid longitudinal observation payload", Self::InvalidTemporalAssociationInput => "invalid temporal association input", Self::InvalidTemporalTransformInput => "invalid temporal transform input", + Self::IrregularRateEstimandUnavailable => { + "requested irregular-rate estimand is not yet available" + } Self::NonPositiveMarginalVariance => { "temporal correlation requires strictly positive marginal variances" } @@ -132,6 +137,10 @@ mod tests { LongitudinalError::InvalidTemporalTransformInput, "invalid temporal transform input", ), + ( + LongitudinalError::IrregularRateEstimandUnavailable, + "requested irregular-rate estimand is not yet available", + ), ( LongitudinalError::NonPositiveMarginalVariance, "temporal correlation requires strictly positive marginal variances", From 330d67f2789b108754ca24c2d20ecd6e0f966f6c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 05:11:11 +0900 Subject: [PATCH 254/296] fix(longitudinal): fail closed with typed estimand error --- crates/longitudinal_core/src/irregular_rate_estimand.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/longitudinal_core/src/irregular_rate_estimand.rs b/crates/longitudinal_core/src/irregular_rate_estimand.rs index c11c18ad4..89d6a627f 100644 --- a/crates/longitudinal_core/src/irregular_rate_estimand.rs +++ b/crates/longitudinal_core/src/irregular_rate_estimand.rs @@ -125,16 +125,16 @@ impl IrregularRateSummary { /// # Errors /// /// Propagates event-time/CWC admission failures. `UnitAverageV1` returns -/// [`LongitudinalError::InvalidTemporalTransformInput`] while the released +/// [`LongitudinalError::IrregularRateEstimandUnavailable`] while the released /// reusable mean contract is unavailable. The pair-average numerical mean can -/// return the same error if its currently shared compatibility arithmetic cannot -/// represent the final admitted-pair mean. +/// return [`LongitudinalError::InvalidTemporalTransformInput`] if its currently +/// shared compatibility arithmetic cannot represent the final admitted-pair mean. pub fn recover_within_unit_irregular_rate_summary( rows: &[EventTimedObservation], estimand: IrregularRateEstimand, ) -> Result { if estimand == IrregularRateEstimand::UnitAverageV1 { - return Err(LongitudinalError::InvalidTemporalTransformInput); + return Err(LongitudinalError::IrregularRateEstimandUnavailable); } let lagged = center_within_unit_event_lags(rows)?; From 8e579512c80e9544d594261186f279d9d896c5e2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 05:11:28 +0900 Subject: [PATCH 255/296] test(longitudinal): require typed unavailable-estimand refusal --- .../longitudinal_core/tests/irregular_rate_estimand_contract.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs index bd47aa500..a3bba5b0a 100644 --- a/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs +++ b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs @@ -115,6 +115,6 @@ fn unit_average_is_versioned_but_fails_closed_until_owner_mean_release() { ); assert_eq!( recover_within_unit_irregular_rate_summary(&rows, IrregularRateEstimand::UnitAverageV1), - Err(LongitudinalError::InvalidTemporalTransformInput) + Err(LongitudinalError::IrregularRateEstimandUnavailable) ); } From 32729ac69562a148a4f55654f48ca6a6ce02d732 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 05:13:42 +0900 Subject: [PATCH 256/296] test(longitudinal): cover nonrepresentable-rate refusal denominator --- .../tests/irregular_rate_estimand_contract.rs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs index a3bba5b0a..43a9d72fe 100644 --- a/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs +++ b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs @@ -106,6 +106,32 @@ fn zero_admissible_rates_still_return_explicit_pair_denominator() { assert_eq!(summary.refused_pairs(), 2); } +#[test] +fn nonrepresentable_same_sign_rate_has_its_own_failure_denominator() { + let adjacent_one = f64::from_bits(1.0_f64.to_bits() + 1); + let rows = [ + timed(1, 0.0, 1.0), + timed(1, f64::MAX / 2.0, adjacent_one), + timed(1, f64::MAX, -2.0), + timed(2, 0.0, 3.0), + timed(2, 1.0, 1.0), + timed(2, 2.0, -4.0), + ]; + + let summary = + recover_within_unit_irregular_rate_summary(&rows, IrregularRateEstimand::LagPairAverageV1) + .expect("pair-level transform refusal remains reportable evidence"); + + assert_eq!(summary.candidate_units(), 2); + assert_eq!(summary.candidate_pairs(), 4); + assert_eq!(summary.admitted_pairs(), 1); + assert_eq!(summary.sign_or_zero_refused_pairs(), 2); + assert_eq!(summary.nonrepresentable_rate_refused_pairs(), 1); + assert_eq!(summary.refused_pairs(), 3); + assert_eq!(summary.contributing_units(), 1); + assert!(summary.estimate().is_some()); +} + #[test] fn unit_average_is_versioned_but_fails_closed_until_owner_mean_release() { let rows = unequal_pair_count_rows(); From 851fadc4e7a01bf6c1f0aeaed3a966d38a0c999c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 05:14:12 +0900 Subject: [PATCH 257/296] docs(longitudinal): currentize estimand failure evidence --- docs/research/irregular-rate-estimand-weighting.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/research/irregular-rate-estimand-weighting.md b/docs/research/irregular-rate-estimand-weighting.md index d5121585f..2dcbd4bd6 100644 --- a/docs/research/irregular-rate-estimand-weighting.md +++ b/docs/research/irregular-rate-estimand-weighting.md @@ -4,11 +4,11 @@ This note records a Longitudinal Modeling estimand boundary exposed by TEPP PR #310 and issue #495. The branch now contains a typed first-release candidate contract, but it remains Draft and is not protected-main or release authority. -Scientific finding head: `df0b4d6d3e0622de3c988b840114fbdb41e5d1b0`. Typed-contract implementation lineage starts at `c7f55acb347ceac38675d9566eff767372739dba`; public contract tests start at `f45e83ba5223ad9d89022482e759ad9e705b229c` and refusal-class denominators are completed by `a3065f6d7589d5a310e54f85d6447b27414955ba`. +Scientific finding head: `df0b4d6d3e0622de3c988b840114fbdb41e5d1b0`. Typed-contract implementation lineage starts at `c7f55acb347ceac38675d9566eff767372739dba`; public contract tests start at `f45e83ba5223ad9d89022482e759ad9e705b229c`; refusal-class denominators are completed by `a3065f6d7589d5a310e54f85d6447b27414955ba`; the explicit unavailable-estimand error boundary lands through `432da36b44af921557f6cb92f4a71b0c8a56105d` and `8e579512c80e9544d594261186f279d9d896c5e2`; a represented same-sign rate-refusal denominator is exercised by `32729ac69562a148a4f55654f48ca6a6ce02d732`. `center_within_unit_event_lags` forms one consecutive event-time lag pair for each admitted adjacent occasion inside a unit. `LaggedWithinResidual` carries the earlier residual, later residual, and typed event interval but not the originating unit identity. The existing scalar recovery therefore computes a lag-pair-average estimand: every admissible pair enters one common rate vector before averaging. -The Draft candidate now names that target explicitly as `tepp.irregular_rate.lag_pair_average.v1`. `tepp.irregular_rate.unit_average.v1` is also a typed name, but it fails closed until an immutable released reusable finite-mean contract can support the second aggregation step without adding another TEPP-local generic summation kernel. +The Draft candidate now names that target explicitly as `tepp.irregular_rate.lag_pair_average.v1`. `tepp.irregular_rate.unit_average.v1` is also a typed name, but it fails closed with `LongitudinalError::IrregularRateEstimandUnavailable` until an immutable released reusable finite-mean contract can support the second aggregation step without adding another TEPP-local generic summation kernel. A scientifically valid but not-yet-activated estimand request is therefore not misclassified as malformed temporal input. ## Two distinct estimands @@ -49,19 +49,22 @@ The Draft `IrregularRateSummary` reports: `refused_pairs()` is the sum of the two pair-level refusal classes. Non-finite input rows remain a payload-level admission failure before a scientific summary is constructed; they are not silently converted into missing pair observations. +The second refusal class matters separately from sign/zero exclusion. A pair can contain two finite, nonzero, same-sign centered residuals and still fail represented-rate admission: for example, adjacent residual magnitudes over an extreme but finite event-time interval can imply a nonzero real log-rate whose binary64 quotient collapses to zero. That pair is counted in `nonrepresentable_rate_refused_pairs()`, not silently erased or conflated with an opposite-sign transition. + If no pair is numerically admissible after otherwise valid CWC/event-time admission, the summary returns `estimate = None` while retaining the complete unit/pair denominators. The legacy scalar recovery remains fail-closed for that case. This separation lets evidence reporting preserve its failure population without changing legacy scalar semantics. Dropping a refused pair changes `k_i` and can change both the pair-average weight and whether a unit contributes to a unit-average target. A unit with no admitted pairs must therefore not disappear silently from a denominator whose interpretation says otherwise. ## Public contract evidence -`crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs` fixes a deterministic unequal-pair-count fixture. One unit contributes three candidate pairs and another contributes two; three rates are admitted and two are refused by the sign/zero rule. The test establishes that: +`crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs` fixes deterministic fixtures for unequal pair counts and pair-level refusal classes. The contract establishes that: - `LagPairAverageV1` bit-matches the legacy pair-average scalar recovery; - the pair-average differs from the independently computed equal-unit comparison when admitted pair counts differ; - row permutation leaves the typed summary unchanged; - a two-unit fixture with no admissible rates retains `candidate_units = 2`, `candidate_pairs = 2`, `admitted_pairs = 0`, `refused_pairs = 2`, and `estimate = None`; -- `UnitAverageV1` has a stable external name but fails closed while the owner mean release is unavailable. +- a finite same-sign adjacent-magnitude pair over an extreme finite interval increments `nonrepresentable_rate_refused_pairs()` while a second unit supplies an admitted rate, proving that this failure denominator is observable without turning the whole evidence summary into an error; +- `UnitAverageV1` has a stable external name and fails closed with `IrregularRateEstimandUnavailable` while the owner mean release is unavailable. This evidence resolves the naming/denominator ambiguity for the pair-weighted first-release candidate. It does not claim equal-unit scientific acceptance and does not repair #310's independent mixed-sign binary64 mean RED. From f2d31ac6120de7a2be235df2f5809556060d0985 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 05:15:29 +0900 Subject: [PATCH 258/296] test(longitudinal): make rate-refusal interval truly nonrepresentable --- .../tests/irregular_rate_estimand_contract.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs index 43a9d72fe..464d6100c 100644 --- a/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs +++ b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs @@ -110,8 +110,8 @@ fn zero_admissible_rates_still_return_explicit_pair_denominator() { fn nonrepresentable_same_sign_rate_has_its_own_failure_denominator() { let adjacent_one = f64::from_bits(1.0_f64.to_bits() + 1); let rows = [ - timed(1, 0.0, 1.0), - timed(1, f64::MAX / 2.0, adjacent_one), + timed(1, -f64::MAX, 1.0), + timed(1, 0.0, adjacent_one), timed(1, f64::MAX, -2.0), timed(2, 0.0, 3.0), timed(2, 1.0, 1.0), From 83caf52368b898944c09252a0a71eb8e7fe3e2ee Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 05:16:01 +0900 Subject: [PATCH 259/296] docs(longitudinal): point refusal evidence at verified fixture --- docs/research/irregular-rate-estimand-weighting.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/research/irregular-rate-estimand-weighting.md b/docs/research/irregular-rate-estimand-weighting.md index 2dcbd4bd6..32e62f4db 100644 --- a/docs/research/irregular-rate-estimand-weighting.md +++ b/docs/research/irregular-rate-estimand-weighting.md @@ -4,7 +4,7 @@ This note records a Longitudinal Modeling estimand boundary exposed by TEPP PR #310 and issue #495. The branch now contains a typed first-release candidate contract, but it remains Draft and is not protected-main or release authority. -Scientific finding head: `df0b4d6d3e0622de3c988b840114fbdb41e5d1b0`. Typed-contract implementation lineage starts at `c7f55acb347ceac38675d9566eff767372739dba`; public contract tests start at `f45e83ba5223ad9d89022482e759ad9e705b229c`; refusal-class denominators are completed by `a3065f6d7589d5a310e54f85d6447b27414955ba`; the explicit unavailable-estimand error boundary lands through `432da36b44af921557f6cb92f4a71b0c8a56105d` and `8e579512c80e9544d594261186f279d9d896c5e2`; a represented same-sign rate-refusal denominator is exercised by `32729ac69562a148a4f55654f48ca6a6ce02d732`. +Scientific finding head: `df0b4d6d3e0622de3c988b840114fbdb41e5d1b0`. Typed-contract implementation lineage starts at `c7f55acb347ceac38675d9566eff767372739dba`; public contract tests start at `f45e83ba5223ad9d89022482e759ad9e705b229c`; refusal-class denominators are completed by `a3065f6d7589d5a310e54f85d6447b27414955ba`; the explicit unavailable-estimand error boundary lands through `432da36b44af921557f6cb92f4a71b0c8a56105d` and `8e579512c80e9544d594261186f279d9d896c5e2`; a represented same-sign rate-refusal denominator is exercised by the corrected extreme-interval fixture at `f2d31ac6120de7a2be235df2f5809556060d0985`. `center_within_unit_event_lags` forms one consecutive event-time lag pair for each admitted adjacent occasion inside a unit. `LaggedWithinResidual` carries the earlier residual, later residual, and typed event interval but not the originating unit identity. The existing scalar recovery therefore computes a lag-pair-average estimand: every admissible pair enters one common rate vector before averaging. @@ -63,7 +63,7 @@ Dropping a refused pair changes `k_i` and can change both the pair-average weigh - the pair-average differs from the independently computed equal-unit comparison when admitted pair counts differ; - row permutation leaves the typed summary unchanged; - a two-unit fixture with no admissible rates retains `candidate_units = 2`, `candidate_pairs = 2`, `admitted_pairs = 0`, `refused_pairs = 2`, and `estimate = None`; -- a finite same-sign adjacent-magnitude pair over an extreme finite interval increments `nonrepresentable_rate_refused_pairs()` while a second unit supplies an admitted rate, proving that this failure denominator is observable without turning the whole evidence summary into an error; +- a finite same-sign adjacent-magnitude pair over an event interval of `f64::MAX` increments `nonrepresentable_rate_refused_pairs()` while a second unit supplies an admitted rate, proving that this failure denominator is observable without turning the whole evidence summary into an error; - `UnitAverageV1` has a stable external name and fails closed with `IrregularRateEstimandUnavailable` while the owner mean release is unavailable. This evidence resolves the naming/denominator ambiguity for the pair-weighted first-release candidate. It does not claim equal-unit scientific acceptance and does not repair #310's independent mixed-sign binary64 mean RED. From b3751f8aabe6279ded05cceb835f09d22fffe34d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 05:18:10 +0900 Subject: [PATCH 260/296] fix(longitudinal): use overflow-safe midpoint in estimand oracle --- .../tests/irregular_rate_estimand_contract.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs index 464d6100c..399378192 100644 --- a/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs +++ b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs @@ -48,9 +48,9 @@ fn lag_pair_average_reports_estimand_and_failure_denominators() { let legacy = recover_within_unit_irregular_residual_log_rate(&rows).expect("legacy pair mean"); assert_eq!(pair_average.to_bits(), legacy.to_bits()); - let unit_one = ((2.0_f64 / 3.0).ln() + (1.0_f64 / 2.0).ln()) / 2.0; + let unit_one = f64::midpoint((2.0_f64 / 3.0).ln(), (1.0_f64 / 2.0).ln()); let unit_two = (1.0_f64 / 3.0).ln(); - let equal_unit_comparison = (unit_one + unit_two) / 2.0; + let equal_unit_comparison = f64::midpoint(unit_one, unit_two); assert!( (pair_average - equal_unit_comparison).abs() > 1.0e-3, "unequal admitted-pair counts must make pair and equal-unit targets distinguishable" From 7373eef9ed129db09556ca104c4fa988b34a490a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 05:32:31 +0900 Subject: [PATCH 261/296] test(longitudinal): pin pair-weight multiplicity estimand --- .../tests/irregular_rate_estimand_contract.rs | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs index 399378192..cef47d4c6 100644 --- a/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs +++ b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs @@ -57,6 +57,60 @@ fn lag_pair_average_reports_estimand_and_failure_denominators() { ); } +#[test] +fn extra_admitted_pair_changes_pair_weight_without_changing_unit_target() { + let base = unequal_pair_count_rows(); + let extended = [ + base[0], + base[1], + base[2], + base[3], + base[4], + base[5], + base[6], + timed(2, 3.0, -4.0 / 3.0), + timed(2, 4.0, 4.0 / 3.0), + ]; + + let base_summary = recover_within_unit_irregular_rate_summary( + &base, + IrregularRateEstimand::LagPairAverageV1, + ) + .expect("base pair-average evidence"); + let extended_summary = recover_within_unit_irregular_rate_summary( + &extended, + IrregularRateEstimand::LagPairAverageV1, + ) + .expect("extended pair-average evidence"); + + assert_eq!(base_summary.admitted_pairs(), 3); + assert_eq!(extended_summary.admitted_pairs(), 4); + assert_eq!(base_summary.candidate_pairs(), 5); + assert_eq!(extended_summary.candidate_pairs(), 7); + assert_eq!(base_summary.refused_pairs(), 2); + assert_eq!(extended_summary.refused_pairs(), 3); + + let base_pair_average = base_summary.estimate().expect("base estimate"); + let extended_pair_average = extended_summary.estimate().expect("extended estimate"); + assert!( + extended_pair_average < base_pair_average, + "an extra admitted ln(1/3) rate must shift the pair-weighted target toward unit two" + ); + assert!( + (extended_pair_average - base_pair_average).abs() > 0.05, + "the multiplicity perturbation must remain scientifically visible" + ); + + let unit_one = f64::midpoint((2.0_f64 / 3.0).ln(), (1.0_f64 / 2.0).ln()); + let unit_two_base = (1.0_f64 / 3.0).ln(); + let unit_two_extended = f64::midpoint(unit_two_base, unit_two_base); + assert_eq!(unit_two_extended.to_bits(), unit_two_base.to_bits()); + + let equal_unit_base = f64::midpoint(unit_one, unit_two_base); + let equal_unit_extended = f64::midpoint(unit_one, unit_two_extended); + assert_eq!(equal_unit_extended.to_bits(), equal_unit_base.to_bits()); +} + #[test] fn lag_pair_average_is_invariant_to_input_row_permutation() { let canonical = unequal_pair_count_rows(); From 0c7f45f2f790292dc341d00dfa60b227cf2bb2fd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 05:34:51 +0900 Subject: [PATCH 262/296] fix(longitudinal): format multiplicity estimand fixture --- .../tests/irregular_rate_estimand_contract.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs index cef47d4c6..b60472587 100644 --- a/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs +++ b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs @@ -72,11 +72,9 @@ fn extra_admitted_pair_changes_pair_weight_without_changing_unit_target() { timed(2, 4.0, 4.0 / 3.0), ]; - let base_summary = recover_within_unit_irregular_rate_summary( - &base, - IrregularRateEstimand::LagPairAverageV1, - ) - .expect("base pair-average evidence"); + let base_summary = + recover_within_unit_irregular_rate_summary(&base, IrregularRateEstimand::LagPairAverageV1) + .expect("base pair-average evidence"); let extended_summary = recover_within_unit_irregular_rate_summary( &extended, IrregularRateEstimand::LagPairAverageV1, From 07b7308d85b279ff5fa494cbd8cadc167e464e72 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 05:37:06 +0900 Subject: [PATCH 263/296] test(longitudinal): add pair-estimand known-truth fixtures --- .../tests/irregular_rate_estimand_contract.rs | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs index b60472587..a9b981b02 100644 --- a/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs +++ b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs @@ -109,6 +109,84 @@ fn extra_admitted_pair_changes_pair_weight_without_changing_unit_target() { assert_eq!(equal_unit_extended.to_bits(), equal_unit_base.to_bits()); } +#[test] +fn balanced_follow_up_recovers_declared_pair_weighted_truth() { + let rows = [ + timed(1, 0.0, -8.0), + timed(1, 1.0, -4.0), + timed(1, 2.0, -2.0), + timed(1, 3.0, 14.0), + timed(2, 0.0, -27.0), + timed(2, 1.0, -9.0), + timed(2, 2.0, -3.0), + timed(2, 3.0, 39.0), + ]; + let summary = + recover_within_unit_irregular_rate_summary(&rows, IrregularRateEstimand::LagPairAverageV1) + .expect("balanced known-truth evidence"); + + assert_eq!(summary.candidate_units(), 2); + assert_eq!(summary.contributing_units(), 2); + assert_eq!(summary.candidate_pairs(), 6); + assert_eq!(summary.admitted_pairs(), 4); + assert_eq!(summary.refused_pairs(), 2); + + let half_rate = 0.5_f64.ln(); + let third_rate = (1.0_f64 / 3.0).ln(); + let expected = f64::midpoint(half_rate, third_rate); + let actual = summary.estimate().expect("balanced pair target"); + assert!( + (actual - expected).abs() <= 8.0 * f64::EPSILON, + "balanced follow-up must recover the equal pair-weighted known truth" + ); +} + +#[test] +fn rate_associated_highly_unbalanced_follow_up_recovers_pair_weighted_truth() { + let mut rows = Vec::with_capacity(21); + for (event_index, exponent) in (1..=16).rev().enumerate() { + rows.push(timed( + 1, + event_index as f64, + -2.0_f64.powi(exponent), + )); + } + rows.push(timed(1, 16.0, 131_070.0)); + rows.extend([ + timed(2, 0.0, -27.0), + timed(2, 1.0, -9.0), + timed(2, 2.0, -3.0), + timed(2, 3.0, 39.0), + ]); + + let summary = recover_within_unit_irregular_rate_summary( + &rows, + IrregularRateEstimand::LagPairAverageV1, + ) + .expect("rate-associated follow-up evidence"); + + assert_eq!(summary.candidate_units(), 2); + assert_eq!(summary.contributing_units(), 2); + assert_eq!(summary.candidate_pairs(), 19); + assert_eq!(summary.admitted_pairs(), 17); + assert_eq!(summary.refused_pairs(), 2); + + let half_rate = 0.5_f64.ln(); + let third_rate = (1.0_f64 / 3.0).ln(); + let expected_pair_target = (15.0 * half_rate + 2.0 * third_rate) / 17.0; + let equal_unit_target = f64::midpoint(half_rate, third_rate); + let actual = summary.estimate().expect("unbalanced pair target"); + + assert!( + (actual - expected_pair_target).abs() <= 16.0 * f64::EPSILON, + "declared pair weighting must recover the known 15:2 follow-up target" + ); + assert!( + (actual - equal_unit_target).abs() > 0.1, + "rate-associated follow-up must not silently masquerade as equal-unit weighting" + ); +} + #[test] fn lag_pair_average_is_invariant_to_input_row_permutation() { let canonical = unequal_pair_count_rows(); From 365a342d8a2ece32bc627f85812d18f38925052a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 05:37:57 +0900 Subject: [PATCH 264/296] fix(longitudinal): format known-truth estimand fixtures --- .../tests/irregular_rate_estimand_contract.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs index a9b981b02..a53fcbe02 100644 --- a/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs +++ b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs @@ -145,11 +145,7 @@ fn balanced_follow_up_recovers_declared_pair_weighted_truth() { fn rate_associated_highly_unbalanced_follow_up_recovers_pair_weighted_truth() { let mut rows = Vec::with_capacity(21); for (event_index, exponent) in (1..=16).rev().enumerate() { - rows.push(timed( - 1, - event_index as f64, - -2.0_f64.powi(exponent), - )); + rows.push(timed(1, event_index as f64, -2.0_f64.powi(exponent))); } rows.push(timed(1, 16.0, 131_070.0)); rows.extend([ @@ -159,11 +155,9 @@ fn rate_associated_highly_unbalanced_follow_up_recovers_pair_weighted_truth() { timed(2, 3.0, 39.0), ]); - let summary = recover_within_unit_irregular_rate_summary( - &rows, - IrregularRateEstimand::LagPairAverageV1, - ) - .expect("rate-associated follow-up evidence"); + let summary = + recover_within_unit_irregular_rate_summary(&rows, IrregularRateEstimand::LagPairAverageV1) + .expect("rate-associated follow-up evidence"); assert_eq!(summary.candidate_units(), 2); assert_eq!(summary.contributing_units(), 2); From ec718201ac8e21e2ae4f33170f18be3bccd0df25 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 05:39:53 +0900 Subject: [PATCH 265/296] fix(longitudinal): avoid lossy test event-time cast --- .../tests/irregular_rate_estimand_contract.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs index a53fcbe02..31dc2a091 100644 --- a/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs +++ b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs @@ -144,10 +144,12 @@ fn balanced_follow_up_recovers_declared_pair_weighted_truth() { #[test] fn rate_associated_highly_unbalanced_follow_up_recovers_pair_weighted_truth() { let mut rows = Vec::with_capacity(21); - for (event_index, exponent) in (1..=16).rev().enumerate() { - rows.push(timed(1, event_index as f64, -2.0_f64.powi(exponent))); + let mut event_time = 0.0; + for exponent in (1..=16).rev() { + rows.push(timed(1, event_time, -2.0_f64.powi(exponent))); + event_time += 1.0; } - rows.push(timed(1, 16.0, 131_070.0)); + rows.push(timed(1, event_time, 131_070.0)); rows.extend([ timed(2, 0.0, -27.0), timed(2, 1.0, -9.0), From 82037e11842151a511f47c8c40f32fa1f55de9f3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 05:49:56 +0900 Subject: [PATCH 266/296] test(longitudinal): recover pair estimand under informative missingness --- .../tests/irregular_rate_estimand_contract.rs | 242 ++++++++++++++++++ 1 file changed, 242 insertions(+) diff --git a/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs index 31dc2a091..762fde82c 100644 --- a/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs +++ b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs @@ -21,6 +21,193 @@ fn unequal_pair_count_rows() -> [EventTimedObservation; 7] { ] } +struct SplitMix64 { + state: u64, +} + +impl SplitMix64 { + fn new(seed: u64) -> Self { + Self { state: seed } + } + + fn next_u64(&mut self) -> u64 { + self.state = self.state.wrapping_add(0x9E37_79B9_7F4A_7C15); + let mut value = self.state; + value = (value ^ (value >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9); + value = (value ^ (value >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB); + value ^ (value >> 31) + } +} + +fn append_missingness_unit( + rows: &mut Vec, + rng: &mut SplitMix64, + unit: u32, + log_rate: f64, + retain_three_of_four: bool, +) -> usize { + let mut event_time = 0.0; + let first_score = -1.0; + let mut negative_sum = first_score; + let mut negative_count = 1_usize; + rows.push(timed(unit, event_time, first_score)); + + for _ in 1..12 { + event_time += 1.0; + let draw = rng.next_u64(); + let retain = if retain_three_of_four { + draw & 3 != 0 + } else { + draw & 3 == 0 + }; + if retain { + let score = -(log_rate * event_time).exp(); + rows.push(timed(unit, event_time, score)); + negative_sum += score; + negative_count += 1; + } + } + + event_time += 1.0; + let endpoint_score = -(log_rate * event_time).exp(); + rows.push(timed(unit, event_time, endpoint_score)); + negative_sum += endpoint_score; + negative_count += 1; + + rows.push(timed(unit, event_time + 1.0, -negative_sum)); + negative_count - 1 +} + +#[derive(Clone, Copy, Debug)] +struct MissingnessRecoveryEvidence { + attempted_replicates: usize, + recovered_replicates: usize, + failed_replicates: usize, + half_rate_admitted_pairs: usize, + third_rate_admitted_pairs: usize, + bias: f64, + rmse: f64, + max_absolute_error: f64, + first_quarter_rmse: f64, + recovery_coverage: f64, + coverage_wilson_lower_95: f64, + bias_standard_error: f64, +} + +fn run_rate_associated_missingness_recovery(seed: u64) -> MissingnessRecoveryEvidence { + const REPLICATES: usize = 256; + const REPLICATES_F64: f64 = 256.0; + const FIRST_QUARTER_REPLICATES: usize = 64; + const FIRST_QUARTER_REPLICATES_F64: f64 = 64.0; + const RECOVERY_TOLERANCE: f64 = 1.0e-12; + + let half_rate = 0.5_f64.ln(); + let third_rate = (1.0_f64 / 3.0).ln(); + let mut rng = SplitMix64::new(seed); + let mut recovered_replicates = 0_usize; + let mut failed_replicates = 0_usize; + let mut half_rate_admitted_pairs = 0_usize; + let mut third_rate_admitted_pairs = 0_usize; + let mut error_sum = 0.0; + let mut squared_error_sum = 0.0; + let mut first_quarter_squared_error_sum = 0.0; + let mut max_absolute_error = 0.0_f64; + let mut covered_replicates = 0_usize; + let mut covered_mass = 0.0; + + for replicate_index in 0..REPLICATES { + let mut rows = Vec::with_capacity(32); + let half_pairs = append_missingness_unit(&mut rows, &mut rng, 1, half_rate, true); + let third_pairs = append_missingness_unit(&mut rows, &mut rng, 2, third_rate, false); + half_rate_admitted_pairs += half_pairs; + third_rate_admitted_pairs += third_pairs; + + let Ok(summary) = recover_within_unit_irregular_rate_summary( + &rows, + IrregularRateEstimand::LagPairAverageV1, + ) else { + failed_replicates += 1; + continue; + }; + let Some(estimate) = summary.estimate() else { + failed_replicates += 1; + continue; + }; + + assert_eq!(summary.candidate_units(), 2); + assert_eq!(summary.contributing_units(), 2); + assert_eq!(summary.admitted_pairs(), half_pairs + third_pairs); + assert_eq!(summary.refused_pairs(), 2); + assert_eq!( + summary.candidate_pairs(), + summary.admitted_pairs() + summary.refused_pairs() + ); + + let mut true_rate_sum = 0.0; + let mut true_rate_denominator = 0.0; + for _ in 0..half_pairs { + true_rate_sum += half_rate; + true_rate_denominator += 1.0; + } + for _ in 0..third_pairs { + true_rate_sum += third_rate; + true_rate_denominator += 1.0; + } + let true_pair_target = true_rate_sum / true_rate_denominator; + let error = estimate - true_pair_target; + let absolute_error = error.abs(); + + recovered_replicates += 1; + error_sum += error; + squared_error_sum += error * error; + if replicate_index < FIRST_QUARTER_REPLICATES { + first_quarter_squared_error_sum += error * error; + } + max_absolute_error = max_absolute_error.max(absolute_error); + if absolute_error <= RECOVERY_TOLERANCE { + covered_replicates += 1; + covered_mass += 1.0; + } + } + + let bias = error_sum / REPLICATES_F64; + let rmse = (squared_error_sum / REPLICATES_F64).sqrt(); + let first_quarter_rmse = + (first_quarter_squared_error_sum / FIRST_QUARTER_REPLICATES_F64).sqrt(); + let recovery_coverage = covered_mass / REPLICATES_F64; + let variance_numerator = (squared_error_sum - REPLICATES_F64 * bias * bias).max(0.0); + let bias_standard_error = + (variance_numerator / (REPLICATES_F64 - 1.0) / REPLICATES_F64).sqrt(); + + let z = 1.96_f64; + let z_squared = z * z; + let wilson_denominator = 1.0 + z_squared / REPLICATES_F64; + let wilson_center = + (recovery_coverage + z_squared / (2.0 * REPLICATES_F64)) / wilson_denominator; + let wilson_margin = z + * ((recovery_coverage * (1.0 - recovery_coverage) / REPLICATES_F64) + + z_squared / (4.0 * REPLICATES_F64 * REPLICATES_F64)) + .sqrt() + / wilson_denominator; + + assert_eq!(covered_replicates, recovered_replicates); + + MissingnessRecoveryEvidence { + attempted_replicates: REPLICATES, + recovered_replicates, + failed_replicates, + half_rate_admitted_pairs, + third_rate_admitted_pairs, + bias, + rmse, + max_absolute_error, + first_quarter_rmse, + recovery_coverage, + coverage_wilson_lower_95: wilson_center - wilson_margin, + bias_standard_error, + } +} + #[test] fn lag_pair_average_reports_estimand_and_failure_denominators() { let rows = unequal_pair_count_rows(); @@ -183,6 +370,61 @@ fn rate_associated_highly_unbalanced_follow_up_recovers_pair_weighted_truth() { ); } +#[test] +fn informative_missingness_recovery_is_reproducible_and_pair_weighted() { + let first = run_rate_associated_missingness_recovery(0x4950_2026_0912); + let replay = run_rate_associated_missingness_recovery(0x4950_2026_0912); + + assert_eq!(first.attempted_replicates, 256); + assert_eq!(first.recovered_replicates, 256); + assert_eq!(first.failed_replicates, 0); + assert!( + first.half_rate_admitted_pairs > 2 * first.third_rate_admitted_pairs, + "retention depends on the true rate and must create informative pair multiplicity" + ); + assert!(first.bias.abs() <= 1.0e-13); + assert!(first.rmse <= 3.0e-13); + assert!(first.max_absolute_error <= 1.0e-12); + assert!(first.recovery_coverage >= 0.999); + assert!(first.coverage_wilson_lower_95 > 0.98); + assert!(first.bias.abs() <= 3.0 * first.bias_standard_error + 1.0e-15); + assert!(first.rmse <= 2.0 * first.first_quarter_rmse + 1.0e-15); + + assert_eq!(first.attempted_replicates, replay.attempted_replicates); + assert_eq!(first.recovered_replicates, replay.recovered_replicates); + assert_eq!(first.failed_replicates, replay.failed_replicates); + assert_eq!( + first.half_rate_admitted_pairs, + replay.half_rate_admitted_pairs + ); + assert_eq!( + first.third_rate_admitted_pairs, + replay.third_rate_admitted_pairs + ); + assert_eq!(first.bias.to_bits(), replay.bias.to_bits()); + assert_eq!(first.rmse.to_bits(), replay.rmse.to_bits()); + assert_eq!( + first.max_absolute_error.to_bits(), + replay.max_absolute_error.to_bits() + ); + assert_eq!( + first.first_quarter_rmse.to_bits(), + replay.first_quarter_rmse.to_bits() + ); + assert_eq!( + first.recovery_coverage.to_bits(), + replay.recovery_coverage.to_bits() + ); + assert_eq!( + first.coverage_wilson_lower_95.to_bits(), + replay.coverage_wilson_lower_95.to_bits() + ); + assert_eq!( + first.bias_standard_error.to_bits(), + replay.bias_standard_error.to_bits() + ); +} + #[test] fn lag_pair_average_is_invariant_to_input_row_permutation() { let canonical = unequal_pair_count_rows(); From e384fdd2355d3501787cf69643f6abcf6463099f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 05:51:49 +0900 Subject: [PATCH 267/296] fix(longitudinal): format missingness recovery fixture --- .../tests/irregular_rate_estimand_contract.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs index 762fde82c..7d9d885a7 100644 --- a/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs +++ b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs @@ -176,8 +176,7 @@ fn run_rate_associated_missingness_recovery(seed: u64) -> MissingnessRecoveryEvi (first_quarter_squared_error_sum / FIRST_QUARTER_REPLICATES_F64).sqrt(); let recovery_coverage = covered_mass / REPLICATES_F64; let variance_numerator = (squared_error_sum - REPLICATES_F64 * bias * bias).max(0.0); - let bias_standard_error = - (variance_numerator / (REPLICATES_F64 - 1.0) / REPLICATES_F64).sqrt(); + let bias_standard_error = (variance_numerator / (REPLICATES_F64 - 1.0) / REPLICATES_F64).sqrt(); let z = 1.96_f64; let z_squared = z * z; @@ -511,4 +510,4 @@ fn unit_average_is_versioned_but_fails_closed_until_owner_mean_release() { recover_within_unit_irregular_rate_summary(&rows, IrregularRateEstimand::UnitAverageV1), Err(LongitudinalError::IrregularRateEstimandUnavailable) ); -} +} \ No newline at end of file From 11082f0be9c365ef33e714170003e6e04a583622 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 06:00:19 +0900 Subject: [PATCH 268/296] fix(longitudinal): restore rustfmt for missingness recovery fixture --- .../tests/irregular_rate_estimand_contract.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs index 7d9d885a7..762fde82c 100644 --- a/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs +++ b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs @@ -176,7 +176,8 @@ fn run_rate_associated_missingness_recovery(seed: u64) -> MissingnessRecoveryEvi (first_quarter_squared_error_sum / FIRST_QUARTER_REPLICATES_F64).sqrt(); let recovery_coverage = covered_mass / REPLICATES_F64; let variance_numerator = (squared_error_sum - REPLICATES_F64 * bias * bias).max(0.0); - let bias_standard_error = (variance_numerator / (REPLICATES_F64 - 1.0) / REPLICATES_F64).sqrt(); + let bias_standard_error = + (variance_numerator / (REPLICATES_F64 - 1.0) / REPLICATES_F64).sqrt(); let z = 1.96_f64; let z_squared = z * z; @@ -510,4 +511,4 @@ fn unit_average_is_versioned_but_fails_closed_until_owner_mean_release() { recover_within_unit_irregular_rate_summary(&rows, IrregularRateEstimand::UnitAverageV1), Err(LongitudinalError::IrregularRateEstimandUnavailable) ); -} \ No newline at end of file +} From 775f858d9ccbf48ea47d3bc8c5b0ca5978929bf9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 06:02:32 +0900 Subject: [PATCH 269/296] test(longitudinal): add noisy informative-missingness Monte Carlo acceptance --- .../irregular_rate_monte_carlo_contract.rs | 283 ++++++++++++++++++ 1 file changed, 283 insertions(+) create mode 100644 crates/longitudinal_core/tests/irregular_rate_monte_carlo_contract.rs diff --git a/crates/longitudinal_core/tests/irregular_rate_monte_carlo_contract.rs b/crates/longitudinal_core/tests/irregular_rate_monte_carlo_contract.rs new file mode 100644 index 000000000..1561274b4 --- /dev/null +++ b/crates/longitudinal_core/tests/irregular_rate_monte_carlo_contract.rs @@ -0,0 +1,283 @@ +//! Scientific acceptance for the declared pair-weighted irregular-rate estimand. +//! +//! This test deliberately keeps Monte Carlo arithmetic in the test boundary. Production +//! finite-binary64 mean ownership remains outside TEPP. + +use longitudinal_core::{ + EventTimedObservation, IrregularRateEstimand, recover_within_unit_irregular_rate_summary, +}; + +const REPLICATES: usize = 4_096; +const FIRST_QUARTER_REPLICATES: usize = REPLICATES / 4; +const INNOVATION_HALF_WIDTH: f64 = 0.12; +const Z_95: f64 = 1.96; + +fn timed(unit: u32, event_time: f64, score: f64) -> EventTimedObservation { + EventTimedObservation::new(unit, event_time, score) +} + +#[derive(Clone, Copy)] +struct SplitMix64 { + state: u64, +} + +impl SplitMix64 { + fn new(seed: u64) -> Self { + Self { state: seed } + } + + fn next_u64(&mut self) -> u64 { + self.state = self.state.wrapping_add(0x9E37_79B9_7F4A_7C15); + let mut value = self.state; + value = (value ^ (value >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9); + value = (value ^ (value >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB); + value ^ (value >> 31) + } + + fn signed_unit(&mut self) -> f64 { + let mantissa = self.next_u64() >> 11; + let unit = mantissa as f64 / ((1_u64 << 53) as f64); + 2.0 * unit - 1.0 + } +} + +#[derive(Clone, Copy, Debug)] +struct GeneratedUnit { + admitted_pairs: usize, + pair_rate_variance_sum: f64, +} + +fn append_noisy_missingness_unit( + rows: &mut Vec, + rng: &mut SplitMix64, + unit: u32, + true_log_rate: f64, + retain_three_of_four: bool, +) -> GeneratedUnit { + const LAST_NEGATIVE_TIME: usize = 12; + let innovation_variance = INNOVATION_HALF_WIDTH * INNOVATION_HALF_WIDTH / 3.0; + + let mut log_magnitude = 0.0; + let first_score = -1.0; + let mut retained_times = Vec::with_capacity(LAST_NEGATIVE_TIME + 1); + let mut negative_sum = first_score; + retained_times.push(0_usize); + rows.push(timed(unit, 0.0, first_score)); + + for event_time in 1..=LAST_NEGATIVE_TIME { + let innovation = INNOVATION_HALF_WIDTH * rng.signed_unit(); + log_magnitude += true_log_rate + innovation; + + let retain = if event_time == LAST_NEGATIVE_TIME { + true + } else { + let draw = rng.next_u64(); + if retain_three_of_four { + draw & 3 != 0 + } else { + draw & 3 == 0 + } + }; + + if retain { + let score = -log_magnitude.exp(); + rows.push(timed(unit, event_time as f64, score)); + negative_sum += score; + retained_times.push(event_time); + } + } + + rows.push(timed( + unit, + (LAST_NEGATIVE_TIME + 1) as f64, + -negative_sum, + )); + + let admitted_pairs = retained_times.len() - 1; + let pair_rate_variance_sum = retained_times + .windows(2) + .map(|times| { + let gap = (times[1] - times[0]) as f64; + innovation_variance / gap + }) + .sum(); + + GeneratedUnit { + admitted_pairs, + pair_rate_variance_sum, + } +} + +#[derive(Clone, Copy, Debug)] +struct MonteCarloEvidence { + attempted_replicates: usize, + recovered_replicates: usize, + failed_replicates: usize, + half_rate_admitted_pairs: usize, + third_rate_admitted_pairs: usize, + bias: f64, + bias_monte_carlo_se: f64, + rmse: f64, + first_quarter_rmse: f64, + coverage_95: f64, + coverage_wilson_lower_95: f64, + coverage_wilson_upper_95: f64, +} + +fn run_noisy_rate_associated_missingness(seed: u64) -> MonteCarloEvidence { + let half_rate = 0.5_f64.ln(); + let third_rate = (1.0_f64 / 3.0).ln(); + let mut rng = SplitMix64::new(seed); + + let mut recovered_replicates = 0_usize; + let mut failed_replicates = 0_usize; + let mut first_quarter_recovered = 0_usize; + let mut half_rate_admitted_pairs = 0_usize; + let mut third_rate_admitted_pairs = 0_usize; + let mut error_sum = 0.0; + let mut squared_error_sum = 0.0; + let mut first_quarter_squared_error_sum = 0.0; + let mut covered_replicates = 0_usize; + + for replicate_index in 0..REPLICATES { + let mut rows = Vec::with_capacity(32); + let half = append_noisy_missingness_unit(&mut rows, &mut rng, 1, half_rate, true); + let third = append_noisy_missingness_unit(&mut rows, &mut rng, 2, third_rate, false); + half_rate_admitted_pairs += half.admitted_pairs; + third_rate_admitted_pairs += third.admitted_pairs; + + let Ok(summary) = recover_within_unit_irregular_rate_summary( + &rows, + IrregularRateEstimand::LagPairAverageV1, + ) else { + failed_replicates += 1; + continue; + }; + let Some(estimate) = summary.estimate() else { + failed_replicates += 1; + continue; + }; + + let admitted_pairs = half.admitted_pairs + third.admitted_pairs; + assert_eq!(summary.candidate_units(), 2); + assert_eq!(summary.contributing_units(), 2); + assert_eq!(summary.admitted_pairs(), admitted_pairs); + assert_eq!(summary.refused_pairs(), 2); + assert_eq!( + summary.candidate_pairs(), + summary.admitted_pairs() + summary.refused_pairs() + ); + + let true_pair_target = (half.admitted_pairs as f64 * half_rate + + third.admitted_pairs as f64 * third_rate) + / admitted_pairs as f64; + let error = estimate - true_pair_target; + let conditional_standard_error = + (half.pair_rate_variance_sum + third.pair_rate_variance_sum).sqrt() + / admitted_pairs as f64; + + recovered_replicates += 1; + error_sum += error; + squared_error_sum += error * error; + if replicate_index < FIRST_QUARTER_REPLICATES { + first_quarter_recovered += 1; + first_quarter_squared_error_sum += error * error; + } + if error.abs() <= Z_95 * conditional_standard_error { + covered_replicates += 1; + } + } + + assert!(recovered_replicates > 1); + assert!(first_quarter_recovered > 0); + + let recovered = recovered_replicates as f64; + let bias = error_sum / recovered; + let rmse = (squared_error_sum / recovered).sqrt(); + let first_quarter_rmse = + (first_quarter_squared_error_sum / first_quarter_recovered as f64).sqrt(); + let sample_error_variance = + (squared_error_sum - recovered * bias * bias).max(0.0) / (recovered - 1.0); + let bias_monte_carlo_se = (sample_error_variance / recovered).sqrt(); + let coverage_95 = covered_replicates as f64 / recovered; + + let z_squared = Z_95 * Z_95; + let wilson_denominator = 1.0 + z_squared / recovered; + let wilson_center = + (coverage_95 + z_squared / (2.0 * recovered)) / wilson_denominator; + let wilson_margin = Z_95 + * ((coverage_95 * (1.0 - coverage_95) / recovered) + + z_squared / (4.0 * recovered * recovered)) + .sqrt() + / wilson_denominator; + + MonteCarloEvidence { + attempted_replicates: REPLICATES, + recovered_replicates, + failed_replicates, + half_rate_admitted_pairs, + third_rate_admitted_pairs, + bias, + bias_monte_carlo_se, + rmse, + first_quarter_rmse, + coverage_95, + coverage_wilson_lower_95: wilson_center - wilson_margin, + coverage_wilson_upper_95: wilson_center + wilson_margin, + } +} + +#[test] +fn noisy_rate_associated_missingness_recovers_declared_pair_estimand() { + let first = run_noisy_rate_associated_missingness(0x4950_2026_0912); + let replay = run_noisy_rate_associated_missingness(0x4950_2026_0912); + + assert_eq!(first.attempted_replicates, REPLICATES); + assert_eq!(first.recovered_replicates, REPLICATES); + assert_eq!(first.failed_replicates, 0); + assert!( + first.half_rate_admitted_pairs > 2 * first.third_rate_admitted_pairs, + "rate-associated retention must create informative pair multiplicity" + ); + + assert!(first.bias.abs() <= 3.0 * first.bias_monte_carlo_se); + assert!(first.rmse > 0.01 && first.rmse < 0.03); + assert!( + (first.rmse - first.first_quarter_rmse).abs() <= 0.25 * first.rmse, + "RMSE should stabilize rather than depend on the first quarter of replicates" + ); + assert!(first.coverage_95 > 0.90 && first.coverage_95 < 0.99); + assert!(first.coverage_wilson_lower_95 <= 0.95); + assert!(first.coverage_wilson_upper_95 >= 0.95); + + assert_eq!(first.attempted_replicates, replay.attempted_replicates); + assert_eq!(first.recovered_replicates, replay.recovered_replicates); + assert_eq!(first.failed_replicates, replay.failed_replicates); + assert_eq!( + first.half_rate_admitted_pairs, + replay.half_rate_admitted_pairs + ); + assert_eq!( + first.third_rate_admitted_pairs, + replay.third_rate_admitted_pairs + ); + assert_eq!(first.bias.to_bits(), replay.bias.to_bits()); + assert_eq!( + first.bias_monte_carlo_se.to_bits(), + replay.bias_monte_carlo_se.to_bits() + ); + assert_eq!(first.rmse.to_bits(), replay.rmse.to_bits()); + assert_eq!( + first.first_quarter_rmse.to_bits(), + replay.first_quarter_rmse.to_bits() + ); + assert_eq!(first.coverage_95.to_bits(), replay.coverage_95.to_bits()); + assert_eq!( + first.coverage_wilson_lower_95.to_bits(), + replay.coverage_wilson_lower_95.to_bits() + ); + assert_eq!( + first.coverage_wilson_upper_95.to_bits(), + replay.coverage_wilson_upper_95.to_bits() + ); +} From 20b38559980af454c6c2da21f26ec85c176ce796 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 06:25:35 +0900 Subject: [PATCH 270/296] fix(longitudinal): restore rustfmt after missingness acceptance --- .../tests/irregular_rate_estimand_contract.rs | 3 +-- .../tests/irregular_rate_monte_carlo_contract.rs | 9 ++------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs index 762fde82c..c000e30de 100644 --- a/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs +++ b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs @@ -176,8 +176,7 @@ fn run_rate_associated_missingness_recovery(seed: u64) -> MissingnessRecoveryEvi (first_quarter_squared_error_sum / FIRST_QUARTER_REPLICATES_F64).sqrt(); let recovery_coverage = covered_mass / REPLICATES_F64; let variance_numerator = (squared_error_sum - REPLICATES_F64 * bias * bias).max(0.0); - let bias_standard_error = - (variance_numerator / (REPLICATES_F64 - 1.0) / REPLICATES_F64).sqrt(); + let bias_standard_error = (variance_numerator / (REPLICATES_F64 - 1.0) / REPLICATES_F64).sqrt(); let z = 1.96_f64; let z_squared = z * z; diff --git a/crates/longitudinal_core/tests/irregular_rate_monte_carlo_contract.rs b/crates/longitudinal_core/tests/irregular_rate_monte_carlo_contract.rs index 1561274b4..ec5b10518 100644 --- a/crates/longitudinal_core/tests/irregular_rate_monte_carlo_contract.rs +++ b/crates/longitudinal_core/tests/irregular_rate_monte_carlo_contract.rs @@ -87,11 +87,7 @@ fn append_noisy_missingness_unit( } } - rows.push(timed( - unit, - (LAST_NEGATIVE_TIME + 1) as f64, - -negative_sum, - )); + rows.push(timed(unit, (LAST_NEGATIVE_TIME + 1) as f64, -negative_sum)); let admitted_pairs = retained_times.len() - 1; let pair_rate_variance_sum = retained_times @@ -203,8 +199,7 @@ fn run_noisy_rate_associated_missingness(seed: u64) -> MonteCarloEvidence { let z_squared = Z_95 * Z_95; let wilson_denominator = 1.0 + z_squared / recovered; - let wilson_center = - (coverage_95 + z_squared / (2.0 * recovered)) / wilson_denominator; + let wilson_center = (coverage_95 + z_squared / (2.0 * recovered)) / wilson_denominator; let wilson_margin = Z_95 * ((coverage_95 * (1.0 - coverage_95) / recovered) + z_squared / (4.0 * recovered * recovered)) From 1e44b73e61de0130615e479259484c741d3d2cf3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 06:28:09 +0900 Subject: [PATCH 271/296] fix(longitudinal): make Monte Carlo fixture lint-clean --- .../irregular_rate_monte_carlo_contract.rs | 40 +++++++++++++------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/crates/longitudinal_core/tests/irregular_rate_monte_carlo_contract.rs b/crates/longitudinal_core/tests/irregular_rate_monte_carlo_contract.rs index ec5b10518..f5ad3ccc8 100644 --- a/crates/longitudinal_core/tests/irregular_rate_monte_carlo_contract.rs +++ b/crates/longitudinal_core/tests/irregular_rate_monte_carlo_contract.rs @@ -16,6 +16,10 @@ fn timed(unit: u32, event_time: f64, score: f64) -> EventTimedObservation { EventTimedObservation::new(unit, event_time, score) } +fn exact_test_count(value: usize) -> f64 { + f64::from(u32::try_from(value).expect("test count must fit in u32")) +} + #[derive(Clone, Copy)] struct SplitMix64 { state: u64, @@ -36,7 +40,11 @@ impl SplitMix64 { fn signed_unit(&mut self) -> f64 { let mantissa = self.next_u64() >> 11; - let unit = mantissa as f64 / ((1_u64 << 53) as f64); + let high = u32::try_from(mantissa >> 32).expect("53-bit mantissa high part must fit u32"); + let low = u32::try_from(mantissa & u64::from(u32::MAX)) + .expect("53-bit mantissa low part must fit u32"); + let unit = (f64::from(high) * 4_294_967_296.0 + f64::from(low)) + / 9_007_199_254_740_992.0; 2.0 * unit - 1.0 } } @@ -73,27 +81,31 @@ fn append_noisy_missingness_unit( } else { let draw = rng.next_u64(); if retain_three_of_four { - draw & 3 != 0 + draw.trailing_zeros() < 2 } else { - draw & 3 == 0 + draw.trailing_zeros() >= 2 } }; if retain { let score = -log_magnitude.exp(); - rows.push(timed(unit, event_time as f64, score)); + rows.push(timed(unit, exact_test_count(event_time), score)); negative_sum += score; retained_times.push(event_time); } } - rows.push(timed(unit, (LAST_NEGATIVE_TIME + 1) as f64, -negative_sum)); + rows.push(timed( + unit, + exact_test_count(LAST_NEGATIVE_TIME + 1), + -negative_sum, + )); let admitted_pairs = retained_times.len() - 1; let pair_rate_variance_sum = retained_times .windows(2) .map(|times| { - let gap = (times[1] - times[0]) as f64; + let gap = exact_test_count(times[1] - times[0]); innovation_variance / gap }) .sum(); @@ -164,13 +176,15 @@ fn run_noisy_rate_associated_missingness(seed: u64) -> MonteCarloEvidence { summary.admitted_pairs() + summary.refused_pairs() ); - let true_pair_target = (half.admitted_pairs as f64 * half_rate - + third.admitted_pairs as f64 * third_rate) - / admitted_pairs as f64; + let half_pairs = exact_test_count(half.admitted_pairs); + let third_pairs = exact_test_count(third.admitted_pairs); + let admitted_pair_count = exact_test_count(admitted_pairs); + let true_pair_target = + (half_pairs * half_rate + third_pairs * third_rate) / admitted_pair_count; let error = estimate - true_pair_target; let conditional_standard_error = (half.pair_rate_variance_sum + third.pair_rate_variance_sum).sqrt() - / admitted_pairs as f64; + / admitted_pair_count; recovered_replicates += 1; error_sum += error; @@ -187,15 +201,15 @@ fn run_noisy_rate_associated_missingness(seed: u64) -> MonteCarloEvidence { assert!(recovered_replicates > 1); assert!(first_quarter_recovered > 0); - let recovered = recovered_replicates as f64; + let recovered = exact_test_count(recovered_replicates); let bias = error_sum / recovered; let rmse = (squared_error_sum / recovered).sqrt(); let first_quarter_rmse = - (first_quarter_squared_error_sum / first_quarter_recovered as f64).sqrt(); + (first_quarter_squared_error_sum / exact_test_count(first_quarter_recovered)).sqrt(); let sample_error_variance = (squared_error_sum - recovered * bias * bias).max(0.0) / (recovered - 1.0); let bias_monte_carlo_se = (sample_error_variance / recovered).sqrt(); - let coverage_95 = covered_replicates as f64 / recovered; + let coverage_95 = exact_test_count(covered_replicates) / recovered; let z_squared = Z_95 * Z_95; let wilson_denominator = 1.0 + z_squared / recovered; From 2498d44c2162a6cae448cd6b1c3c5d173a97ef11 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 06:30:35 +0900 Subject: [PATCH 272/296] fix(longitudinal): apply pinned rustfmt to Monte Carlo fixture --- .../tests/irregular_rate_monte_carlo_contract.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/longitudinal_core/tests/irregular_rate_monte_carlo_contract.rs b/crates/longitudinal_core/tests/irregular_rate_monte_carlo_contract.rs index f5ad3ccc8..42d15e75e 100644 --- a/crates/longitudinal_core/tests/irregular_rate_monte_carlo_contract.rs +++ b/crates/longitudinal_core/tests/irregular_rate_monte_carlo_contract.rs @@ -43,8 +43,7 @@ impl SplitMix64 { let high = u32::try_from(mantissa >> 32).expect("53-bit mantissa high part must fit u32"); let low = u32::try_from(mantissa & u64::from(u32::MAX)) .expect("53-bit mantissa low part must fit u32"); - let unit = (f64::from(high) * 4_294_967_296.0 + f64::from(low)) - / 9_007_199_254_740_992.0; + let unit = (f64::from(high) * 4_294_967_296.0 + f64::from(low)) / 9_007_199_254_740_992.0; 2.0 * unit - 1.0 } } From e6d1d637d3d271a1e3703a7d793bb7fdc9c6b40c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 06:34:07 +0900 Subject: [PATCH 273/296] fix(longitudinal): make estimand retention lint-clean --- .../tests/irregular_rate_estimand_contract.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs index c000e30de..9ac433641 100644 --- a/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs +++ b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs @@ -56,9 +56,9 @@ fn append_missingness_unit( event_time += 1.0; let draw = rng.next_u64(); let retain = if retain_three_of_four { - draw & 3 != 0 + draw.trailing_zeros() < 2 } else { - draw & 3 == 0 + draw.trailing_zeros() >= 2 }; if retain { let score = -(log_rate * event_time).exp(); From b72f1f1e9c6cd24e273104a8c31d0e68f081faa6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 07:04:59 +0900 Subject: [PATCH 274/296] test(longitudinal): keep Monte Carlo acceptance in canonical fixture --- .../tests/irregular_rate_estimand_contract.rs | 247 +----------------- 1 file changed, 5 insertions(+), 242 deletions(-) diff --git a/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs index 9ac433641..307886aee 100644 --- a/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs +++ b/crates/longitudinal_core/tests/irregular_rate_estimand_contract.rs @@ -1,4 +1,8 @@ -//! Public contract for pair-weighted irregular-rate estimand identity. +//! Deterministic public contract for pair-weighted irregular-rate estimand identity. +//! +//! Realistic rate-associated missingness and Monte Carlo uncertainty are exercised +//! separately in `irregular_rate_monte_carlo_contract.rs`. This file keeps only +//! deterministic estimand, weighting, refusal, and fail-closed fixtures. use longitudinal_core::{ EventTimedObservation, IrregularRateEstimand, LongitudinalError, @@ -21,192 +25,6 @@ fn unequal_pair_count_rows() -> [EventTimedObservation; 7] { ] } -struct SplitMix64 { - state: u64, -} - -impl SplitMix64 { - fn new(seed: u64) -> Self { - Self { state: seed } - } - - fn next_u64(&mut self) -> u64 { - self.state = self.state.wrapping_add(0x9E37_79B9_7F4A_7C15); - let mut value = self.state; - value = (value ^ (value >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9); - value = (value ^ (value >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB); - value ^ (value >> 31) - } -} - -fn append_missingness_unit( - rows: &mut Vec, - rng: &mut SplitMix64, - unit: u32, - log_rate: f64, - retain_three_of_four: bool, -) -> usize { - let mut event_time = 0.0; - let first_score = -1.0; - let mut negative_sum = first_score; - let mut negative_count = 1_usize; - rows.push(timed(unit, event_time, first_score)); - - for _ in 1..12 { - event_time += 1.0; - let draw = rng.next_u64(); - let retain = if retain_three_of_four { - draw.trailing_zeros() < 2 - } else { - draw.trailing_zeros() >= 2 - }; - if retain { - let score = -(log_rate * event_time).exp(); - rows.push(timed(unit, event_time, score)); - negative_sum += score; - negative_count += 1; - } - } - - event_time += 1.0; - let endpoint_score = -(log_rate * event_time).exp(); - rows.push(timed(unit, event_time, endpoint_score)); - negative_sum += endpoint_score; - negative_count += 1; - - rows.push(timed(unit, event_time + 1.0, -negative_sum)); - negative_count - 1 -} - -#[derive(Clone, Copy, Debug)] -struct MissingnessRecoveryEvidence { - attempted_replicates: usize, - recovered_replicates: usize, - failed_replicates: usize, - half_rate_admitted_pairs: usize, - third_rate_admitted_pairs: usize, - bias: f64, - rmse: f64, - max_absolute_error: f64, - first_quarter_rmse: f64, - recovery_coverage: f64, - coverage_wilson_lower_95: f64, - bias_standard_error: f64, -} - -fn run_rate_associated_missingness_recovery(seed: u64) -> MissingnessRecoveryEvidence { - const REPLICATES: usize = 256; - const REPLICATES_F64: f64 = 256.0; - const FIRST_QUARTER_REPLICATES: usize = 64; - const FIRST_QUARTER_REPLICATES_F64: f64 = 64.0; - const RECOVERY_TOLERANCE: f64 = 1.0e-12; - - let half_rate = 0.5_f64.ln(); - let third_rate = (1.0_f64 / 3.0).ln(); - let mut rng = SplitMix64::new(seed); - let mut recovered_replicates = 0_usize; - let mut failed_replicates = 0_usize; - let mut half_rate_admitted_pairs = 0_usize; - let mut third_rate_admitted_pairs = 0_usize; - let mut error_sum = 0.0; - let mut squared_error_sum = 0.0; - let mut first_quarter_squared_error_sum = 0.0; - let mut max_absolute_error = 0.0_f64; - let mut covered_replicates = 0_usize; - let mut covered_mass = 0.0; - - for replicate_index in 0..REPLICATES { - let mut rows = Vec::with_capacity(32); - let half_pairs = append_missingness_unit(&mut rows, &mut rng, 1, half_rate, true); - let third_pairs = append_missingness_unit(&mut rows, &mut rng, 2, third_rate, false); - half_rate_admitted_pairs += half_pairs; - third_rate_admitted_pairs += third_pairs; - - let Ok(summary) = recover_within_unit_irregular_rate_summary( - &rows, - IrregularRateEstimand::LagPairAverageV1, - ) else { - failed_replicates += 1; - continue; - }; - let Some(estimate) = summary.estimate() else { - failed_replicates += 1; - continue; - }; - - assert_eq!(summary.candidate_units(), 2); - assert_eq!(summary.contributing_units(), 2); - assert_eq!(summary.admitted_pairs(), half_pairs + third_pairs); - assert_eq!(summary.refused_pairs(), 2); - assert_eq!( - summary.candidate_pairs(), - summary.admitted_pairs() + summary.refused_pairs() - ); - - let mut true_rate_sum = 0.0; - let mut true_rate_denominator = 0.0; - for _ in 0..half_pairs { - true_rate_sum += half_rate; - true_rate_denominator += 1.0; - } - for _ in 0..third_pairs { - true_rate_sum += third_rate; - true_rate_denominator += 1.0; - } - let true_pair_target = true_rate_sum / true_rate_denominator; - let error = estimate - true_pair_target; - let absolute_error = error.abs(); - - recovered_replicates += 1; - error_sum += error; - squared_error_sum += error * error; - if replicate_index < FIRST_QUARTER_REPLICATES { - first_quarter_squared_error_sum += error * error; - } - max_absolute_error = max_absolute_error.max(absolute_error); - if absolute_error <= RECOVERY_TOLERANCE { - covered_replicates += 1; - covered_mass += 1.0; - } - } - - let bias = error_sum / REPLICATES_F64; - let rmse = (squared_error_sum / REPLICATES_F64).sqrt(); - let first_quarter_rmse = - (first_quarter_squared_error_sum / FIRST_QUARTER_REPLICATES_F64).sqrt(); - let recovery_coverage = covered_mass / REPLICATES_F64; - let variance_numerator = (squared_error_sum - REPLICATES_F64 * bias * bias).max(0.0); - let bias_standard_error = (variance_numerator / (REPLICATES_F64 - 1.0) / REPLICATES_F64).sqrt(); - - let z = 1.96_f64; - let z_squared = z * z; - let wilson_denominator = 1.0 + z_squared / REPLICATES_F64; - let wilson_center = - (recovery_coverage + z_squared / (2.0 * REPLICATES_F64)) / wilson_denominator; - let wilson_margin = z - * ((recovery_coverage * (1.0 - recovery_coverage) / REPLICATES_F64) - + z_squared / (4.0 * REPLICATES_F64 * REPLICATES_F64)) - .sqrt() - / wilson_denominator; - - assert_eq!(covered_replicates, recovered_replicates); - - MissingnessRecoveryEvidence { - attempted_replicates: REPLICATES, - recovered_replicates, - failed_replicates, - half_rate_admitted_pairs, - third_rate_admitted_pairs, - bias, - rmse, - max_absolute_error, - first_quarter_rmse, - recovery_coverage, - coverage_wilson_lower_95: wilson_center - wilson_margin, - bias_standard_error, - } -} - #[test] fn lag_pair_average_reports_estimand_and_failure_denominators() { let rows = unequal_pair_count_rows(); @@ -369,61 +187,6 @@ fn rate_associated_highly_unbalanced_follow_up_recovers_pair_weighted_truth() { ); } -#[test] -fn informative_missingness_recovery_is_reproducible_and_pair_weighted() { - let first = run_rate_associated_missingness_recovery(0x4950_2026_0912); - let replay = run_rate_associated_missingness_recovery(0x4950_2026_0912); - - assert_eq!(first.attempted_replicates, 256); - assert_eq!(first.recovered_replicates, 256); - assert_eq!(first.failed_replicates, 0); - assert!( - first.half_rate_admitted_pairs > 2 * first.third_rate_admitted_pairs, - "retention depends on the true rate and must create informative pair multiplicity" - ); - assert!(first.bias.abs() <= 1.0e-13); - assert!(first.rmse <= 3.0e-13); - assert!(first.max_absolute_error <= 1.0e-12); - assert!(first.recovery_coverage >= 0.999); - assert!(first.coverage_wilson_lower_95 > 0.98); - assert!(first.bias.abs() <= 3.0 * first.bias_standard_error + 1.0e-15); - assert!(first.rmse <= 2.0 * first.first_quarter_rmse + 1.0e-15); - - assert_eq!(first.attempted_replicates, replay.attempted_replicates); - assert_eq!(first.recovered_replicates, replay.recovered_replicates); - assert_eq!(first.failed_replicates, replay.failed_replicates); - assert_eq!( - first.half_rate_admitted_pairs, - replay.half_rate_admitted_pairs - ); - assert_eq!( - first.third_rate_admitted_pairs, - replay.third_rate_admitted_pairs - ); - assert_eq!(first.bias.to_bits(), replay.bias.to_bits()); - assert_eq!(first.rmse.to_bits(), replay.rmse.to_bits()); - assert_eq!( - first.max_absolute_error.to_bits(), - replay.max_absolute_error.to_bits() - ); - assert_eq!( - first.first_quarter_rmse.to_bits(), - replay.first_quarter_rmse.to_bits() - ); - assert_eq!( - first.recovery_coverage.to_bits(), - replay.recovery_coverage.to_bits() - ); - assert_eq!( - first.coverage_wilson_lower_95.to_bits(), - replay.coverage_wilson_lower_95.to_bits() - ); - assert_eq!( - first.bias_standard_error.to_bits(), - replay.bias_standard_error.to_bits() - ); -} - #[test] fn lag_pair_average_is_invariant_to_input_row_permutation() { let canonical = unequal_pair_count_rows(); From 6a5acddf5a88ec89cbd422259c7aa1f05010786f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 08:01:56 +0900 Subject: [PATCH 275/296] test(longitudinal): pin irregular-rate estimand authority --- ..._longitudinal_scientific_authority_sync.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/quality/test_longitudinal_scientific_authority_sync.py b/tests/quality/test_longitudinal_scientific_authority_sync.py index 161d629a4..abe8f3418 100644 --- a/tests/quality/test_longitudinal_scientific_authority_sync.py +++ b/tests/quality/test_longitudinal_scientific_authority_sync.py @@ -5,6 +5,8 @@ ROOT = Path(__file__).resolve().parents[2] CLAUDE = ROOT / "CLAUDE.md" +PRD_AMENDMENT = ROOT / "docs/product/prd-v0.4-amendment-longitudinal-time-ownership.md" +OWNERSHIP_ADR = ROOT / "docs/adr/longitudinal-modeling-ownership-addendum.md" def test_stationary_variance_overflow_guidance_matches_source_contract() -> None: @@ -14,3 +16,25 @@ def test_stationary_variance_overflow_guidance_matches_source_contract() -> None assert "When `2 a` overflows, form `(q / a) * -0.5`." not in guidance assert "Do not form `0.5 q` first (`q = from_bits(1)` underflows)." not in guidance assert "When `2 a` overflows, form `(q * 0.5) / |a|`." in guidance + + +def test_prd_names_irregular_rate_estimands_and_weighting_population() -> None: + """Keep the product target explicit when follow-up multiplicity changes pair weights.""" + prd = PRD_AMENDMENT.read_text(encoding="utf-8") + + assert "`tepp.irregular_rate.lag_pair_average.v1`" in prd + assert "`tepp.irregular_rate.unit_average.v1`" in prd + assert "candidate/contributing units" in prd + assert "candidate/admitted/refused pairs" in prd + assert "fail closed" in prd + + +def test_ownership_adr_keeps_pair_and_unit_estimands_distinct() -> None: + """Prevent the implemented pair target from silently becoming an equal-unit target.""" + adr = OWNERSHIP_ADR.read_text(encoding="utf-8") + + assert "`tepp.irregular_rate.lag_pair_average.v1`" in adr + assert "`tepp.irregular_rate.unit_average.v1`" in adr + assert "occasion count" in adr + assert "membership weight" in adr + assert "fail closed" in adr From a33c0e8e7272add3bd5b6ba5f58e82da88febe5d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 08:02:20 +0900 Subject: [PATCH 276/296] docs(prd): make irregular-rate estimand identity explicit --- .../prd-v0.4-amendment-longitudinal-time-ownership.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/product/prd-v0.4-amendment-longitudinal-time-ownership.md b/docs/product/prd-v0.4-amendment-longitudinal-time-ownership.md index f60541c8a..21a6bc178 100644 --- a/docs/product/prd-v0.4-amendment-longitudinal-time-ownership.md +++ b/docs/product/prd-v0.4-amendment-longitudinal-time-ownership.md @@ -2,7 +2,7 @@ **Parent product baseline:** `docs/product/prd-v0.4-approved.md` **Amendment:** v0.4-LONGITUDINAL-TIME-1 -**Status:** Approved clarification; protected-main implementation remains pending +**Status:** Proposed clarification; protected-main implementation and acceptance remain pending **Recorded:** 2026-09-01 This amendment narrows implementation authority without changing TEPP's approved product thesis or scientific claims. @@ -13,10 +13,12 @@ For event-time lagged association, the product contract requires lagged covarian CWC-then-irregular residual log-rate is Longitudinal Modeling composition, not a generic psychometric kernel. Consecutive unit-mean-centered pairs keep typed event-time intervals. The pairwise mean of the Driver, Oud, and Voelkle (2017, Eq. 3) scalar inverse on nonzero same-sign residuals is not raw-process drift (Curran & Bauer, 2011, Eq. 36). Already-centered irregular pairs may recover that inverse, including `ln(0.5)`. This is not DSEM and not Newton least-squares. +The weighting target is part of that scientific contract. `tepp.irregular_rate.lag_pair_average.v1` gives every admitted consecutive lag pair equal weight, so units with more admitted pairs contribute more total weight. Its evidence must report candidate/contributing units and candidate/admitted/refused pairs, including refusal categories that change the admitted-pair population. `tepp.irregular_rate.unit_average.v1` is a distinct equal-unit target: each unit must first retain identity through its within-unit summary before units are combined with equal weight. The latter remains fail closed until TEPP can consume the immutable released reusable finite-mean contract from fast-mlsirm. Occasion count, follow-up length, missingness, or cross-classified/multiple-membership composition must not be silently reinterpreted as a design or membership weight. A future explicitly design-weighted target requires its own versioned contract and denominator policy. + Occasion-mean event-time composition is also Longitudinal Modeling composition. For Hamaker, Kuiper, and Grasman (2015, Eq. 1a), the product may form `p_it = x_it - μ_t` only from an occasion-specific group mean on numeric event time. Numeric `-0.0` and `+0.0` are one occasion, a unit may contribute at most one observation to an occasion, each admitted occasion must retain multilevel evidence, and consecutive unit-specific pairs keep their own positive finite event-time interval. Occasion means must be deterministic under row permutation and must remain recoverable when the final mean is representable even if a naive intermediate same-sign sum would overflow. These deviations are not person-mean CWC residuals and are not within-person effects by themselves; stable between-person differences may remain. TEPP therefore refuses promoting the resulting occasion-residual log-rate to RI-CLPM, DSEM, causal, or within-person authority. For the scalar stationary Driver, Oud, and Voelkle (2017) p. 16 `discreteDRIFTstd` mapping, TEPP may expose a deterministic event-time transform only when stable drift and positive stationary within-person variance are established. Numerical equality with `exp(a Δt)` in that scalar stationary special case does not make unstandardised `discreteDRIFT` and `discreteDRIFTstd` the same estimand. When the stationary variance cancels algebraically from a standardized scalar map, positive real-valued stationarity is established from finite positive diffusion and stable finite drift; the cancelled variance is not required to fit in binary64 if the final standardized result is representable. Conversely, a nonzero exact transition that rounds to a false endpoint such as `1.0` or `0.0` remains fail-closed rather than being reported as no change or complete dissipation. Reusable static/generalized-mixed/dependence-aware psychometric arithmetic remains owned by `ContextualWisdomLab/fast-mlsirm`; TEPP consumes versioned contracts through an anti-corruption layer and does not retain duplicate production kernels. LLM execution remains `contextual-orchestrator`-owned and cannot substitute for numerical estimation or scientific acceptance. -Acceptance for this amendment requires typed event-time admission in the public longitudinal boundary, regression coverage for wrong-clock-shaped numeric input and extreme finite binary64 cases, explicit occasion-vs-CWC claim-boundary recovery on the same longitudinal panel, current-head Rust/documentation/security evidence, and normal protected-main integration. Branch-local implementation does not constitute released product capability. +Acceptance for this amendment requires typed event-time admission in the public longitudinal boundary, regression coverage for wrong-clock-shaped numeric input and extreme finite binary64 cases, explicit occasion-vs-CWC claim-boundary recovery on the same longitudinal panel, deterministic unequal-follow-up fixtures that distinguish pair-weighted from equal-unit targets, realistic leakage-safe Monte Carlo recovery for the declared estimand with attempted/recovered/failed denominators and Monte Carlo uncertainty, current-head Rust/documentation/security evidence, and normal protected-main integration. Branch-local implementation does not constitute released product capability. From 8f3bd98eec8aa4dc668f47dc4d67e97f5f22d537 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 08:02:45 +0900 Subject: [PATCH 277/296] docs(adr): keep irregular-rate weighting policy proposed and explicit --- docs/adr/longitudinal-modeling-ownership-addendum.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/adr/longitudinal-modeling-ownership-addendum.md b/docs/adr/longitudinal-modeling-ownership-addendum.md index 1fe0d3954..b081e44ef 100644 --- a/docs/adr/longitudinal-modeling-ownership-addendum.md +++ b/docs/adr/longitudinal-modeling-ownership-addendum.md @@ -4,7 +4,7 @@ **Decision identity:** ADR 0005; this addendum does not mint a new ADR number -**Status:** Accepted clarification +**Status:** Proposed clarification **Recorded:** 2026-09-01 @@ -22,6 +22,8 @@ The lagged-correlation boundary similarly requires both occasion-specific margin CWC-then-irregular residual log-rate is also Longitudinal Modeling composition. Person-mean centering of a time-related series is not raw-process drift (Curran & Bauer, 2011, pp. 583–619; PMC3059070 XML opened 2026-09-02; Eq. 36). The unique pairwise-mean-after-CWC evidence from Draft #327 is folded here with typed `EventTimeInterval` rather than grown on `psychometric_core`. Already-centered irregular pairs `(1, 0.5)` recover `ln(0.5)` only for `Δt = 1`; for a general admitted interval the exact map is `ln(0.5) / Δt`. CWC of a raw AR path does not recover raw-process drift. +Repeated-pair multiplicity is part of the estimand rather than a numerical implementation detail. `tepp.irregular_rate.lag_pair_average.v1` weights every admitted consecutive pair equally and therefore lets a unit's total contribution vary with its admitted pair count. Its evidence records candidate/contributing units and candidate/admitted/refused pairs so missingness, follow-up duration, and refusal policy cannot silently disappear from the weighting population. `tepp.irregular_rate.unit_average.v1` is a different target: unit identity must survive until within-unit rates have first been summarized and the resulting unit summaries are combined with equal unit weight. It remains fail closed while the required reusable finite-mean operation has no immutable released fast-mlsirm contract. Occasion count is not a membership weight, including under time-varying multilevel, cross-classified, or multiple-membership composition. An explicit design-weighted target, if later required, needs a separate versioned contract instead of overloading either existing estimand. + Occasion-mean event-time composition is likewise Longitudinal Modeling authority. Hamaker, Kuiper, and Grasman (2015, Eq. 1a) write `x_it = μ_t + p_it`, where `μ_t` is the occasion-specific group mean. TEPP forms those deviations only across numeric event-time occasions, treats IEEE-754 `-0.0` and `+0.0` as one numeric occasion, admits at most one observation per unit and occasion, and preserves each consecutive unit-specific interval as an `EventTimeInterval`. The resulting `p_it` still contains stable between-person differences and therefore does not become a within-person lag merely because it is lagged. It is distinct from person-mean CWC, a sample-wide grand mean, RI-CLPM, and DSEM. The occasion-specific mean must remain deterministic under row permutation and must not reject a representable final mean merely because a naive same-sign intermediate sum would overflow. ## DDD consequences @@ -29,13 +31,17 @@ Occasion-mean event-time composition is likewise Longitudinal Modeling authority - `psychometric_core` is not the authority for temporal transforms merely because an earlier branch placed them there. - `EventTimeInterval` is a value object of Longitudinal Modeling. Assertion-, document-, system-, availability-, and method-occasion intervals require explicit owning-context conversion before they can be admitted as substantive event time. - Occasion identity is numeric event time, not the raw binary encoding of an otherwise equal numeric zero. -- One transform, route, clock, or refusal does not create a bounded context or a new ADR identity. +- Irregular-rate weighting policy is a versioned scientific contract. Pair-weighted, equal-unit, and any future design-weighted target remain distinct even when a particular fixture produces the same number. +- Cross-classified and multiple-membership weights come from their owning composition contract; occasion or pair count must never be inferred as membership weight. +- One transform, route, clock, refusal, or weighting option does not create a bounded context or a new ADR identity. - Compatibility adapters may preserve public callers during a landed migration, but domain ownership and dependency direction must remain explicit. ## Verification PR #310 is the current landing vehicle for this clarification. Its RED lineage includes an extreme stable-drift case that failed because `-2a` overflowed despite a representable stationary variance, a cancelled-stationary case in which the final scalar standardisation is representable even though `q / (-2a)` itself lies outside binary64 range, and a typed event-time contract that could not compile before the value object existed. The repaired source keeps actual stationary-variance recovery fail-closed when `p` itself is requested, but standardized scalar maps validate stationarity algebraically and avoid materializing an intermediate that cancels. The #327 fold adds CWC-then-pairwise-mean residual log-rate with Curran refusal and known-truth already-centered recovery of `ln(0.5)` at `Δt = 1`; arbitrary admitted intervals recover the exact `ln(0.5) / Δt` map. +Issue #495 and PR #310 make the irregular-rate weighting identity explicit. `LagPairAverageV1` preserves the existing pair-weighted scalar while exposing unit/pair denominators and refusal counts. Unequal-follow-up fixtures distinguish that target from an independently computed equal-unit comparator, including a highly unbalanced rate-associated follow-up case. A deterministic informative-missingness Monte Carlo contract evaluates the declared pair target against truth conditioned on actually admitted lag-pair counts and reports attempted/recovered/failed replicates, bias, RMSE, Monte Carlo uncertainty, interval coverage, and replay reproducibility. `UnitAverageV1` remains typed but unavailable rather than being approximated with another TEPP-local generic mean. + The #486 fold is verified against the same landing vehicle rather than retained as a second `psychometric_core` authority. Its Hamaker claim boundary is implemented by `longitudinal_core::center_occasion_mean_event_lags` and `recover_occasion_mean_centered_irregular_residual_log_rate`. Regressions cover numeric signed-zero identity, duplicate-unit admission, intermediate-sum overflow with a representable occasion mean, permutation stability, known drift recovery, and the same-panel distinction between occasion-mean residuals and person-mean CWC residuals. The dedicated research note records the primary-source trace and explicitly refuses promotion to a within-person effect. -Protected-main maturity is not claimed until the exact landing head passes the live ruleset and is merged. +This addendum remains Proposed while its landing vehicle is Draft. Accepted/protected-main maturity is not claimed until the exact landing head passes the live ruleset, the required immutable numerical dependency is released and consumed, and the change is merged normally. From a9420c1e80933f879b9d551dd5ae5a70c9b56150 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 08:03:21 +0900 Subject: [PATCH 278/296] test(longitudinal): require TRD estimand traceability --- .../test_longitudinal_scientific_authority_sync.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/quality/test_longitudinal_scientific_authority_sync.py b/tests/quality/test_longitudinal_scientific_authority_sync.py index abe8f3418..5464776c4 100644 --- a/tests/quality/test_longitudinal_scientific_authority_sync.py +++ b/tests/quality/test_longitudinal_scientific_authority_sync.py @@ -6,6 +6,7 @@ ROOT = Path(__file__).resolve().parents[2] CLAUDE = ROOT / "CLAUDE.md" PRD_AMENDMENT = ROOT / "docs/product/prd-v0.4-amendment-longitudinal-time-ownership.md" +TRD = ROOT / "docs/TRD.md" OWNERSHIP_ADR = ROOT / "docs/adr/longitudinal-modeling-ownership-addendum.md" @@ -29,6 +30,17 @@ def test_prd_names_irregular_rate_estimands_and_weighting_population() -> None: assert "fail closed" in prd +def test_trd_requires_versioned_irregular_rate_weighting_and_denominators() -> None: + """Keep the technical contract aligned with the public estimand API.""" + trd = TRD.read_text(encoding="utf-8") + + assert "`tepp.irregular_rate.lag_pair_average.v1`" in trd + assert "`tepp.irregular_rate.unit_average.v1`" in trd + assert "candidate/contributing units" in trd + assert "candidate/admitted/refused pairs" in trd + assert "attempted/recovered/failed" in trd + + def test_ownership_adr_keeps_pair_and_unit_estimands_distinct() -> None: """Prevent the implemented pair target from silently becoming an equal-unit target.""" adr = OWNERSHIP_ADR.read_text(encoding="utf-8") From bf393bc4391a671466438efa53ae092e7c8aabab Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 08:03:54 +0900 Subject: [PATCH 279/296] docs(trd): trace irregular-rate estimand and Monte Carlo requirements --- docs/TRD.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/TRD.md b/docs/TRD.md index 031c2113a..38c436e34 100644 --- a/docs/TRD.md +++ b/docs/TRD.md @@ -1,7 +1,7 @@ # TEPP Technical Requirements Document **Status:** Accepted technical baseline aligned to approved PRD v0.4 -**Last reviewed:** 2026-08-16 +**Last reviewed:** 2026-09-12 ## 1. Technical objective @@ -47,6 +47,10 @@ Shared-latent temporal/relational topic estimation provides posterior uncertaint Longitudinal ESEM/DSEM (Asparouhov & Muthén, 2009; Asparouhov et al., 2018; Marsh et al., 2014) must distinguish stable between-unit differences from within-unit temporal change, test measurement invariance where comparisons require it (American Educational Research Association, American Psychological Association, & National Council on Measurement in Education, 2014), account for irregular intervals when necessary, and propagate topic-posterior uncertainty through plausible values or joint estimation. These psychometric targets remain accepted-target. +Irregular-rate summaries must expose their weighting target as a versioned scientific contract rather than leaving contribution multiplicity implicit. `tepp.irregular_rate.lag_pair_average.v1` assigns equal weight to every admitted consecutive event-time pair and therefore permits a unit's total contribution to vary with admitted pair count. Its result evidence must carry candidate/contributing units and candidate/admitted/refused pairs, including refusal categories that alter the admitted population. `tepp.irregular_rate.unit_average.v1` is a separate equal-unit target that preserves unit identity through the within-unit summary before combining units. It remains fail closed until TEPP can consume an immutable released reusable finite-mean contract from fast-mlsirm; TEPP must not implement a second generic mean kernel to activate it. Occasion count, follow-up duration, missingness, or pair multiplicity must not be inferred as design or cross-classified/multiple-membership weights. Any future design-weighted target requires its own versioned contract. + +Scientific acceptance for irregular-rate weighting requires deterministic unequal-follow-up fixtures, permutation invariance, explicit refusal denominators, and leakage-safe known-truth simulation. Informative-missingness Monte Carlo evidence must report attempted/recovered/failed replicates, the truth corresponding to the actually admitted weighting population, bias, RMSE, interval coverage, convergence/replay evidence where applicable, and Monte Carlo uncertainty. The alternative weighting policy should be retained as a comparison or negative estimator when scientifically useful; numerical equality in one fixture does not collapse the estimand identities. + ## 8. Compute requirements Production mathematical/psychometric arithmetic is Rust. CPU `f64` is the numerical reference. CPU parallelism uses bounded fixed worker pools/thread-local sufficient statistics to reduce context switching and oversubscription. GPU execution is introduced only when computationally material, streamed under a VRAM budget, and parity-tested against the CPU reference. OOM triggers bounded batch reduction and safe CPU fallback rather than uncontrolled failure. From 29e44e15ed074209fc383e114aa2006210334de2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 08:07:49 +0900 Subject: [PATCH 280/296] test(research): require irregular-rate weighting sources in canonical register --- .../test_longitudinal_scientific_authority_sync.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/quality/test_longitudinal_scientific_authority_sync.py b/tests/quality/test_longitudinal_scientific_authority_sync.py index 5464776c4..064020586 100644 --- a/tests/quality/test_longitudinal_scientific_authority_sync.py +++ b/tests/quality/test_longitudinal_scientific_authority_sync.py @@ -8,6 +8,7 @@ PRD_AMENDMENT = ROOT / "docs/product/prd-v0.4-amendment-longitudinal-time-ownership.md" TRD = ROOT / "docs/TRD.md" OWNERSHIP_ADR = ROOT / "docs/adr/longitudinal-modeling-ownership-addendum.md" +LITERATURE_REGISTER = ROOT / "docs/research/standards-and-literature.md" def test_stationary_variance_overflow_guidance_matches_source_contract() -> None: @@ -50,3 +51,14 @@ def test_ownership_adr_keeps_pair_and_unit_estimands_distinct() -> None: assert "occasion count" in adr assert "membership weight" in adr assert "fail closed" in adr + + +def test_irregular_rate_weighting_sources_are_in_canonical_literature_register() -> None: + """Keep claim-specific informative-size sources in the canonical APA register.""" + literature = LITERATURE_REGISTER.read_text(encoding="utf-8") + + assert "Wang, M., Kong, M., & Datta, S. (2011)." in literature + assert "Huang, Y. (2011)." in literature + assert "Kahan, B. C., Li, F., Blette, B., Jairath, V., Copas, A., & Harhay, M. O. (2023)." in literature + assert "irregular-rate estimand" in literature + assert "record multiplicity" in literature From 3b1c801d5fb3f1d0ea92577c696d956b9d4e7f73 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 08:10:35 +0900 Subject: [PATCH 281/296] docs(research): register irregular-rate weighting evidence --- docs/research/standards-and-literature.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/research/standards-and-literature.md b/docs/research/standards-and-literature.md index 66e3c5955..9ccfdee4e 100644 --- a/docs/research/standards-and-literature.md +++ b/docs/research/standards-and-literature.md @@ -47,6 +47,14 @@ Browne, W. J., Goldstein, H., & Rasbash, J. (2001). Multiple membership multiple Jones, K. (1991). Specifying and estimating multi-level models for geographical research. *Transactions of the Institute of British Geographers, 16*(2), 148–160. https://doi.org/10.2307/622612 +Huang, Y. (2011). Informative cluster sizes for subcluster-level covariates and weighted generalized estimating equations. *Biometrics, 67*(3), 843–851. https://doi.org/10.1111/j.1541-0420.2010.01542.x + +Kahan, B. C., Li, F., Blette, B., Jairath, V., Copas, A., & Harhay, M. O. (2023). Informative cluster size in cluster-randomised trials: A case study from the TRIGGER trial. *Clinical Trials*. https://doi.org/10.1177/17407745231186094 + +Wang, M., Kong, M., & Datta, S. (2011). Inference for marginal linear models for clustered longitudinal data with potentially informative cluster sizes. *Statistical Methods in Medical Research, 20*(4), 347–367. https://doi.org/10.1177/0962280209347043 + +For TEPP's irregular-rate estimand contract, these informative-cluster-size sources are methodological analogies rather than treatment-effect authority. They support the narrower requirement that record multiplicity and follow-up intensity can change the weighting population, so pair-weighted, equal-unit, and any future design-weighted target must remain explicitly distinct. See `docs/research/irregular-rate-estimand-weighting.md`; the repository does not import the papers' treatment estimands into TEPP. + TEPP applies these sources to construct definition, score interpretation, reliability, validity evidence, uncertainty, consequences, longitudinal invariance, ESEM cross-loadings, and DSEM. Topic outputs are treated as fallible indicators or components only after their construct role is evaluated. Reflective, formative, and network classes remain distinct (Bollen & Lennox, 1991). Complete-data OLS loadings across posterior indicator draws are combined with Rubin (1996) \(T_m\); the arithmetic-mean helper remains a point estimate. Mislevy (1991, *Psychometrika, 56*, 177–196, DOI 10.1007/bf02294457) remains unread (Unpaywall/OpenAlex/Semantic Scholar 2026-08-18T03:07Z: closed). The 1988 ETS RR-88-45 / DTIC ADA200179 technical report of the same title was opened 2026-08-17T12:04Z from archive.org; it is not the 1991 journal article and is not used as Mislevy plausible-value authority. Temporal precedence is not causal identification (Holland, 1986). Within/between OLS follows Enders and Tofighi (2007), Curran and Bauer (2011), and Hamaker et al. (2015). Enders and Tofighi (2007, Table 2, pp. 124–127; PDF opened 2026-08-17) show that the CWC cluster-mean coefficient is the contextual effect (`between − within`), not the between-cluster effect. Curran and Bauer (2011, pp. 607–608) reject person-mean subtraction on a raw autoregressive series as the lagged within-person residual; already-centered irregular residuals use the Voelkle et al. (2012, Eq. 7) / Driver et al. (2017, Eq. 3) scalar map. Discrete lags from unequal event intervals are remapped through that log-rate (Voelkle et al., 2012, ZORA accepted manuscript re-opened 2026-08-17T13:13Z) and are not pooled. Driver, Oud, and Voelkle (2017, Eq. 3 and p. 4) write \(A_{\Delta t}=\operatorname{expm}(A\Delta t)\) and restate the discrete intercept as a function of \(A\) and \(\Delta t\). A binary64 underflow of \(\exp(a\Delta t)\) to `+0` is refused because discrete auto-effects are strictly positive. The discrete effect of a constant predictor is Voelkle et al. (2012, Eq. 12; ZORA accepted manuscript re-opened 2026-08-17T14:20Z, Introducing Intercepts, manuscript p. 20), evaluated as \(a_{yx}(\operatorname{expm1}(z)/a_{xx})\) with \(z=a_{xx}\Delta t\) so a finite result is not lost when \(z\) overflows to \(-\infty\) or when \(a_{yx}\Delta t\) overflows, and in log space when `expm1(z)` overflows at a finite \(z\); a zero continuous effect is exactly zero; an overflowing \(a_{yx}/a_{xx}\) rewrite term fails closed; the first-order product is the underflow limit of that equation, not the general constant-predictor discrete effect. The discrete effect of a time-varying predictor with matched sampling and constancy intervals is Voelkle et al. (2012, Eq. 14; manuscript p. 21): \(b^{*}_{y.x}(\Delta t)=a_{yx}\Delta t\). That product is not Eq. 12. Unmatched intervals fail closed (Oud & Jansen, 2000, unread). The exact scalar discrete process noise is Driver, Oud, and Voelkle (2017, Eq. 3; JSS PDF re-opened 2026-08-18T14:04Z, p. 4): \(Q_{\Delta t}=0.5 q(\operatorname{expm1}(z)/a)\) with \(z=2(a\Delta t)\) for \(a\neq 0\) and \(q=GG^{\top}\ge 0\); do not form \(2a\) first; \(a=0\) recovers \(q\Delta t\); an overflowing rewrite scale \(0.5 q/a\) fails closed. This is not a Kalman filter. Driver, Oud, and Voelkle (2017, Eq. 3; JSS PDF re-opened 2026-08-18T14:04Z) write the same discrete intercept as \(A^{-1}[e^{A\Delta t}-I]\xi\). The lagged covariance is \(\mathrm{e}^{a\Delta t}p\) and the unconditional variance is \(\mathrm{e}^{2a\Delta t}p+Q_{\Delta t}\) (Driver et al., 2017, Eq. 3–4, pp. 4–5); a zero diffusion whose \(2(a\Delta t)\) overflows to \(+\infty\) fails closed. The stationary within-subject variance is the \(\Delta t\to\infty\) limit of Eq. 4: \(-q/(2a)\) for stable \(a<0\) (JSS p. 16 `asymDIFFUSION`; §4.3; PDF re-opened 2026-08-18T18:03Z). Finite-interval \(Q_{\Delta t}\) is not that limit. Trait-plus-state variance is \(\mathrm{trait}+\mathrm{state}\) and lagged covariance is \(\mathrm{trait}+\mathrm{e}^{a\Delta t}p\) (Driver et al., 2017, §4.3, p. 9; JSS PDF re-opened 2026-08-18T21:07Z). Trait variance is not process noise and not `asymDIFFUSION`. The first-occasion map `τ + λ μ_0` is not `E(y_t)`. The contemporaneous `TDPREDEFFECT` impulse is `m x` (Driver et al., 2017, Eq. 3 fourth summand; Table 2; §7.2; JSS PDF re-opened 2026-08-20T07:10Z). `TDPREDEFFECT` is not `CINT`. `M x` is not `A^{-1}[e^{A Δt} − I] B z` and is not Voelkle et al. (2012, Eq. 14). Metric/weak invariance does not license latent-mean comparison. Putnick and Bornstein (2016, PMC author manuscript PMC5145197 opened 2026-08-19T22:15Z) require scalar invariance before latent-mean comparison and state that residual invariance is not a prerequisite. Two-observation OLS residual variance is identically `0` and is not strict. Meredith (1993) remains unread (Unpaywall/OpenAlex 2026-08-25T11:32Z: `is_oa: false`; Springer `content/pdf` is a 3038-byte HTML stub; Cambridge Core DOI 10.1007/BF02294825 remains a closed product page). Vandenberg and Lance (2000) remains unread. Mislevy (1991, *Psychometrika, 56*, 177–196, DOI 10.1007/bf02294457) remains unread (Unpaywall/OpenAlex 2026-08-25T11:32Z: `is_oa: false`; Springer `content/pdf` is a 3038-byte HTML stub). ERIC ED334221 is Singer and Willett (1991), not the 1991 journal article. ERIC ED333032 is Mislevy, Sheehan, and Wingersky (1990), ETS RR-90-17-ONR, not the 1991 journal article. Oud and Jansen (2000) remains unread (Unpaywall/OpenAlex 2026-08-18T21:07Z: closed). For Meredith (1993), the Cambridge Core original-paper page and abstract were opened on 2026-08-21; Unpaywall, Springer `content/pdf`, OpenAlex, Semantic Scholar, and Cambridge Core PDF lookup were re-tried 2026-08-25T11:32Z and remain closed. The earlier `remains unread` note means that the full text was not available, not that the authoritative record was unverified. From d6bde81d562495e65b7d009a414bcc57c98675e7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 08:13:37 +0900 Subject: [PATCH 282/296] test(research): require complete irregular-rate APA records --- ..._longitudinal_scientific_authority_sync.py | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/tests/quality/test_longitudinal_scientific_authority_sync.py b/tests/quality/test_longitudinal_scientific_authority_sync.py index 064020586..bfc2130f3 100644 --- a/tests/quality/test_longitudinal_scientific_authority_sync.py +++ b/tests/quality/test_longitudinal_scientific_authority_sync.py @@ -53,12 +53,24 @@ def test_ownership_adr_keeps_pair_and_unit_estimands_distinct() -> None: assert "fail closed" in adr -def test_irregular_rate_weighting_sources_are_in_canonical_literature_register() -> None: - """Keep claim-specific informative-size sources in the canonical APA register.""" +def test_irregular_rate_weighting_sources_are_doctored_in_canonical_register() -> None: + """Keep claim-specific informative-size sources complete enough for APA traceability.""" literature = LITERATURE_REGISTER.read_text(encoding="utf-8") - assert "Wang, M., Kong, M., & Datta, S. (2011)." in literature - assert "Huang, Y. (2011)." in literature - assert "Kahan, B. C., Li, F., Blette, B., Jairath, V., Copas, A., & Harhay, M. O. (2023)." in literature + assert ( + "Huang, Y., & Leroux, B. (2011). Informative cluster sizes for subcluster-level " + "covariates and weighted generalized estimating equations. *Biometrics, 67*(3), " + "843–851." in literature + ) + assert ( + "Kahan, B. C., Li, F., Blette, B., Jairath, V., Copas, A., & Harhay, M. O. " + "(2023). Informative cluster size in cluster-randomised trials: A case study from " + "the TRIGGER trial. *Clinical Trials, 20*(6), 661–669." in literature + ) + assert ( + "Wang, M., Kong, M., & Datta, S. (2011). Inference for marginal linear models for " + "clustered longitudinal data with potentially informative cluster sizes. " + "*Statistical Methods in Medical Research, 20*(4), 347–367." in literature + ) assert "irregular-rate estimand" in literature assert "record multiplicity" in literature From 6b273f574d7a8bc17cc4ef1f6592bc925c5a403a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 08:14:37 +0900 Subject: [PATCH 283/296] docs(research): doctor irregular-rate bibliographic records --- docs/research/irregular-rate-estimand-weighting.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/research/irregular-rate-estimand-weighting.md b/docs/research/irregular-rate-estimand-weighting.md index 32e62f4db..5b859b48c 100644 --- a/docs/research/irregular-rate-estimand-weighting.md +++ b/docs/research/irregular-rate-estimand-weighting.md @@ -72,7 +72,7 @@ This evidence resolves the naming/denominator ambiguity for the pair-weighted fi This is not a cluster-randomized treatment-effect model. However, the statistical identification issue is analogous: unequal numbers of observations inside a higher-level unit can induce different weighting targets, and informative cluster or subcluster size can make those targets materially different. The estimator has to match the declared estimand rather than inheriting weights accidentally from record multiplicity. -Wang, Kong, and Datta (2011) study clustered longitudinal data and show that informative cluster size can invalidate ordinary marginal inference when cluster size is related to the outcome distribution. Huang (2011) further shows that the appropriate weights depend on the population of interest and on within-cluster covariate structure. Kahan et al. (2023) give a clear modern estimand distinction between equal participant weighting and equal cluster weighting. TEPP does not import their treatment-effect estimands; it imports the narrower methodological requirement that aggregation weights are part of estimand identity. +Wang, Kong, and Datta (2011) study clustered longitudinal data and show that informative cluster size can invalidate ordinary marginal inference when cluster size is related to the outcome distribution. Huang and Leroux (2011) further show that the appropriate weights depend on the population of interest and on within-cluster covariate structure. Kahan et al. (2023) give a clear modern estimand distinction between equal participant weighting and equal cluster weighting. TEPP does not import their treatment-effect estimands; it imports the narrower methodological requirement that aggregation weights are part of estimand identity. ## Decision and documentation boundary @@ -89,8 +89,8 @@ The current mixed-sign binary64 mean RED in #310 remains independent. Clarifying ## References -Huang, Y. (2011). Informative cluster sizes for subcluster-level covariates and weighted generalized estimating equations. *Biometrics, 67*(3), 843–851. https://doi.org/10.1111/j.1541-0420.2010.01542.x +Huang, Y., & Leroux, B. (2011). Informative cluster sizes for subcluster-level covariates and weighted generalized estimating equations. *Biometrics, 67*(3), 843–851. https://doi.org/10.1111/j.1541-0420.2010.01542.x -Kahan, B. C., Li, F., Blette, B., Jairath, V., Copas, A., & Harhay, M. O. (2023). Informative cluster size in cluster-randomised trials: A case study from the TRIGGER trial. *Clinical Trials*. https://doi.org/10.1177/17407745231186094 +Kahan, B. C., Li, F., Blette, B., Jairath, V., Copas, A., & Harhay, M. O. (2023). Informative cluster size in cluster-randomised trials: A case study from the TRIGGER trial. *Clinical Trials, 20*(6), 661–669. https://doi.org/10.1177/17407745231186094 Wang, M., Kong, M., & Datta, S. (2011). Inference for marginal linear models for clustered longitudinal data with potentially informative cluster sizes. *Statistical Methods in Medical Research, 20*(4), 347–367. https://doi.org/10.1177/0962280209347043 From c486143ef05e30a5fa388817663b0cc3f2e22870 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 08:17:23 +0900 Subject: [PATCH 284/296] docs(research): complete irregular-rate APA records --- docs/research/standards-and-literature.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/research/standards-and-literature.md b/docs/research/standards-and-literature.md index 9ccfdee4e..218325676 100644 --- a/docs/research/standards-and-literature.md +++ b/docs/research/standards-and-literature.md @@ -47,9 +47,9 @@ Browne, W. J., Goldstein, H., & Rasbash, J. (2001). Multiple membership multiple Jones, K. (1991). Specifying and estimating multi-level models for geographical research. *Transactions of the Institute of British Geographers, 16*(2), 148–160. https://doi.org/10.2307/622612 -Huang, Y. (2011). Informative cluster sizes for subcluster-level covariates and weighted generalized estimating equations. *Biometrics, 67*(3), 843–851. https://doi.org/10.1111/j.1541-0420.2010.01542.x +Huang, Y., & Leroux, B. (2011). Informative cluster sizes for subcluster-level covariates and weighted generalized estimating equations. *Biometrics, 67*(3), 843–851. https://doi.org/10.1111/j.1541-0420.2010.01542.x -Kahan, B. C., Li, F., Blette, B., Jairath, V., Copas, A., & Harhay, M. O. (2023). Informative cluster size in cluster-randomised trials: A case study from the TRIGGER trial. *Clinical Trials*. https://doi.org/10.1177/17407745231186094 +Kahan, B. C., Li, F., Blette, B., Jairath, V., Copas, A., & Harhay, M. O. (2023). Informative cluster size in cluster-randomised trials: A case study from the TRIGGER trial. *Clinical Trials, 20*(6), 661–669. https://doi.org/10.1177/17407745231186094 Wang, M., Kong, M., & Datta, S. (2011). Inference for marginal linear models for clustered longitudinal data with potentially informative cluster sizes. *Statistical Methods in Medical Research, 20*(4), 347–367. https://doi.org/10.1177/0962280209347043 @@ -118,7 +118,7 @@ Brown, T. B., Mann, B., Ryder, N., Subbiah, M., Kaplan, J. D., Dhariwal, P., Nee Reynolds, L., & McDonell, K. (2021). Prompt programming for large language models: Beyond the few-shot paradigm. In *Extended abstracts of the 2021 CHI conference on human factors in computing systems*. Association for Computing Machinery. https://doi.org/10.1145/3411763.3451760 -Liu, P., Yuan, W., Fu, J., Jiang, Z., Hayashi, H., & Neubig, G. (2023). Pre-train, prompt, and predict: A systematic survey of prompting methods in natural language processing. *ACM Computing Surveys, 55*(9), Article 195. https://doi.org/10.1145/3560815 +Liu, P., Yuan, W., Fu, J., Jiang, Z., Hayashi, H., & Neubig, E. (2023). Pre-train, prompt, and predict: A systematic survey of prompting methods in natural language processing. *ACM Computing Surveys, 55*(9), Article 195. https://doi.org/10.1145/3560815 TEPP retains a logistic-normal CPU reference while allowing adapter backends that satisfy shared-latent, posterior, temporal, relational, and measurement-invariance contracts. Brown et al. (2020) and Reynolds and McDonell (2021) provide primary research context for prompts as task-conditioning and prompt-programming mechanisms; they do not define TEPP's latent-content labels. As a normative ADR 0004/0012 contract, instruction and prompt boilerplate is therefore modeled as explicit method structure, not unique latent content and not a stopword deletion. Liu et al. (2023) is secondary survey background only and is not evidence for that repository-specific classification. `topic_lineage` keeps one global topic identity when activity becomes dormant or reactivated. From 9a3854cc9bfc5381e1dfb89506d394adbdf3adec Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 08:19:33 +0900 Subject: [PATCH 285/296] fix(research): restore unrelated literature record --- docs/research/standards-and-literature.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/research/standards-and-literature.md b/docs/research/standards-and-literature.md index 218325676..618c3aa5f 100644 --- a/docs/research/standards-and-literature.md +++ b/docs/research/standards-and-literature.md @@ -114,11 +114,11 @@ TEPP retains a logistic-normal CPU reference while allowing adapter backends tha TEPP retains a logistic-normal CPU reference while allowing adapter backends that satisfy shared-latent, posterior, temporal, relational, and measurement-invariance contracts. House-voice style residue is modeled as explicit structure, not unique latent content and not a stopword deletion (Roberts et al., 2019). TEPP retains a logistic-normal CPU reference while allowing adapter backends that satisfy shared-latent, posterior, temporal, relational, and measurement-invariance contracts. Non-lexical modality is modeled as explicit structure, not unique latent content and not a stopword deletion (Bateman, 2008). TEPP retains a logistic-normal CPU reference while allowing adapter backends that satisfy shared-latent, posterior, temporal, relational, and measurement-invariance contracts. Corpus-background wording is modeled as explicit structure, not unique latent content and not a stopword deletion (Chemudugunta et al., 2007). -Brown, T. B., Mann, B., Ryder, N., Subbiah, M., Kaplan, J. D., Dhariwal, P., Neelakantan, A., Shyam, P., Sastry, G., Askell, A., Agarwal, S., Herbert-Voss, A., Krueger, G., Henighan, T., Child, R., Ramesh, A., Ziegler, D., Wu, J., Winter, C., … Amodei, D. (2020). Language models are few-shot learners. *Advances in Neural Information Processing Systems, 33*, 1877–1901. https://papers.neurips.cc/paper/2020/hash/1457c0d6bfcb4967418bfb8ac142f64a-Abstract.html +Brown, T. B., Mann, B., Ryder, N., Subbiah, M., Kaplan, J. D., Dhariwal, P., Neelakantan, A., Shyam, P., Sastry, G., Askell, A., Agarwal, S., Herbert-Voss, A., Krueger, G., Henighan, T., Child, R., Ramesh, A., Ziegler, D., Wu, J., Winter, C., … Amodei, D. (2020). Language models are few-shot learners. *Advances in Neural Information Processing Systems, 33*, 1877–1901. https://papers.neurips.cc/paper/2020/hash/1457c0d6bf4967418bfb8ac142f64a-Abstract.html Reynolds, L., & McDonell, K. (2021). Prompt programming for large language models: Beyond the few-shot paradigm. In *Extended abstracts of the 2021 CHI conference on human factors in computing systems*. Association for Computing Machinery. https://doi.org/10.1145/3411763.3451760 -Liu, P., Yuan, W., Fu, J., Jiang, Z., Hayashi, H., & Neubig, E. (2023). Pre-train, prompt, and predict: A systematic survey of prompting methods in natural language processing. *ACM Computing Surveys, 55*(9), Article 195. https://doi.org/10.1145/3560815 +Liu, P., Yuan, W., Fu, J., Jiang, Z., Hayashi, H., & Neubig, G. (2023). Pre-train, prompt, and predict: A systematic survey of prompting methods in natural language processing. *ACM Computing Surveys, 55*(9), Article 195. https://doi.org/10.1145/3560815 TEPP retains a logistic-normal CPU reference while allowing adapter backends that satisfy shared-latent, posterior, temporal, relational, and measurement-invariance contracts. Brown et al. (2020) and Reynolds and McDonell (2021) provide primary research context for prompts as task-conditioning and prompt-programming mechanisms; they do not define TEPP's latent-content labels. As a normative ADR 0004/0012 contract, instruction and prompt boilerplate is therefore modeled as explicit method structure, not unique latent content and not a stopword deletion. Liu et al. (2023) is secondary survey background only and is not evidence for that repository-specific classification. `topic_lineage` keeps one global topic identity when activity becomes dormant or reactivated. From 4f7f6ba7545e57d36c4660badf572780e96e0fed Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 08:21:43 +0900 Subject: [PATCH 286/296] fix(research): restore canonical Brown URL --- docs/research/standards-and-literature.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/research/standards-and-literature.md b/docs/research/standards-and-literature.md index 618c3aa5f..e399eb866 100644 --- a/docs/research/standards-and-literature.md +++ b/docs/research/standards-and-literature.md @@ -114,7 +114,7 @@ TEPP retains a logistic-normal CPU reference while allowing adapter backends tha TEPP retains a logistic-normal CPU reference while allowing adapter backends that satisfy shared-latent, posterior, temporal, relational, and measurement-invariance contracts. House-voice style residue is modeled as explicit structure, not unique latent content and not a stopword deletion (Roberts et al., 2019). TEPP retains a logistic-normal CPU reference while allowing adapter backends that satisfy shared-latent, posterior, temporal, relational, and measurement-invariance contracts. Non-lexical modality is modeled as explicit structure, not unique latent content and not a stopword deletion (Bateman, 2008). TEPP retains a logistic-normal CPU reference while allowing adapter backends that satisfy shared-latent, posterior, temporal, relational, and measurement-invariance contracts. Corpus-background wording is modeled as explicit structure, not unique latent content and not a stopword deletion (Chemudugunta et al., 2007). -Brown, T. B., Mann, B., Ryder, N., Subbiah, M., Kaplan, J. D., Dhariwal, P., Neelakantan, A., Shyam, P., Sastry, G., Askell, A., Agarwal, S., Herbert-Voss, A., Krueger, G., Henighan, T., Child, R., Ramesh, A., Ziegler, D., Wu, J., Winter, C., … Amodei, D. (2020). Language models are few-shot learners. *Advances in Neural Information Processing Systems, 33*, 1877–1901. https://papers.neurips.cc/paper/2020/hash/1457c0d6bf4967418bfb8ac142f64a-Abstract.html +Brown, T. B., Mann, B., Ryder, N., Subbiah, M., Kaplan, J. D., Dhariwal, P., Neelakantan, A., Shyam, P., Sastry, G., Askell, A., Agarwal, S., Herbert-Voss, A., Krueger, G., Henighan, T., Child, R., Ramesh, A., Ziegler, D., Wu, J., Winter, C., … Amodei, D. (2020). Language models are few-shot learners. *Advances in Neural Information Processing Systems, 33*, 1877–1901. https://papers.neurips.cc/paper/2020/hash/1457c0d6bfcb4967418bfb8ac142f64a-Abstract.html Reynolds, L., & McDonell, K. (2021). Prompt programming for large language models: Beyond the few-shot paradigm. In *Extended abstracts of the 2021 CHI conference on human factors in computing systems*. Association for Computing Machinery. https://doi.org/10.1145/3411763.3451760 From 8eb95d14f43ac40b06d0d039ef4d94ad1a053924 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 12:03:42 +0900 Subject: [PATCH 287/296] docs(longitudinal): currentize numerical owner evidence --- docs/research/longitudinal-binary64-mean-owner-handoff.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/research/longitudinal-binary64-mean-owner-handoff.md b/docs/research/longitudinal-binary64-mean-owner-handoff.md index 864efb188..8a5ab313f 100644 --- a/docs/research/longitudinal-binary64-mean-owner-handoff.md +++ b/docs/research/longitudinal-binary64-mean-owner-handoff.md @@ -10,7 +10,7 @@ TEPP PR #310 exposes a compact public RED through `crates/longitudinal_core/test The represented rate values are `P = 0x1.8p+106` (`0x4698000000000000`), `-2^53`, and `-1`. The exact real sum is `P - 2^53 - 1`. The correctly rounded mean is `0x1.fffffffffffffp+104` (`0x467fffffffffffff`), while TEPP's current `scaled_compensated_mean` path returns `0x4680000000000000`, one ULP high because the same-side coalescing step can lose the final `-1` before the original-count division. -This is an ordinary finite three-value input through the public already-centered residual path. It is not a resource-extreme synthetic witness. The latest TEPP source-level RED authority remains Rust Foundation run `34595076660`: formatting and Clippy passed, 1,568/1,569 tests passed, and only `mixed_sign_mean_rounding_contract::half_ulp_tail_changes_the_final_mixed_sign_rounding` failed with the expected bit mismatch. +This is an ordinary finite three-value input through the public already-centered residual path. It is not a resource-extreme synthetic witness. The current TEPP exact head is `4f7f6ba7545e57d36c4660badf572780e96e0fed`; Rust Foundation run `34657771583` passes exact-head checkout, pinned toolchain/tool verification, formatting and Clippy, then fails at the no-retry workspace test suite. Production line and branch coverage generation on the same run also fails before authored-denominator enforcement. The scientific RED remains the public mixed-sign half-ULP rounding contract; no coverage denominator relaxation or alternate local mean is authorized. ## Bounded-context ownership @@ -32,7 +32,7 @@ Current exact-head hosted evidence on `432765c...` is mixed and therefore non-me - native CodeQL `34606295285`, Security Scan `34606295363`, SAST Semgrep `34606295323`, and ClusterFuzzLite `34606295428` are GREEN; - repository CI `34606295259` is RED because `gpu-smoke` job `103285576158` failed while installing the software Vulkan adapter, before Vulkan availability or GPU parity executed. Package and fuzz jobs are GREEN. That run is historical evidence of a failed acquisition path, not the current root-cause authority for GPU readiness; -- required delegated CodeQL PR `34606295286` is RED. Python job `103285476466` and Actions job `103285476530` both successfully read the current-head dispatch verdict and then failed at `Release runner or enforce current-head CodeQL verdict`; only afterward did dispatch job `103286411396` succeed. This remains the central producer/consumer settlement class and must be repaired at the canonical `.github` owner rather than copied into TEPP or fast-mlsirm; +- required delegated CodeQL PR `34606295286` is RED. Python job `103285476466` and Actions job `103285476530` both successfully read the current-head dispatch verdict and then failed at `Release runner or enforce current-head CodeQL verdict`; only afterward did dispatch job `103286411396` succeed. The live canonical repair vehicle is `.github#2051@558693e0333e48012beea142f739bc634b0674a7`, which now records a versioned rollout/bootstrap prerequisite because the protected handler and PR-head client currently use incompatible exact run-name identities. TEPP and fast-mlsirm must not copy that control-plane repair, synthesize receipts, or loop no-op reruns; - current formal reviews are COMMENTED only. The CodeRabbit oracle/standards-link findings are resolved, but there is no qualifying submitted current-head `APPROVED` review; - current Noema evidence admitted `orchestrator/free` but the verdict request ended in HTTP 502; the exact consumer evidence belongs to the contextual-orchestrator owner path rather than a leaf provider/model retry; - current central coverage contexts still do not provide authoritative exact-head Rust owned line+branch evidence for this material Rust implementation; no denominator trick or Python line-touch substitute is acceptable; @@ -50,7 +50,7 @@ A non-weakening owner-side candidate is therefore **arena packing**, not a lower The arena candidate is acceptable only if the owner proves, on one exact head: generated score/E-step layouts use no more than ten storage buffers; packing offsets and arena round trips are deterministic including empty/padded segments; CPU-`f64` parity thresholds on realistic marginal/EAP fixtures remain unchanged; the existing SwiftShader capacity probe is followed by the real explicit GPU parity path rather than skip/fallback; realistic per-iteration or p95 evidence shows the packing does not erase the GPU benefit; and recovery/error behavior remains intact. If the owner rejects arena packing, the resource-topology reason belongs in its ADR/evidence rather than restoring an arbitrary 18-buffer admission floor. -The #1717 forward repair also moved the environment-specific capacity probe out of ordinary workspace tests into `crates/mlsirm-core/examples/gpu_adapter_capacity.rs`, so ordinary `cargo test --workspace` no longer performs hardware acquisition outside the dedicated GPU lane. Its exact-head CI `34607995391` reached `gpu-smoke` job `103291084354`, configured image-local SwiftShader, proved the Vulkan loader, ran the isolated capacity probe, reproduced the 10-versus-18 buffer mismatch, and failed closed at that invariant. The workflow was subsequently cancelled when the PR returned to Draft; the completed capacity RED remains diagnostic evidence. The ordinary Rust/Python/package/fuzz jobs that were cancelled are neither current-running nor terminal-GREEN landing evidence. +The #1717 forward repair also moved the environment-specific capacity probe out of ordinary workspace tests into `crates/mlsirm-core/examples/gpu_adapter_capacity.rs`, so ordinary `cargo test --workspace` no longer performs hardware acquisition outside the dedicated GPU lane. Exact-head CI `34607995391` is terminal: `gpu-smoke` job `103291084354` configures image-local SwiftShader, proves the Vulkan loader, runs the isolated capacity probe, reproduces the 10-versus-18 buffer mismatch, and fails closed at that invariant; package job `103291084379` is GREEN. Rust job `103291084335`, Python 3.12 `103291084281`, Python 3.14 `103291084331`, and fuzz `103291084064` are CANCELLED, while aggregate `python` job `103292403176` is RED because its required matrix/GPU evidence is incomplete. Those cancellation conclusions are terminal facts only; this record does not infer their cause or promote them to GREEN. The next causal GPU repair therefore belongs to #1717 or its verified successor. #1816 must reacquire its own exact-head repository evidence only after that canonical CI path is protected-integrated; TEPP must wait for the resulting immutable numerical release. From c5f129138f18c72a0c75ec024c2c652d60782ac4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 13:00:19 +0900 Subject: [PATCH 288/296] docs(longitudinal): make mean handoff evidence head-stable --- docs/research/longitudinal-binary64-mean-owner-handoff.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/research/longitudinal-binary64-mean-owner-handoff.md b/docs/research/longitudinal-binary64-mean-owner-handoff.md index 8a5ab313f..6361fd6a2 100644 --- a/docs/research/longitudinal-binary64-mean-owner-handoff.md +++ b/docs/research/longitudinal-binary64-mean-owner-handoff.md @@ -10,7 +10,7 @@ TEPP PR #310 exposes a compact public RED through `crates/longitudinal_core/test The represented rate values are `P = 0x1.8p+106` (`0x4698000000000000`), `-2^53`, and `-1`. The exact real sum is `P - 2^53 - 1`. The correctly rounded mean is `0x1.fffffffffffffp+104` (`0x467fffffffffffff`), while TEPP's current `scaled_compensated_mean` path returns `0x4680000000000000`, one ULP high because the same-side coalescing step can lose the final `-1` before the original-count division. -This is an ordinary finite three-value input through the public already-centered residual path. It is not a resource-extreme synthetic witness. The current TEPP exact head is `4f7f6ba7545e57d36c4660badf572780e96e0fed`; Rust Foundation run `34657771583` passes exact-head checkout, pinned toolchain/tool verification, formatting and Clippy, then fails at the no-retry workspace test suite. Production line and branch coverage generation on the same run also fails before authored-denominator enforcement. The scientific RED remains the public mixed-sign half-ULP rounding contract; no coverage denominator relaxation or alternate local mean is authorized. +This is an ordinary finite three-value input through the public already-centered residual path. It is not a resource-extreme synthetic witness. The latest fully terminal predecessor evidence before this documentation repair is TEPP PR #310 head `8eb95d14f43ac40b06d0d039ef4d94ad1a053924`: Rust Foundation run `34669350804` passes exact-head checkout, pinned toolchain/tool verification, formatting and Clippy, then reports 1,577 PASS / 1 FAIL / 0 skipped across 1,578 tests, with the single failure being the public mixed-sign half-ULP rounding contract. Production line and branch coverage generation on the same run also fails before authored-denominator enforcement because that product RED stops the instrumented suite. This paragraph is predecessor evidence, not a self-referential claim about the live PR head; current acceptance authority is always the live PR head plus its own terminal checks. No coverage denominator relaxation or alternate local mean is authorized. ## Bounded-context ownership From e0a25d5cab4b12c824d4662aec4431d9e32bb9bc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 13:00:38 +0900 Subject: [PATCH 289/296] docs(longitudinal): remove untraced IEEE project-status claim --- docs/research/occasion-mean-event-time-composition.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/research/occasion-mean-event-time-composition.md b/docs/research/occasion-mean-event-time-composition.md index c7450e6a1..7872c6344 100644 --- a/docs/research/occasion-mean-event-time-composition.md +++ b/docs/research/occasion-mean-event-time-composition.md @@ -38,7 +38,7 @@ Current #310 branch tests: - `sparse_unaligned_and_nonfinite_occasion_inputs_fail_closed` — empty/singleton payloads, unaligned one-unit occasions, and non-finite event-time input fail closed at the Longitudinal boundary. - `singleton_wave_unit_does_not_manufacture_or_block_lag_evidence` — a one-wave unit may contribute to an admitted occasion mean but cannot count toward the two-unit lag-evidence floor; two genuine lag-contributing units still recover the known rate. -The initial occasion-composition RED is `75b0184d2f6341ef23cf14fc84398c68d8d95d22`. Deterministic-order RED `8a59019ed3112a3e27dd0dcd1b6b86d8d45e5435` exposes order-dependent same-sign averaging; causal repair `465d139dce6101c4958c8b0827b6ef5d674b54c2` orders same-sign values before averaging. Successor-evidence commit `b9e952bb8a893f62aaead59cdf825b5e3c6251c6` ports #486's valid same-panel occasion-vs-CWC scientific claim boundary into the owner-correct Longitudinal test surface. Admission-evidence commit `aad56b502bbdfab08ba896b7d3560884c87fc589` preserves #486's sparse/unaligned/non-finite and singleton-wave cases without retaining its wrong crate ownership. Subnormal-rounding RED `9aff817f9e0f82b9cdb2077f3f62bb3e6a987103` exposes the incremental same-sign mean's loss of a half-ULP update at the binary64 floor; causal repair `40e057b83980a0cc501ad936c02e2d59f90a6fe9` replaces that recurrence with deterministic normalized compensated averaging. Signed-zero residual RED `crates/longitudinal_core/tests/occasion_mean_signed_zero_contract.rs` drives `center_occasion_mean_event_lags` with a T=3 mixed-sign panel whose middle score is `-0.0`; causal repair `238c4def7a5a5f19963d2e5cc936dd750d55c5a1` canonicalizes only a validated exact-zero occasion-mean residual to public `+0.0`. IEEE Std 754-2019 remains the active published floating-point standard while IEEE P754 is the active revision project as of 2026-09-03. The owner-correct implementation remains in `crates/longitudinal_core/src/occasion_mean.rs`. Only the current exact branch head may be used for merge evidence. +The initial occasion-composition RED is `75b0184d2f6341ef23cf14fc84398c68d8d95d22`. Deterministic-order RED `8a59019ed3112a3e27dd0dcd1b6b86d8d45e5435` exposes order-dependent same-sign averaging; causal repair `465d139dce6101c4958c8b0827b6ef5d674b54c2` orders same-sign values before averaging. Successor-evidence commit `b9e952bb8a893f62aaead59cdf825b5e3c6251c6` ports #486's valid same-panel occasion-vs-CWC scientific claim boundary into the owner-correct Longitudinal test surface. Admission-evidence commit `aad56b502bbdfab08ba896b7d3560884c87fc589` preserves #486's sparse/unaligned/non-finite and singleton-wave cases without retaining its wrong crate ownership. Subnormal-rounding RED `9aff817f9e0f82b9cdb2077f3f62bb3e6a987103` exposes the incremental same-sign mean's loss of a half-ULP update at the binary64 floor; causal repair `40e057b83980a0cc501ad936c02e2d59f90a6fe9` replaces that recurrence with deterministic normalized compensated averaging. Signed-zero residual RED `crates/longitudinal_core/tests/occasion_mean_signed_zero_contract.rs` drives `center_occasion_mean_event_lags` with a T=3 mixed-sign panel whose middle score is `-0.0`; causal repair `238c4def7a5a5f19963d2e5cc936dd750d55c5a1` canonicalizes only a validated exact-zero occasion-mean residual to public `+0.0`. IEEE Std 754-2019 is the cited floating-point standard used for the signed-zero and ties-to-even contract; no time-dependent IEEE P754 project-status claim is used as scientific evidence. The owner-correct implementation remains in `crates/longitudinal_core/src/occasion_mean.rs`. Only the current exact branch head may be used for merge evidence. ## Claim boundary From 32ac500f56ed1f2dbae7a00f1202803c8359c8e6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 13:00:54 +0900 Subject: [PATCH 290/296] docs(longitudinal): keep IEEE evidence time-stable --- docs/research/longitudinal-within-between.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/research/longitudinal-within-between.md b/docs/research/longitudinal-within-between.md index 260d88a43..583c02ca3 100644 --- a/docs/research/longitudinal-within-between.md +++ b/docs/research/longitudinal-within-between.md @@ -43,8 +43,9 @@ That sign bit does not represent positive versus negative within-person change: the deviation is exactly zero. The public decomposition boundary therefore canonicalizes only validated exact-zero residuals to `+0.0`; private numerical intermediates remain free to retain signed zero where it is diagnostically -meaningful. IEEE Std 754-2019 remains the active published floating-point -standard while IEEE P754 is the active revision project as of 2026-09-03. +meaningful. IEEE Std 754-2019 is the cited floating-point standard for this +signed-zero contract; no time-dependent IEEE P754 project-status claim is used +as scientific evidence. Signed-zero traceability: From e515de40fa8e86375dc83852ec9d0943c521a815 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 13:01:06 +0900 Subject: [PATCH 291/296] test(longitudinal): escape APA en dashes for Ruff --- .../quality/test_longitudinal_scientific_authority_sync.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/quality/test_longitudinal_scientific_authority_sync.py b/tests/quality/test_longitudinal_scientific_authority_sync.py index bfc2130f3..0750990c6 100644 --- a/tests/quality/test_longitudinal_scientific_authority_sync.py +++ b/tests/quality/test_longitudinal_scientific_authority_sync.py @@ -60,17 +60,17 @@ def test_irregular_rate_weighting_sources_are_doctored_in_canonical_register() - assert ( "Huang, Y., & Leroux, B. (2011). Informative cluster sizes for subcluster-level " "covariates and weighted generalized estimating equations. *Biometrics, 67*(3), " - "843–851." in literature + "843\u2013851." in literature ) assert ( "Kahan, B. C., Li, F., Blette, B., Jairath, V., Copas, A., & Harhay, M. O. " "(2023). Informative cluster size in cluster-randomised trials: A case study from " - "the TRIGGER trial. *Clinical Trials, 20*(6), 661–669." in literature + "the TRIGGER trial. *Clinical Trials, 20*(6), 661\u2013669." in literature ) assert ( "Wang, M., Kong, M., & Datta, S. (2011). Inference for marginal linear models for " "clustered longitudinal data with potentially informative cluster sizes. " - "*Statistical Methods in Medical Research, 20*(4), 347–367." in literature + "*Statistical Methods in Medical Research, 20*(4), 347\u2013367." in literature ) assert "irregular-rate estimand" in literature assert "record multiplicity" in literature From b949166fe6fa565064534f724d0a3c6d8cdfb38b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 13:05:50 +0900 Subject: [PATCH 292/296] refactor(longitudinal): remove shadow irregular-rate facade --- crates/longitudinal_core/src/lib.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/crates/longitudinal_core/src/lib.rs b/crates/longitudinal_core/src/lib.rs index 9f0607606..c72238e09 100644 --- a/crates/longitudinal_core/src/lib.rs +++ b/crates/longitudinal_core/src/lib.rs @@ -22,7 +22,6 @@ mod irregular_rate_estimand; mod irregular_residual; mod level; mod occasion_mean; -mod stable_irregular_rate; mod stationary; mod temporal_association; @@ -68,6 +67,10 @@ pub use irregular_residual::EventTimedObservation; pub use irregular_residual::LaggedWithinResidual; /// Cluster-mean-center consecutive event-time lags inside each unit. pub use irregular_residual::center_within_unit_event_lags; +/// Mean exact scalar log-rate on already-centered residuals with stable count weighting. +pub use irregular_residual::recover_centered_irregular_residual_log_rate; +/// Pairwise-mean exact log-rate after CWC with stable count weighting. +pub use irregular_residual::recover_within_unit_irregular_residual_log_rate; /// Refuse treating a CWC residual log-rate as raw-process AR drift. pub use irregular_residual::refuse_cwc_residual_log_rate_as_raw_process_drift; /// Established longitudinal component level. @@ -80,12 +83,8 @@ pub use occasion_mean::center_occasion_mean_event_lags; pub use occasion_mean::recover_occasion_mean_centered_irregular_residual_log_rate; /// Refuse treating occasion-mean residual lag as within-person change. pub use occasion_mean::refuse_occasion_mean_centered_log_rate_as_within_person_lag; -/// Mean exact scalar log-rate on already-centered residuals with stable count weighting. -pub use stable_irregular_rate::recover_centered_irregular_residual_log_rate; -/// Pairwise-mean exact log-rate after CWC with stable count weighting. -pub use stable_irregular_rate::recover_within_unit_irregular_residual_log_rate; /// Recover the scalar Driver p.16 unstandardised stationary within-person variance. pub use stationary::recover_stationary_within_variance; /// Recover a valid event-time lagged correlation from covariance and both /// marginal variances through the typed event-time boundary. -pub use temporal_association::recover_event_time_lagged_correlation; +pub use temporal_association::recover_event_time_lagged_correlation; \ No newline at end of file From c1c312c7437d74768f4303b7b0945fa1e5c8e139 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 13:05:56 +0900 Subject: [PATCH 293/296] refactor(longitudinal): remove shadow irregular-rate facade --- crates/longitudinal_core/src/stable_irregular_rate.rs | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 crates/longitudinal_core/src/stable_irregular_rate.rs diff --git a/crates/longitudinal_core/src/stable_irregular_rate.rs b/crates/longitudinal_core/src/stable_irregular_rate.rs deleted file mode 100644 index 3eabc56d3..000000000 --- a/crates/longitudinal_core/src/stable_irregular_rate.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! Stable irregular residual log-rate API. -//! -//! Numerical ownership lives in [`crate::irregular_residual`], alongside event -//! ordering, CWC admission, and the shared same-sign log-rate primitive. The -//! typed weighting/denominator contract is exported directly from the crate root -//! and is not re-exported through this private compatibility facade. - -/// Legacy scalar irregular-rate entry points retained for compatibility. -pub use crate::irregular_residual::{ - recover_centered_irregular_residual_log_rate, recover_within_unit_irregular_residual_log_rate, -}; From 87af89d294de1e447c04c041b414a92bc56017b7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 13:06:05 +0900 Subject: [PATCH 294/296] test(longitudinal): retire source-string shadow-authority checks --- ...rregular_rate_shadow_authority_contract.rs | 67 ------------------- 1 file changed, 67 deletions(-) delete mode 100644 crates/longitudinal_core/tests/irregular_rate_shadow_authority_contract.rs diff --git a/crates/longitudinal_core/tests/irregular_rate_shadow_authority_contract.rs b/crates/longitudinal_core/tests/irregular_rate_shadow_authority_contract.rs deleted file mode 100644 index aef0f09b6..000000000 --- a/crates/longitudinal_core/tests/irregular_rate_shadow_authority_contract.rs +++ /dev/null @@ -1,67 +0,0 @@ -#![forbid(unsafe_code)] - -//! Architecture fitness for one Longitudinal Modeling irregular-rate estimand. -//! -//! The crate-public path and its internal temporal composition must share one -//! numerical authority. A compatibility/public facade may re-export the named -//! operations, but it must not declare wrapper functions or carry a second -//! mean/log-rate implementation whose floating-point edge semantics can drift. - -#[test] -fn shadowed_centered_rate_does_not_reintroduce_direct_ratio_rejection() { - let source = include_str!("../src/irregular_residual.rs"); - - assert!( - !source.contains("let ratio = pair.later_residual() / pair.earlier_residual();"), - "the internal centered-rate path still rejects finite log-rates solely because the direct residual ratio overflows or underflows" - ); - assert!( - source.contains("driver_same_sign_log_rate("), - "the internal composition must reuse the Longitudinal Modeling same-sign log-rate primitive" - ); -} - -#[test] -fn public_facade_reexports_the_canonical_recovery_functions_instead_of_wrapping_them() { - let canonical = include_str!("../src/irregular_residual.rs"); - let facade = include_str!("../src/stable_irregular_rate.rs"); - - assert!( - canonical.contains("pub fn recover_centered_irregular_residual_log_rate("), - "the canonical centered-rate function must remain re-exportable from the private implementation module" - ); - assert!( - canonical.contains("pub fn recover_within_unit_irregular_residual_log_rate("), - "the canonical CWC irregular-rate function must remain re-exportable from the private implementation module" - ); - assert!( - facade.contains("pub use crate::irregular_residual::{"), - "the public facade must re-export the canonical function identities rather than declare wrapper functions" - ); - assert!( - !facade.contains("pub fn recover_centered_irregular_residual_log_rate("), - "stable_irregular_rate still declares a second centered-rate wrapper function" - ); - assert!( - !facade.contains("pub fn recover_within_unit_irregular_residual_log_rate("), - "stable_irregular_rate still declares a second CWC irregular-rate wrapper function" - ); -} - -#[test] -fn public_irregular_rate_facade_has_no_second_numerical_implementation() { - let facade = include_str!("../src/stable_irregular_rate.rs"); - - assert!( - !facade.contains("fn stable_mean("), - "stable_irregular_rate still owns a second floating-point mean implementation" - ); - assert!( - facade.contains("recover_centered_irregular_residual_log_rate"), - "the public facade must expose the centered-rate estimand from the canonical Longitudinal Modeling implementation" - ); - assert!( - facade.contains("recover_within_unit_irregular_residual_log_rate"), - "the public facade must expose the CWC irregular-rate estimand from the canonical Longitudinal Modeling implementation" - ); -} From dd8018a96b6a64af926a704321134409b697c9af Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 13:07:41 +0900 Subject: [PATCH 295/296] fix(longitudinal): point occasion composition at canonical rate owner --- crates/longitudinal_core/src/occasion_mean.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/longitudinal_core/src/occasion_mean.rs b/crates/longitudinal_core/src/occasion_mean.rs index ab206dcc6..1e0f01a5d 100644 --- a/crates/longitudinal_core/src/occasion_mean.rs +++ b/crates/longitudinal_core/src/occasion_mean.rs @@ -9,9 +9,9 @@ use std::collections::{BTreeMap, BTreeSet}; use crate::irregular_residual::{ - EventTimedObservation, LaggedWithinResidual, scaled_compensated_mean, + EventTimedObservation, LaggedWithinResidual, recover_centered_irregular_residual_log_rate, + scaled_compensated_mean, }; -use crate::stable_irregular_rate::recover_centered_irregular_residual_log_rate; use crate::{EventTimeInterval, LongitudinalError}; /// Form consecutive event-time lags after subtracting each occasion's group mean. From ba10820e0d28cc33d1b91ef37f6f6d163b3d91e9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 12 Sep 2026 13:09:35 +0900 Subject: [PATCH 296/296] style(longitudinal): restore rustfmt EOF newline --- crates/longitudinal_core/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/longitudinal_core/src/lib.rs b/crates/longitudinal_core/src/lib.rs index c72238e09..c909c770b 100644 --- a/crates/longitudinal_core/src/lib.rs +++ b/crates/longitudinal_core/src/lib.rs @@ -87,4 +87,4 @@ pub use occasion_mean::refuse_occasion_mean_centered_log_rate_as_within_person_l pub use stationary::recover_stationary_within_variance; /// Recover a valid event-time lagged correlation from covariance and both /// marginal variances through the typed event-time boundary. -pub use temporal_association::recover_event_time_lagged_correlation; \ No newline at end of file +pub use temporal_association::recover_event_time_lagged_correlation;