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: 1 addition & 1 deletion ARCHITECTURE.md

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `discreteTDPREDEFFECTstd`; Eq. 3, p. 4; Table 2, p. 12; footnote 4; §7.2, pp. 20–22; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-31T03:08Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104) scalar standardised discrete time-dependent predictor effect on current main as an independent successor of queued unstandardised `discreteTDPREDEFFECT` (#336). Page 16 prints discrete-time transformations for a chosen event interval and, when appropriate, standardised matrices with the suffix `std`. Footnote 4 standardises using only the relevant variance, not the total. Table 2 names `M` `TDPREDEFFECT` and names `TDPREDVAR` the time-dependent predictor variance. The 2017-era source (cran 2.5.0) forms `discreteDRIFT` as `OpenMx::expm(DRIFT * timeInterval)` and forms continuous `TDPREDEFFECT`; it does not form a `discreteTDPREDEFFECT` or `discreteTDPREDEFFECTstd` matrix, and it comments out `TDPREDVAR` / `TDPREDVARstd`. Page 22 does not report standardised TDPRED estimates because that example assumes no model for the predictor variance. The scalar map is the footnote 4 standardisation of the named discrete coefficient `e^{A Δt} M` after strictly positive `asymDIFFUSION` `p = −q / (2 a)` and strictly positive `TDPREDVAR` `v`: `e^{a Δt} m · √v / √p`. Form strictly positive `p` first, then strictly positive `v`, then the discrete coefficient, then the SD ratio. A zero coefficient is exactly zero after those positive SDs. Unstandardised `e^{a Δt} m` is defined for growing `a ≥ 0` and for zero diffusion; standardised discrete TDPRED is not. Zero `q` has no positive process SD and fails closed. Zero `v` has no positive predictor SD and fails closed. Lasting `p` requires stable `a < 0`. A non-event clock fails closed. A non-positive event interval fails closed. Binary64 underflow of `e^{a Δt}` to `+0` is a vanishing discrete coefficient and is kept. `m · √v / √p` is `TDPREDEFFECTstd` and does not depend on `Δt`. `e^{a Δt}` is `discreteDRIFTstd` after the scalar stationary SD ratio of 1 and is not this map. `A^{-1}[e^{A Δt} − I] M · √v / √p` is discreteCINT arithmetic on `M` and is not this map. `e^{a Δt} m · √v / √(trait + p + added)` uses the total and is not this residual map. Meredith (1993) remains unread. Mislevy (1991, *Psychometrika, 56*, 177–196) remains unread. Still not a Kalman filter, not a matrix `expm`, not ESEM estimation, not DSEM, and not ctsem estimation.

- `event_core` adds bounded Allen interval-consistency classification, atomic path-consistency closure, contradiction/resource refusals, and an explicit dependency-error fallback without claiming unrestricted global satisfiability.

- `psychometric_core` recovers the Driver, Oud, and Voelkle (2017, Table 2, p. 12 `MANIFESTTRAITVAR`; §7.1, p. 19; p. 16 `MANIFESTTRAITVARstd`; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-27T14:20Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104) scalar standardised manifest-trait variance on current main after `0ce16e8` dropped the pre-consolidation code while research notes already named the map (register items 83–84). Table 2 names `MANIFESTTRAITVAR` `Ψ_τ` the additional time-invariant variance-covariance on the measurement level and sets it `NULL` when there is no manifest trait. Equation 5 writes `Γ ~ N(τ, Ψ)` and names that covariance the manifest traits. Section 7.1 names manifest traits stable individual differences in indicator levels, distinct from process-level `TRAITVAR` `φ_ξ`. Page 16 prints standardised matrices with the suffix `std` when appropriate. The printed example on p. 16 is `discreteDRIFTstd`, not `MANIFESTTRAITVARstd`. Footnote 4 standardises using only the relevant variance, not the total. The relevant variance for that named indicator-level correlation is `MANIFESTTRAITVAR`, not process-level `TRAITVAR` and not residual `MANIFESTVAR` `θ`. The 2017-era source forms `MANIFESTTRAITVARstd` only when `MANIFESTTRAITVAR != 0`, as `solve(sqrt(diag(MANIFESTTRAITVAR) + ridging)) %&% MANIFESTTRAITVAR` when `verbose = TRUE`. OpenMx `%&%` is `t(A) %*% B %*% A`. Unlike `TRAITVARstd`, that formation adds `diag(c(ridging), n.manifest)`. The default `ridging = FALSE` adds 0, not `0.0001`; that ridge is a numerical hack and is not this exact map. The scalar correlation is `ψ / ψ = 1` after strictly positive `MANIFESTTRAITVAR`. Form strictly positive `ψ` first, then `1 / √ψ`, then `(1 / √ψ) ψ (1 / √ψ)`. Unstandardised `MANIFESTTRAITVAR` is defined for a zero trait; standardised `MANIFESTTRAITVAR` is not. Zero `MANIFESTTRAITVAR` skips forming `MANIFESTTRAITVARstd` in the 2017-era source and fails closed here. Indicator-level trait variance is an event-time structural quantity, so a non-event clock fails closed. `MANIFESTTRAITVAR` does not require stable `a < 0`. Distinct positive `ψ` recover the same 1. `trait / trait = 1` is `TRAITVARstd` and recovers the same number and remains a distinct named quantity. `θ` is `MANIFESTVAR` and is measurement error, not this correlation. Meredith (1993) remains unread (web search 2026-08-27T14:20Z: Springer/Cambridge Core paywalled; Unpaywall historically `is_oa: false`; Springer `content/pdf` is an HTML stub). Mislevy (1991, *Psychometrika, 56*, 177–196) remains unread on the same terms (DOI `10.1007/bf02294457`). Still not a Kalman filter, not a matrix `expm`, not ESEM estimation, not DSEM, and not ctsem estimation.
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md

Large diffs are not rendered by default.

91 changes: 91 additions & 0 deletions crates/psychometric_core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,37 @@ pub enum PsychometricError {
/// `MANIFESTVARstd`. `λ² Var(η) + θ` is `Var(y)`, not the
/// correlation form of `Θ`.
ObservedVarianceIsNotStandardisedManifestVariance,
/// Footnote-4 `discreteTDPREDEFFECTstd` was requested without a
/// strictly positive `asymDIFFUSION`. Footnote 4 standardises
/// using only the relevant variance; zero `q` has no positive
/// process SD.
StandardisedDiscreteTimeDependentPredictorEffectRequiresPositiveStationaryVariance,
/// Footnote-4 `discreteTDPREDEFFECTstd` was requested without a
/// strictly positive time-dependent predictor variance. Page 22
/// does not form standardised TDPRED estimates when there is no
/// model for that variance; zero `v` has no positive predictor SD.
StandardisedDiscreteTimeDependentPredictorEffectRequiresPositivePredictorVariance,
/// Unstandardised remaining impulse `e^{a Δt} m` was treated as
/// `discreteTDPREDEFFECTstd`. Unstandardised `e^{a Δt} m` is
/// defined for growing `a ≥ 0` and for zero diffusion;
/// standardised discrete TDPRED is not.
UnstandardisedDiscreteTimeDependentPredictorEffectIsNotStandardisedDiscreteTimeDependentPredictorEffect,
/// Footnote-4 `TDPREDEFFECTstd` `m · √v / √p` was treated as
/// `discreteTDPREDEFFECTstd`. The continuous std does not depend
/// on the event interval.
StandardisedTimeDependentPredictorEffectIsNotStandardisedDiscreteTimeDependentPredictorEffect,
/// `discreteDRIFTstd` `e^{a Δt}` was treated as
/// `discreteTDPREDEFFECTstd`. The auto-effect is not
/// `e^{a Δt} m · √v / √p`.
StandardisedDiscreteDriftIsNotStandardisedDiscreteTimeDependentPredictorEffect,
/// Intercept-style `A^{-1}[e^{A Δt} − I] M · √v / √p` was treated
/// as `discreteTDPREDEFFECTstd`. That is discreteCINT arithmetic
/// on `M`.
InterceptStyleStandardisedTimeDependentEffectIsNotStandardisedDiscreteTimeDependentPredictorEffect,
/// `e^{a Δt} m · √v / √(trait + p + added)` was treated as
/// `discreteTDPREDEFFECTstd`. Footnote 4 uses `asymDIFFUSION`,
/// not the total.
TraitScaledDiscreteTimeDependentPredictorEffectIsNotStandardisedDiscreteTimeDependentPredictorEffect,
}

impl fmt::Display for PsychometricError {
Expand Down Expand Up @@ -1235,6 +1266,27 @@ impl fmt::Display for PsychometricError {
Self::ObservedVarianceIsNotStandardisedManifestVariance => {
"observed-indicator variance is not standardised measurement-error variance"
}
Self::StandardisedDiscreteTimeDependentPredictorEffectRequiresPositiveStationaryVariance => {
"standardised discrete time-dependent predictor effect requires strictly positive stationary within-subject variance"
}
Self::StandardisedDiscreteTimeDependentPredictorEffectRequiresPositivePredictorVariance => {
"standardised discrete time-dependent predictor effect requires strictly positive time-dependent predictor variance"
}
Self::UnstandardisedDiscreteTimeDependentPredictorEffectIsNotStandardisedDiscreteTimeDependentPredictorEffect => {
"unstandardised discrete time-dependent predictor effect is not standardised discrete time-dependent predictor effect"
}
Self::StandardisedTimeDependentPredictorEffectIsNotStandardisedDiscreteTimeDependentPredictorEffect => {
"standardised time-dependent predictor effect is not standardised discrete time-dependent predictor effect"
}
Self::StandardisedDiscreteDriftIsNotStandardisedDiscreteTimeDependentPredictorEffect => {
"standardised discrete drift is not standardised discrete time-dependent predictor effect"
}
Self::InterceptStyleStandardisedTimeDependentEffectIsNotStandardisedDiscreteTimeDependentPredictorEffect => {
"intercept-style standardised time-dependent effect is not standardised discrete time-dependent predictor effect"
}
Self::TraitScaledDiscreteTimeDependentPredictorEffectIsNotStandardisedDiscreteTimeDependentPredictorEffect => {
"trait-scaled discrete time-dependent predictor effect is not standardised discrete time-dependent predictor effect"
}
};
formatter.write_str(message)
}
Expand Down Expand Up @@ -2073,4 +2125,43 @@ mod tests {
"measurement error is not standardised manifest-trait variance"
);
}

