Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
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.

1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,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 `asymCINTstd`; Eq. 3, p. 4; footnote 4; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-24T09:05Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104) scalar standardised asymptotic continuous intercept. Page 16 prints standardised matrices with the suffix `std` when appropriate. Footnote 4 standardises using only the relevant variance, not the total. `CINT` is the process intercept of individual, or average individual, dynamics, so that relevant variance is within-subject `asymDIFFUSION` `p = −q / (2 a)`. The 2017-era `summary.ctsemFit.R` forms `asymCINT` whenever `verbose = TRUE`, as `-solve(DRIFT) %*% CINT`. That source does not form an `asymCINTstd` matrix; the scalar map here is the footnote 4 standardisation of that named asymptotic intercept: `(-κ / a) / √p`. Form strictly positive `p` first, then the asymptotic intercept, then divide by `√p`. A zero intercept is exactly zero. Unstandardised `asymCINT` is defined for a zero process; standardised `asymCINT` is not. Zero `q` has no positive process SD and fails closed. Lasting `asymDIFFUSION` requires stable `a < 0`. A non-event clock 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. Still not a Kalman filter, not a matrix `expm`, not ESEM estimation, not DSEM, not `CINTstd`, not `discreteCINTstd`, and not ctsem estimation. Meredith (1993) remains unread (Unpaywall 2026-08-24T08:02Z: `is_oa: false`, 0 locations; title *Measurement Invariance, Factor Analysis and Factorial Invariance*). Mislevy (1991, *Psychometrika, 56*, 177–196) remains unread (Unpaywall 2026-08-24T08:02Z: `is_oa: false`, 0 locations; title *Randomization-Based Inference about Latent Variables from Complex Samples*).
- `psychometric_core` recovers the Driver, Oud, and Voelkle (2017, p. 16 `discreteCINTstd`; Eq. 3, p. 4; footnote 4; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-24T05:20Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104) scalar standardised discrete continuous intercept. 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. `CINT` is the process intercept of individual, or average individual, dynamics, so that relevant variance is within-subject `asymDIFFUSION` `p = −q / (2 a)`. The 2017-era `summary.ctsemFit.R` forms `discreteCINT` whenever `verbose = TRUE`, as `solve(DRIFT) %*% (discreteDRIFT − I) %*% CINT`. That source does not form a `discreteCINTstd` matrix; the scalar map here is the footnote 4 standardisation of that named discrete intercept: `A^{-1}[e^{A Δt} − I] κ / √p`. Form strictly positive `p` first, then the discrete intercept, then divide by `√p`. A zero intercept is exactly zero. Unstandardised `discreteCINT` is defined for growing `a ≥ 0` and for zero diffusion; standardised `discreteCINT` is not. Zero `q` has no positive process SD and fails closed. Lasting `asymDIFFUSION` requires stable `a < 0`. A non-event clock fails closed. A non-positive event interval 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. Still not a Kalman filter, not a matrix `expm`, not ESEM estimation, not DSEM, not `CINTstd`, and not ctsem estimation. Meredith (1993) remains unread (Unpaywall 2026-08-24T08:02Z: `is_oa: false`, 0 locations; title *Measurement Invariance, Factor Analysis and Factorial Invariance*). Mislevy (1991, *Psychometrika, 56*, 177–196) remains unread (Unpaywall 2026-08-24T08:02Z: `is_oa: false`, 0 locations; title *Randomization-Based Inference about Latent Variables from Complex Samples*).
- `psychometric_core` recovers the Driver, Oud, and Voelkle (2017, p. 16 `asymDIFFUSIONstd`; footnote 4; Eq. 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T23:02Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104) scalar standardised asymptotic within-subject variance. Page 16 names `asymDIFFUSION` the total within-subject variance as `Δt → ∞` and prints standardised matrices with the suffix `std` when appropriate. Footnote 4 standardises using only the relevant variance, not the total. The 2017-era `summary.ctsemFit.R` forms `asymDIFFUSIONstd` whenever `verbose = TRUE`, as `solve(sqrt(diag(asymDIFFUSION) + ridging)) %&% asymDIFFUSION`. OpenMx `%&%` is the quadratic form `t(A) %*% B %*% A`. That formation adds `diag(c(ridging), n.latent)`. The default `ridging = FALSE` adds 0, not `0.0001`; that ridge is a numerical hack and is not this exact map. The 2017-era source assigns `dimnames(asymDIFFUSIONstd)` to `latentNames`; that assignment matches the `n.latent × n.latent` matrix and is this map. The scalar correlation is `p / p = 1` after strictly positive Lyapunov `p = −q / (2 a)`. Form strictly positive `p` first, then `1 / √p`, then `(1 / √p) p (1 / √p)`. Unstandardised `asymDIFFUSION` is defined for a zero process; standardised `asymDIFFUSION` is not. Zero `q` makes `solve(sqrt(0))` fail in the 2017-era source and fails closed here. That source does not skip forming `asymDIFFUSIONstd` when `p = 0`. Within-subject variance is an event-time structural quantity, so a non-event clock fails closed. Lasting `asymDIFFUSION` requires stable `a < 0`. 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. Still not a Kalman filter, not a matrix `expm`, not ESEM estimation, not DSEM, and not ctsem estimation. Meredith (1993) remains unread (Unpaywall 2026-08-24T08:02Z: `is_oa: false`, 0 locations; title *Measurement Invariance, Factor Analysis and Factorial Invariance*). Mislevy (1991, *Psychometrika, 56*, 177–196) remains unread (Unpaywall 2026-08-24T08:02Z: `is_oa: false`, 0 locations; title *Randomization-Based Inference about Latent Variables from Complex Samples*).
- `psychometric_core` recovers the Driver, Oud, and Voelkle (2017, Table 2, p. 12 `TIPREDVAR`; p. 16 `TIPREDVARstd`; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-23T22:53Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104) scalar standardised time-independent predictor variance. Table 2 names `TIPREDVAR` the variance/covariance of time-independent predictors. Page 16 prints standardised matrices with the suffix `std` when appropriate. The 2017-era `summary.ctsemFit.R` forms `TIPREDVARstd` whenever `verbose = TRUE` and `n.TIpred > 0`, as `solve(sqrt(diag(TIPREDVAR) + ridging)) %&% TIPREDVAR`. OpenMx `%&%` is the quadratic form `t(A) %*% B %*% A`. Unlike `TRAITVARstd`, that formation adds `diag(c(ridging), n.TIpred)`. The default `ridging = FALSE` adds 0, not `0.0001`; that ridge is a numerical hack and is not this exact map. The 2017-era source assigns `dimnames(TIPREDVARstd)` to `TIpredNames`; that assignment matches the `n.TIpred × n.TIpred` matrix and is this map. The scalar correlation is `v / v = 1` after strictly positive `TIPREDVAR`. Form strictly positive `v` first, then `1 / √v`, then `(1 / √v) v (1 / √v)`. Unstandardised `TIPREDVAR` is defined for a zero predictor; standardised `TIPREDVAR` is not. Zero `v` makes `solve(sqrt(0))` fail in the 2017-era source and fails closed here. Unlike `TRAITVAR` / `MANIFESTTRAITVAR`, that source does not skip forming `TIPREDVARstd` when `v = 0`. Predictor variance is an event-time structural quantity, so a non-event clock fails closed. `TIPREDVAR` does not require stable `a < 0`. 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. Still not a Kalman filter, not a matrix `expm`, not ESEM estimation, not DSEM, and not ctsem estimation. Meredith (1993) remains unread (Unpaywall 2026-08-23T22:21Z: `is_oa: false`; title *Measurement Invariance, Factor Analysis and Factorial Invariance*). Mislevy (1991, *Psychometrika, 56*, 177–196) remains unread (Unpaywall 2026-08-23T22:21Z: `is_oa: false`; title *Randomization-Based Inference about Latent Variables from Complex Samples*).
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md

