Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang

- `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, p. 16 `TDPREDEFFECTstd`; Table 2, p. 12; Eq. 3, p. 5; footnote 4; §7.1, pp. 18–19; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-30T04:11Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104) scalar standardised continuous time-dependent predictor effect on current main after `0ce16e8` dropped the pre-consolidation code while research notes already named the map (register items 75–76). Page 16 prints continuous-time parameters and, when appropriate, standardised matrices with the suffix `std`. The printed example on p. 16 is `discreteDRIFTstd`, not `TDPREDEFFECTstd`. Footnote 4: standardisations use only the relevant variance, not the total. Table 2 names `M` `TDPREDEFFECT` and names `TDPREDVAR` the time-dependent predictor variance. The affecting variance is `TDPREDVAR` `v`, not `TIPREDVAR`. The affected variance is within-subject `asymDIFFUSION` `-q / (2 a)`, because the process dynamics are individual, or average individual, temporal dynamics. The 2017-era `summary.ctsemFit.R` `n.TDpred` section forms unstandardised `TDPREDEFFECT` only and comments out `TDPREDVAR` and `TDPREDVARstd`. That source does not form a `TDPREDEFFECTstd` matrix; the scalar map is the footnote 4 standardisation of that named coefficient: `m · √v / √(-q / (2 a))`. Form strictly positive `asymDIFFUSION` first, then strictly positive `v`, then the continuous Dirac coefficient, then the SD ratio. Form the SD ratio before multiplying so an overflowing `m √v` does not lose a finite std. A zero coefficient after strictly positive SDs is exactly zero (`0 · Inf` is NaN). When that ratio overflows, form `(m / √p) √v`. Unstandardised `M` is defined for a zero coefficient and for zero predictor variance; standardised `TDPREDEFFECT` is not. Zero `q` or zero `v` has no positive SD and fails closed. Lasting `p` requires stable `a < 0`. A non-event clock fails closed. `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 this continuous Dirac coefficient. `m · √v / √(trait + p + added)` uses the total, not `asymDIFFUSION`, and is not `TDPREDEFFECTstd` when `TRAITVAR` is nonzero. `TRAITVAR` is not the standardisation variance. Independent of open `#303` `T0TDPREDEFFECTstd`, `#300` `TIPREDEFFECTstd`, `#299` `asymTIPREDEFFECTstd`, `#298` `DRIFTstd`, `#297` `DIFFUSIONstd`, `#296` `discreteDIFFUSIONstd`, `#280` `discreteDRIFTstd`, and `#272` `TIPREDVARstd`. Meredith (1993) remains unread (web search 2026-08-30T04:11Z: Springer/Cambridge Core paywalled; Unpaywall historically `is_oa: false`; Springer `content/pdf` is 403). 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.