#[test]
fn standardised_discrete_time_dependent_predictor_effect_boundary_messages_are_stable() {
assert_eq!(
PsychometricError::StandardisedDiscreteTimeDependentPredictorEffectRequiresPositiveStationaryVariance
.to_string(),
"standardised discrete time-dependent predictor effect requires strictly positive stationary within-subject variance"
);
assert_eq!(
PsychometricError::StandardisedDiscreteTimeDependentPredictorEffectRequiresPositivePredictorVariance
.to_string(),
"standardised discrete time-dependent predictor effect requires strictly positive time-dependent predictor variance"
);
assert_eq!(
PsychometricError::UnstandardisedDiscreteTimeDependentPredictorEffectIsNotStandardisedDiscreteTimeDependentPredictorEffect
.to_string(),
"unstandardised discrete time-dependent predictor effect is not standardised discrete time-dependent predictor effect"
);
assert_eq!(
PsychometricError::StandardisedTimeDependentPredictorEffectIsNotStandardisedDiscreteTimeDependentPredictorEffect
.to_string(),
"standardised time-dependent predictor effect is not standardised discrete time-dependent predictor effect"
);
assert_eq!(
PsychometricError::StandardisedDiscreteDriftIsNotStandardisedDiscreteTimeDependentPredictorEffect
.to_string(),
"standardised discrete drift is not standardised discrete time-dependent predictor effect"
);
assert_eq!(
PsychometricError::InterceptStyleStandardisedTimeDependentEffectIsNotStandardisedDiscreteTimeDependentPredictorEffect
.to_string(),
"intercept-style standardised time-dependent effect is not standardised discrete time-dependent predictor effect"
);
assert_eq!(
PsychometricError::TraitScaledDiscreteTimeDependentPredictorEffectIsNotStandardisedDiscreteTimeDependentPredictorEffect
.to_string(),
"trait-scaled discrete time-dependent predictor effect is not standardised discrete time-dependent predictor effect"
);
}
}
Loading
Loading