Large diffs are not rendered by default.

54 changes: 54 additions & 0 deletions crates/psychometric_core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1061,6 +1061,24 @@ pub enum PsychometricError {
/// `discreteCINTstd`. `(-κ / a) / √p` is the standardised
/// total intercept change, not the finite-interval map.
AsymptoticStandardisedContinuousInterceptIsNotStandardisedDiscreteContinuousIntercept,
/// Driver p. 16 `asymCINTstd` was requested with a
/// non-positive within-subject variance. Footnote 4
/// standardisation of the 2017-era `asymCINT` vector
/// requires strictly positive `asymDIFFUSION`.
StandardisedAsymptoticContinuousInterceptRequiresPositiveWithinSubjectVariance,
/// Driver Table 2 unstandardised `asymCINT` `-κ / a` was
/// treated as `asymCINTstd`. Unstandardised asymptotic
/// intercept is defined for a zero process; standardised
/// `asymCINT` is not.
UnstandardisedAsymptoticContinuousInterceptIsNotStandardisedAsymptoticContinuousIntercept,
/// Driver p. 16 `CINTstd` analog `κ / √p` was treated as
/// `asymCINTstd`. The continuous intercept standardisation
/// is not the standardised total intercept change.
StandardisedContinuousInterceptIsNotStandardisedAsymptoticContinuousIntercept,
/// Driver p. 16 `discreteCINTstd` was treated as
/// `asymCINTstd`. A finite event interval is not the
/// `Δt → ∞` intercept change.
StandardisedDiscreteContinuousInterceptIsNotStandardisedAsymptoticContinuousIntercept,
}