- `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.

- `psychometric_core` recovers the Driver, Oud, and Voelkle (2017, Table 2, p. 12 `TRAITVAR`; §7.1, pp. 18–19; p. 16 `TRAITVARstd`; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-26T17:45Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104) scalar standardised trait variance on current main after `0ce16e8` dropped the pre-consolidation code while research notes already named the map (register items 81–82). Table 2 names `TRAITVAR` `φ_ξ` the latent trait variance/covariance and sets it `NULL` when there is no trait. Section 7.1 names traits the stable between-subject differences (unit-level unobserved heterogeneity). Page 16 prints standardised matrices with the suffix `std` when appropriate. The printed example on p. 16 is `discreteDRIFTstd`, not `TRAITVARstd`. Footnote 4 standardises using only the relevant variance, not the total. The relevant variance for that named between-subject correlation is `TRAITVAR`, not free first-occasion `T0VAR` and not process-dynamics `asymDIFFUSION`. The 2017-era source forms `TRAITVARstd` only when `TRAITVAR != 0`, as `solve(sqrt(diag(TRAITVAR))) %&% TRAITVAR` when `verbose = TRUE`. OpenMx `%&%` is `t(A) %*% B %*% A`. Unlike `T0VARstd`, that formation uses `diag(diag(TRAITVAR))` and does not add `diag(c(ridging))`. The ridge is a `T0VAR` numerical hack and is not this exact map. The scalar correlation is `trait / trait = 1` after strictly positive `TRAITVAR`. Form strictly positive `trait` first, then `1 / √trait`, then `(1 / √trait) trait (1 / √trait)`. Unstandardised `TRAITVAR` is defined for a zero trait; standardised `TRAITVAR` is not. Zero `TRAITVAR` skips forming `TRAITVARstd` in the 2017-era source and fails closed here. Between-subject variance is an event-time structural quantity, so a non-event clock fails closed. `TRAITVAR` does not require stable `a < 0`. Distinct positive `trait` recover the same 1. `p_0 / p_0 = 1` is `T0VARstd` and recovers the same number and remains a distinct named quantity. `t0_b² v` is `addedT0TIPREDVAR` and is extra first-occasion TI variance, not this correlation. Meredith (1993) remains unread (Unpaywall 2026-08-26T17:20Z: `is_oa: false`; OpenAlex closed; Springer `content/pdf` is an HTML stub). Mislevy (1991, *Psychometrika, 56*, 177–196) remains unread on the same terms (DOI `10.1007/bf02294457`; Unpaywall `is_oa: false`). Still not a Kalman filter, not a matrix `expm`, not ESEM estimation, not DSEM, and not ctsem estimation.
Expand Down
82 changes: 82 additions & 0 deletions crates/psychometric_core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,36 @@ pub enum PsychometricError {
/// `MANIFESTVARstd`. `λ² Var(η) + θ` is `Var(y)`, not the
/// correlation form of `Θ`.
ObservedVarianceIsNotStandardisedManifestVariance,

/// Driver p. 16 `TDPREDEFFECTstd` was requested without a
/// strictly positive `asymDIFFUSION`. Footnote 4 standardises
/// using only the relevant within-subject variance; zero `q`
/// has no positive process SD.
StandardisedTimeDependentEffectRequiresPositiveWithinSubjectVariance,
/// Driver p. 16 `TDPREDEFFECTstd` was requested without a
/// strictly positive time-dependent predictor variance.
/// Footnote 4 `m · √v / √p` has no positive affecting SD when
/// `v = 0`.
StandardisedTimeDependentEffectRequiresPositivePredictorVariance,
/// Driver Table 2 unstandardised `TDPREDEFFECT` `M` was treated
/// as `TDPREDEFFECTstd`. Unstandardised `M` is defined for a
/// zero coefficient and for zero predictor variance;
/// standardised `TDPREDEFFECT` is not.
UnstandardisedTimeDependentEffectIsNotStandardisedTimeDependentEffect,
/// Driver p. 16 `TIPREDEFFECTstd` `B · √v / √p` was treated as
/// `TDPREDEFFECTstd`. Equal numbers when `M = B` remain distinct
/// named quantities.
StandardisedTimeIndependentEffectIsNotStandardisedTimeDependentEffect,
/// Finite-interval intercept-style
/// `A^{-1}[e^{A Δt} − I] M · √v / √p` was treated as p. 16
/// `TDPREDEFFECTstd`. That map depends on `Δt` and is not the
/// continuous Dirac coefficient.
DiscreteStandardisedTimeDependentEffectIsNotStandardisedTimeDependentEffect,
/// `m · √v / √(trait + p + added)` was treated as p. 16
/// `TDPREDEFFECTstd`. Footnote 4 uses only `asymDIFFUSION`, not
/// total variance. `TRAITVAR` is not the standardisation
/// variance.
TraitContaminatedTimeDependentEffectIsNotStandardisedTimeDependentEffect,
}

impl fmt::Display for PsychometricError {
Expand Down Expand Up @@ -1235,6 +1265,24 @@ impl fmt::Display for PsychometricError {
Self::ObservedVarianceIsNotStandardisedManifestVariance => {
"observed-indicator variance is not standardised measurement-error variance"
}
Self::StandardisedTimeDependentEffectRequiresPositiveWithinSubjectVariance => {
"standardised time-dependent effect requires strictly positive within-subject variance"
}
Self::StandardisedTimeDependentEffectRequiresPositivePredictorVariance => {
"standardised time-dependent effect requires strictly positive predictor variance"
}
Self::UnstandardisedTimeDependentEffectIsNotStandardisedTimeDependentEffect => {
"unstandardised time-dependent effect is not standardised time-dependent effect"
}
Self::StandardisedTimeIndependentEffectIsNotStandardisedTimeDependentEffect => {
"standardised time-independent effect is not standardised time-dependent effect"
}
Self::DiscreteStandardisedTimeDependentEffectIsNotStandardisedTimeDependentEffect => {
"discrete standardised time-dependent effect is not standardised time-dependent effect"
}
Self::TraitContaminatedTimeDependentEffectIsNotStandardisedTimeDependentEffect => {
"trait-contaminated time-dependent effect is not standardised time-dependent effect"
}
};
formatter.write_str(message)
}
Expand Down Expand Up @@ -2073,4 +2121,38 @@ mod tests {
"measurement error is not standardised manifest-trait variance"
);
}

#[test]
fn standardised_time_dependent_effect_boundary_messages_are_stable() {
assert_eq!(
PsychometricError::StandardisedTimeDependentEffectRequiresPositiveWithinSubjectVariance
.to_string(),
"standardised time-dependent effect requires strictly positive within-subject variance"
);
assert_eq!(
PsychometricError::StandardisedTimeDependentEffectRequiresPositivePredictorVariance
.to_string(),
"standardised time-dependent effect requires strictly positive predictor variance"
);
assert_eq!(
PsychometricError::UnstandardisedTimeDependentEffectIsNotStandardisedTimeDependentEffect
.to_string(),
"unstandardised time-dependent effect is not standardised time-dependent effect"
);
assert_eq!(
PsychometricError::StandardisedTimeIndependentEffectIsNotStandardisedTimeDependentEffect
.to_string(),
"standardised time-independent effect is not standardised time-dependent effect"
);
assert_eq!(
PsychometricError::DiscreteStandardisedTimeDependentEffectIsNotStandardisedTimeDependentEffect
.to_string(),
"discrete standardised time-dependent effect is not standardised time-dependent effect"
);
assert_eq!(
PsychometricError::TraitContaminatedTimeDependentEffectIsNotStandardisedTimeDependentEffect
.to_string(),
"trait-contaminated time-dependent effect is not standardised time-dependent effect"
);
}
}
Loading
Loading