Skip to content
Merged
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang

## [Unreleased]

<<<<<<< HEAD
- `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.
=======
- `psychometric_core` recovers the Driver, Oud, and Voelkle (2017, p. 16 `asymDIFFUSIONstd`; footnote 4; Eq. 4, p. 5; Table 2, p. 12; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-26T17:20Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104) scalar standardised asymptotic within-subject variance on current main after `0ce16e8` dropped the pre-consolidation code while research notes already named the map (register items 89–90). Page 16 prints standardised matrices with the suffix `std` when appropriate, and names `asymDIFFUSION` the total within-subject variance as `Δt → ∞`. The printed example on p. 16 is `discreteDRIFTstd`, not `asymDIFFUSIONstd`. Footnote 4 standardises using only the relevant variance, not the total. The relevant variance for that named process-dynamics correlation is within-subject `asymDIFFUSION` `p = −q / (2 a)`, not free first-occasion `T0VAR`. The 2017-era source forms `asymDIFFUSIONstd` as `solve(sqrt(diag(asymDIFFUSION))) %&% asymDIFFUSION` when `verbose = TRUE`. OpenMx `%&%` is `t(A) %*% B %*% A`. The default `ridging = FALSE` adds 0, not `0.0001`; that ridge is a numerical hack and is not this exact map. The scalar correlation is `p / p = 1` after strictly positive `p`. Form strictly positive `p` first, then `1 / √p`, then `(1 / √p) p (1 / √p)`. Unstandardised `p` is defined for a zero process; standardised `asymDIFFUSION` is not. Zero `q` has no positive SD and fails closed. Lasting `p` requires stable `a < 0`. A non-event clock fails closed. Distinct positive `p` recover the same 1. `p_0 / p_0 = 1` is `T0VARstd` and recovers the same number and remains a distinct named quantity. `q / p = −2 a` is `DIFFUSIONstd` and is not this correlation. `v / v = 1` is `TIPREDVARstd` and recovers the same number and remains a distinct named quantity. 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.
>>>>>>> origin/main
Comment on lines +39 to +43

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Unresolved merge conflict markers corrupt the changelog

The Unreleased section contains raw <<<<<<< HEAD / ======= / >>>>>>> origin/main markers (lines 39-43), leaving two competing entries and conflict scaffolding in the published changelog.

Prompt for agents
Unresolved Git merge conflict markers were committed in CHANGELOG.md at lines 39-43. Remove the conflict markers and keep both intended Unreleased entries (the HEAD TRAITVARstd entry and the origin/main asymDIFFUSIONstd entry) as separate bullet lines.
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

- `psychometric_core` recovers the Driver, Oud, and Voelkle (2017, p. 16 `T0VARstd`; Table 2, p. 12; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-26T07:17Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104) scalar standardised initial latent variance on current main after `0ce16e8` dropped the pre-consolidation code while research notes already named the map (register items 79–80). Page 16 prints standardised matrices with the suffix `std` when appropriate. The printed example on p. 16 is `discreteDRIFTstd`, not `T0VARstd`. Footnote 4 standardises using only the relevant variance, not the total. Table 2 names `T0VAR` the latent process initial variance/covariance. The first-occasion relevant variance is free `T0VAR` `p_0`, not process-dynamics `asymDIFFUSION` `-q / (2 a)`. The 2017-era source forms `T0VARstd` as `solve(sqrt(diag(T0VAR))) %&% T0VAR` when `verbose = TRUE`. OpenMx `%&%` is `t(A) %*% B %*% A`. The default `ridging = FALSE` adds 0, not `0.0001`; that ridge is a numerical hack and is not this exact map. The scalar correlation is `p_0 / p_0 = 1` after strictly positive `p_0`. Form strictly positive `p_0` first, then `1 / √p_0`, then `(1 / √p_0) p_0 (1 / √p_0)`. A zero first-occasion variance has no positive SD and fails closed. `T0` is an event-time occasion, so a non-event clock fails closed. Free `T0VAR` does not require stable `a < 0`. Distinct positive `p_0` recover the same 1. `μ_0 / √p_0` is `T0MEANSstd` and recovers the same number when `μ_0 = √p_0` and remains a distinct named quantity. `p / p = 1` is `asymDIFFUSIONstd` and recovers the same number and remains a distinct named quantity. Meredith (1993) remains unread (Unpaywall 2026-08-26T07:17Z: `is_oa: false`; OpenAlex closed; Springer `content/pdf` is a 3038-byte HTML stub). Mislevy (1991, *Psychometrika, 56*, 177–196) remains unread on the same terms (DOI `10.1007/bf02294457`; Unpaywall `is_oa: false`). Still not a Kalman filter, not a matrix `expm`, not ESEM estimation, not DSEM, and not ctsem estimation.
- `psychometric_core` recovers the Driver, Oud, and Voelkle (2017, p. 16 `asymCINTstd`; Eq. 3, p. 4; Table 2, p. 12; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-26T00:20Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104) scalar standardised asymptotic continuous intercept on current main after consolidation dropped the pre-consolidation `(-κ / a) / √p` slice. Page 16 prints standardised matrices with the suffix `std` when appropriate, and asymptotic values as `Δt → ∞`. Footnote 4 standardises using only the relevant variance, not the total. Table 2 names `κ` `CINT`. The relevant variance for that process intercept is within-subject `asymDIFFUSION` `p = −q / (2 a)`. The 2017-era source forms unstandardised `asymCINT` whenever `verbose = TRUE` as `-solve(DRIFT) %*% CINT` and does not form an `asymCINTstd` matrix. Form strictly positive `p` first, then the asymptotic intercept, then divide by `√p`. A zero intercept is exactly zero after that positive SD. Zero `q` has no positive process SD and fails closed. Lasting `p` requires stable `a < 0`. A non-event clock fails closed. `κ / √p` is `CINTstd` and is not this total-change map. `A^{-1}[e^{A Δt} − I] κ / √p` is `discreteCINTstd` and depends on the event interval. Meredith (1993) remains unread (Unpaywall 2026-08-25T18:22Z: `is_oa: false`; Springer `content/pdf` is a 3038-byte HTML stub). Mislevy (1991) remains unread on the same terms. Still not a Kalman filter, not a matrix `expm`, not ESEM estimation, not DSEM, and not ctsem estimation.
- `psychometric_core` recovers the Driver, Oud, and Voelkle (2017, p. 16 `T0MEANSstd`; Table 2, p. 12; footnote 4; 2017-era ctsem `summary.ctsemFit.R`; JSS PDF re-opened 2026-08-26T04:09Z from https://www.jstatsoft.org/index.php/jss/article/download/v077i05/1104) scalar standardised initial latent mean on current main after `0ce16e8` dropped the pre-consolidation code while research notes already named the map. Page 16 prints standardised matrices with the suffix `std` when appropriate. Footnote 4 standardises using only the relevant variance, not the total. Table 2 names `T0MEANS` the `n.latent × 1` matrix of latent process means at the first time point `T0` and names `T0VAR` the latent process initial variance/covariance. The first-occasion relevant variance is free `T0VAR` `p_0`, not process-dynamics `asymDIFFUSION` `-q / (2 a)`. The 2017-era source forms unstandardised `T0MEANS` and does not form a `T0MEANSstd` matrix; the scalar map is `μ_0 / √p_0` after strictly positive `p_0`. A zero mean is exactly zero. Zero `p_0` has no positive SD and fails closed. `T0` is an event-time occasion, so a non-event clock fails closed. Free `T0MEANS` does not require stable `a < 0`. `p_0 / p_0 = 1` recovers the same number when `μ_0 = √p_0` and remains a distinct named quantity. `μ_0 / √asymDIFFUSION` uses process-dynamics variance and is not this first-occasion map. Meredith (1993) remains unread (Unpaywall 2026-08-26T00:22Z: `is_oa: false`; OpenAlex closed; Springer `content/pdf` is a 3038-byte HTML stub). Mislevy (1991, *Psychometrika, 56*, 177–196) remains unread on the same terms. Still not a Kalman filter, not a matrix `expm`, not ESEM estimation, not DSEM, and not ctsem estimation.
Expand Down
60 changes: 60 additions & 0 deletions crates/psychometric_core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,27 @@ pub enum PsychometricError {
/// process-dynamics `asymDIFFUSION`; `TIPREDVARstd` is the
/// correlation form of `TIPREDVAR`.
StandardisedTimeIndependentPredictorVarianceIsNotStandardisedAsymptoticDiffusion,

/// Driver p. 16 `TRAITVARstd` was requested with a non-positive
/// trait variance. The 2017-era source skips forming
/// `TRAITVARstd` when `TRAITVAR == 0`; footnote 4
/// standardisation requires strictly positive `TRAITVAR`.
StandardisedTraitVarianceRequiresPositiveTraitVariance,
/// Driver Table 2 unstandardised `TRAITVAR` was treated as
/// p. 16 `TRAITVARstd`. Unstandardised trait variance is
/// defined for a zero trait; standardised `TRAITVAR` is not.
UnstandardisedTraitVarianceIsNotStandardisedTraitVariance,
/// Driver p. 16 `T0VARstd` was treated as p. 16 `TRAITVARstd`.
/// Equal numbers when both correlations equal 1 are still
/// distinct named quantities. `TRAITVARstd` is the correlation
/// form of between-subject `TRAITVAR`; `T0VARstd` is the
/// correlation form of free first-occasion `T0VAR`.
StandardisedInitialLatentVarianceIsNotStandardisedTraitVariance,
/// Driver 2017-era `addedT0TIPREDVAR` `t0_b² v` was treated as
/// p. 16 `TRAITVARstd`. Extra first-occasion TI variance is not
/// the correlation form of between-subject `TRAITVAR`.
InitialTimeIndependentVarianceIsNotStandardisedTraitVariance,

/// Driver p. 16 `discreteCINTstd` was requested without a strictly
/// positive `asymDIFFUSION`. Footnote 4 standardises using only the
/// relevant variance; zero `q` has no positive process SD.
Expand Down Expand Up @@ -1105,6 +1126,21 @@ impl fmt::Display for PsychometricError {
Self::StandardisedTimeIndependentPredictorVarianceIsNotStandardisedAsymptoticDiffusion => {
"standardised time-independent predictor variance is not standardised asymptotic diffusion"
}
Self::StandardisedTraitVarianceRequiresPositiveTraitVariance => {
"standardised trait variance requires strictly positive trait variance"
}
Self::UnstandardisedTraitVarianceIsNotStandardisedTraitVariance => {
"unstandardised trait variance is not standardised trait variance"
}
Self::StandardisedInitialLatentVarianceIsNotStandardisedTraitVariance => {
"standardised initial latent variance is not standardised trait variance"
}
Self::InitialTimeIndependentVarianceIsNotStandardisedTraitVariance => {
"initial time-independent predictor variance is not standardised trait variance"
}



Self::StandardisedDiscreteContinuousInterceptRequiresPositiveStationaryVariance => {
"standardised discrete continuous intercept requires strictly positive stationary within-subject variance"
}
Expand Down Expand Up @@ -1874,6 +1910,30 @@ mod tests {
}

#[test]
fn standardised_trait_variance_boundary_messages_are_stable() {
assert_eq!(
PsychometricError::StandardisedTraitVarianceRequiresPositiveTraitVariance.to_string(),
"standardised trait variance requires strictly positive trait variance"
);
assert_eq!(
PsychometricError::UnstandardisedTraitVarianceIsNotStandardisedTraitVariance
.to_string(),
"unstandardised trait variance is not standardised trait variance"
);
assert_eq!(
PsychometricError::StandardisedInitialLatentVarianceIsNotStandardisedTraitVariance
.to_string(),
"standardised initial latent variance is not standardised trait variance"
);
assert_eq!(
PsychometricError::InitialTimeIndependentVarianceIsNotStandardisedTraitVariance
.to_string(),
"initial time-independent predictor variance is not standardised trait variance"
);
}

#[test]

fn standardised_discrete_continuous_intercept_boundary_messages_are_stable() {
assert_eq!(
PsychometricError::StandardisedDiscreteContinuousInterceptRequiresPositiveStationaryVariance
Expand Down
Loading
Loading