impl fmt::Display for PsychometricError {
Expand Down Expand Up @@ -1832,6 +1850,18 @@ impl fmt::Display for PsychometricError {
Self::AsymptoticStandardisedContinuousInterceptIsNotStandardisedDiscreteContinuousIntercept => {
"asymptotic standardised continuous intercept is not standardised discrete continuous intercept"
}
Self::StandardisedAsymptoticContinuousInterceptRequiresPositiveWithinSubjectVariance => {
"standardised asymptotic continuous intercept requires strictly positive within-subject variance"
}
Self::UnstandardisedAsymptoticContinuousInterceptIsNotStandardisedAsymptoticContinuousIntercept => {
"unstandardised asymptotic continuous intercept is not standardised asymptotic continuous intercept"
}
Self::StandardisedContinuousInterceptIsNotStandardisedAsymptoticContinuousIntercept => {
"standardised continuous intercept is not standardised asymptotic continuous intercept"
}
Self::StandardisedDiscreteContinuousInterceptIsNotStandardisedAsymptoticContinuousIntercept => {
"standardised discrete continuous intercept is not standardised asymptotic continuous intercept"
}
};
formatter.write_str(message)
}
Expand Down Expand Up @@ -3163,4 +3193,28 @@ mod tests {
"asymptotic standardised continuous intercept is not standardised discrete continuous intercept"
);
}

#[test]
fn standardised_asymptotic_continuous_intercept_boundary_messages_are_stable() {
assert_eq!(
PsychometricError::StandardisedAsymptoticContinuousInterceptRequiresPositiveWithinSubjectVariance
.to_string(),
"standardised asymptotic continuous intercept requires strictly positive within-subject variance"
);
assert_eq!(
PsychometricError::UnstandardisedAsymptoticContinuousInterceptIsNotStandardisedAsymptoticContinuousIntercept
.to_string(),
"unstandardised asymptotic continuous intercept is not standardised asymptotic continuous intercept"
);
assert_eq!(
PsychometricError::StandardisedContinuousInterceptIsNotStandardisedAsymptoticContinuousIntercept
.to_string(),
"standardised continuous intercept is not standardised asymptotic continuous intercept"
);
assert_eq!(
PsychometricError::StandardisedDiscreteContinuousInterceptIsNotStandardisedAsymptoticContinuousIntercept
.to_string(),
"standardised discrete continuous intercept is not standardised asymptotic continuous intercept"
);
}
}
Loading