truth_info identifies the
generating parameterization and time origin; truth_lcsm
supplies first-occasion targets for quadratic and nonparametric
change-score models. Shifted-time growth-curve intercepts must not be
compared directly with change-score initial levels. Nonlinear
approximation limits remain explicit.getDiagnostics() checks convergence,
covariance admissibility, uncertainty information and mixture component
sizes; new fits warn about detected problems while still returning the
fitted object.res_scale values take precedence
over automatic residual variance starts. The default NULL
calculation, per-person NLS and SEM optimizer are unchanged.ID and handles occasions with entirely missing outcomes
when selecting the observed curve.getSimData() simulates wide-format longitudinal
data in which each person’s repeated measures follow a latent growth
curve (linear, quadratic, negative exponential, Jenss-Bayley, bilinear
spline or nonparametric), with fixed or random growth ratios and knots,
individually varying measurement occasions, growth TICs, and several
latent classes with fixed proportions or cluster-TIC multinomial logits.
The same data can be fitted with a latent growth curve model or a latent
change score model, which use the same names for the true values. The
true parameter values are attached under the names of the fit’s
Estimates, and each person’s growth factors are attached
too, so a fit can be compared with the values that generated the data.
It also generates multivariate growth models (two or more outcomes, with
growth factors and residuals correlated across outcomes), models with a
time-varying covariate (a direct effect, or decompositions 1, 2 or 3, in
which the outcome responds to the TVC’s slopes, interval changes or
change from baseline and the TVC’s baseline predicts the outcome’s
growth factors), and longitudinal mediation models (linear or bilinear
spline, with a baseline or longitudinal predictor). A new section of the
workflow vignette generates data, fits the matching model and compares
the estimates with the truth.getSimData() reads named parameters by name: a mean
vector in another order (such as c(eta1 = 2, eta0 = 20) for
the means of a decomposed TVC) previously generated a different model
while reporting the same true values. Values must be finite, covariance
matrices symmetric and positive definite, and paths of the exact length
(a single baseline mediation path is no longer copied to every growth
factor). Simulated processes are named only when the data are assembled,
so prefixes such as y_var = "M", "TVC" or
"TIC" no longer drop or overwrite columns, and prefixes or
covariate names that would duplicate a column are rejected.inst/extdata were regenerated
with this version (REGENERATED.txt lists how each compares
with the 1.0.0.9000 objects). Fourteen reproduce their likelihood, seven
reach a better optimum (Math_TVCchgBL_BLS_r is now
admissible, and the bilinear spline example again gives the knot and
path quoted in the LGCM guide), and two reach a poorer one
(Math_TVC_BLS_f, Math_BLS_LGCM3).
getDiagnostics() flags four stored solutions with slightly
indefinite class or group covariance matrices; the guides note
them.getDiagnostics(): a nonzero optimizer status, an
inadmissible covariance matrix, a Hessian that is not positive definite,
incomplete standard errors or an empty mixture class. The fit is still
returned; the warning (class "nlpsem_diagnostics_warning")
lists the issues, so an invalid solution is no longer reported
silently.y_var = c("A", "B", "AB") the covariance of outcomes A and
B and the variance of outcome AB had the same label, and with
c("A", "BC", "AB", "C") two pairs shared one; the builder
failed or forced the two parameters equal, standardized fits scaled one
pair with the other’s factors, and getSimData() reported
the wrong true values. getMGM(), getMGroup(),
getMIX() and getSimData() now stop before
fitting or simulating, naming the conflict; latent change score models
also reject prefixes that differ only in letter case. Prefixes that were
unambiguous keep their labels and results.c<k> from every label,
also outside multiple-group and mixture models, so with
y_var = c("A", "c1A") the parameters of c1A
were converted with the centre and scale of A: the fit
returned wrong estimates with a zero optimizer status, or stopped when
the two outcomes’ scales differed widely. Only the prefix of a
parameter’s own class is removed now.MY_residuals with Y_residuals (and
XYZ_RES with YZ_RES), so
paramOut = TRUE failed after fitting.getDiagnostics() records as unavailable rather than as an
issue.getSimData(), getLCSM() and
getTVCmodel() describes two approximations measured on
generated data: the first-order Taylor expansion of models with a random
growth ratio or knot (about 1% or less on the growth factor means, but a
TVC effect 5% to 10% too high with a negative exponential curve and
decompose = 1 or 3), and the midpoint rate of
latent change score models with a negative exponential or Jenss-Bayley
form (means up to about 2.5% too large in magnitude with ten yearly
occasions).getLGCM(), getLCSM(),
getTVCmodel(), getMGM(),
getMGroup() and getMIX(). Each outcome is
centred and scaled at its first recorded occasion, a TVC is scaled, and
time is divided by the mean of its last recorded occasion. The model
starts from the raw-scale starting values, mapped exactly, and the
optimizer and retry settings are unchanged.getSpecification() records the
centres, scales and condition numbers under
fitting$standardization.
options(nlpsem.standardize = FALSE) fits on the raw
scale.tests/testthat/golden/fit_golden.rds) were regenerated
with 1.0.0.9006 on macOS. The QUAD and EXP references are unchanged. The
four JB references fit the nearly linear math scores and stop with
status 6; they are excluded explicitly, and three bilinear spline
references that did not converge before now do. The random-ratio EXP
fits reach the 1.0.0.9000 reference likelihoods that the 9005 raw-scale
fits missed. JB fits improve but stop with status 6: their random slope
and acceleration terms are nearly collinear on this sample.getSpecification() records the
scale basis.growth_TIC = "T1")
is read from an unscaled copy in the standardized fit. It was rescaled
with the time column before, which made the converted solution and
likelihood wrong.getDiagnostics() before comparing numbers. Five references
reach status 0 with inadmissible solutions on the 200-person subsample
(four two-class mixtures and the multiple-group bilinear spline model);
they are kept as fixtures for getDiagnostics() rather than
as model coverage, which leaves 33 positive references. Two fixed-ratio
JB references fitted to the reading scores of all 500 children were
added.TVC_LGCM_BLS_r_d1
(-2LL 16093.60 to 16090.76) and MGM_LGCM_BLS_r (25005.40 to
24975.52), both passing getDiagnostics(). The other
references are unchanged.records are not in ascending order
no longer fail. Plots of TVC models label their curves as the growth
component, which excludes the effect of the TVC, and add the full
model-implied mean of the outcome (or its change from baseline) at each
wave, for each class or group in mixture and multiple-group plots. The
TVC vignette explains that the growth components of a model with a
direct TVC and of one with interval-specific slopes describe different
quantities, instead of reading their difference as underestimation of
the growth factor means.stats::nls() for EXP, JB and BLS trajectories, following
the 0.3 formulas. The shared initializer supplies the same method across
the seven model families.getDiagnostics() inspects stored covariance
admissibility, Hessian and standard-error information, and mixture
component sizes. It does not refit, impose constraints, or establish
global optimality or repeated-start stability.getSpecification() exposes the
metadata without duplicating input data or retaining the calling
environment. Manifest-group metadata preserve original group labels and
retained/dropped row indices.print(), summary(),
coef(), logLik(), and plot()
methods. Coefficients retain full precision; likelihood attributes
support standard AIC() and BIC() calculations.
Automatic plotting uses saved specifications and permits display changes
without silently changing the fitted structure.NULL from
getSpecification() and require explicit structural plotting
arguments. The concise S4 display uses the new print method.bootstrap_diagnostics attribute. Fewer than two usable
replications fail informatively. Confidence levels and integer
replication counts are validated before estimation.getLatentKappa() matches categories by label values
instead of factor positions. Different observed category sets use their
union; class-label alignment remains an explicit user responsibility.
Invalid confidence levels are rejected.data-raw/.getLRT()
documentation example..build_LGCM(),
.build_LCSM(), .build_TVC(),
.build_MGM() and .build_MED() build one
mxModel per prefix: the single-group model with an empty
prefix, the class-specific models of a multiple-group or mixture model
with c1, c2, ….
get<Model>.mxModel() and
getsub.<Model>(container =) are thin wrappers; the
container contributes only its data rows (multiple-group) or the
row-wise fit function (mixture). The loading, derived-parameter and
TVC-component helpers are parameterised the same way, and the
multiple-group and mixture output helpers share
.class_output(). The ten
MGroup.SUBMODEL.*/MIX.SUBMODEL.* builders, six
MIX_* helpers and the second output helper are gone:
R/ shrinks from 27,525 to 14,692 lines. A structural
snapshot of 688 model specifications (every sub-model, curve form,
intrinsic option, TIC and decomposition setting, single-group,
multiple-group and mixture with and without cluster TICs) against the
0.4.1 builders shows identical matrices, free parameters and algebras
apart from the items listed below.getMGroup()/getMIX() with
sub_Model = "TVC", y_model = "LCSM",
curveFun = "EXP", intrinsic = TRUE,
decompose != 0 and no growth TICs could not be fitted
(“Unknown reference ‘c1Y_alpha0’”): the class-specific builder branch
was a copy of the wrong case. The unified builder uses the single-group
model for this case.
The mixture weight rows of getMIX() tables
(p2, …) now carry the standard errors of the softmax
weights; 0.4 repeated the estimates in the SE column.
getLRT() returns numeric p-values; 0.4 replaced
values below 0.0001 by the string "<0.0001", which
turned the column into text.
getFigure() for multiple-group Jenss-Bayley
change-score models used the lower-bound acceleration terms in the upper
bound of the growth-rate interval and vice versa.
Data-driven residual-correlation starts for multivariate and
decomposed-TVC models are now computed from the per-person curve
residuals (getUNI.GF() attaches them as the
"residuals" attribute) instead of person-mean-centred
scores. The 0.4 heuristic left the growth trend in the deviations, so
two processes that grew together started from a residual correlation
above 0.9 (the fitted value on the example data is 0.24); with that
start getMIX() could not recover the three-class bivariate
example. Mediation models keep the builders’ default start of 0.3 (an
unused copy of the heuristic was removed from the mediation start
helper).
getMIX() with sub_Model = "MGM" now
damps the class-specific fixed-knot bilinear-spline covariance starts as
getMGroup() already did (each provisional class is a
smaller sample whose data-driven transformed covariance is too large to
start from). With both start changes the three-class bivariate example
converges at the first optimisation attempt.
getMGroup() and getMIX() with
sub_Model = "MED" and a baseline predictor
(x_type = "baseline") could not be fitted unless the
predictor column was literally named X: the class-specific
mediation builders hard-coded "X" in the paths for the
predictor’s mean, variance and effects. They now use x_var,
as the single-group builder always did. Found by the new fitting-layer
tests.
Y_knot (was knot) and the reparameterised TIC
coefficients beta01, … (was beta01s, …),
matching the multiple-group and mixture models. This affects
omxGetParameters() names and mxCI() labels
only; estimate tables are unchanged.eta1Y,
etarY, eta2Y (the single-group convention) in
all containers; the class-specific models used etaYr,
etaY2.c<k>X_abs_rate<j> (was
c<k>abs_rate<j>), as in the single-group
models.decompose != 0, and the two
multivariate negative-exponential change-score models state the ratio
type.The fitted example objects in inst/extdata were
regenerated with the current code (R 4.5.3, OpenMx 2.22.11) by the
scripts now kept in data-raw/, which follow the vignette
calls. Twenty of the twenty-three fits reproduce the previous
log-likelihoods exactly. Math_TVCslp_BLS_f now reaches a
lower -2LL (42269.2 instead of 42457.3), while RM_PLGCM.f
(62609.2 instead of 62589.5) and RM_BLS_PLGCM3 (61903.6
instead of 61865.3, and now labelled with the process names R/M instead
of the pre-0.4 Y/Z) converge to nearby local optima of these multimodal
likelihoods from the new start values;
inst/extdata/REGENERATED.txt records the comparison. As
with any random-knot or mixture model, compare several start sets
(prop_starts, res_cor, starts)
before interpreting a single fit.
The scripts that regenerate the fitted example objects in
inst/extdata, the derivation of RMS_dat from
the public-use ECLS-K:2011 files, and the generator of the fitting-layer
reference values now live in data-raw/ (excluded from the
built package).
Added fitting-layer regression tests
(tests/testthat/test-fitting_golden.R): every sub-model and
functional form is fitted on a subsample and compared with stored
reference values. They are skipped on CRAN and take several minutes
locally.
Examples run in a few minutes instead of 37: post-processing examples load the shipped fits, the fitting examples keep one fast model each, and the long intrinsically nonlinear and mixture fits are shown but not run.
getEstimateStats(): An unrecognized
CI_type now stops with an informative error listing the
supported options ("Wald", "likelihood",
"bootstrap", "all") instead of failing with
object 'statOut' not found.getEstimateStats(): Requesting bootstrap CIs
(CI_type = "bootstrap" or "all") without
supplying a positive integer rep now stops with an
informative error instead of passing NA to
mxBootstrap().getEstimateStats(): conf.level is now
honoured for likelihood-based and bootstrap confidence intervals. Both
were fixed at 95% regardless of the argument (mxCI() was
called without interval, and the bootstrap quantiles were
hard-coded at 2.5% and 97.5%).getFigure() now works for longitudinal mediation models
(sub_Model = "MED"), with both baseline and longitudinal
predictors, in single-group, multiple-group, and mixture fits. The
plotting helper looked up growth-factor means under the user’s variable
prefix with a _mean0 suffix, whereas the mediation builders
name them by role (Y_mean, M_mean,
X_mean) and store bilinear spline growth factors as (slope
1, value at knot, slope 2) with the knot in
<role>_mug. Every release up to 0.4 therefore failed
with object '<prefix>_mean0' not found; the 0.4
release note on getFigure() for mediation models described
only the trajectory-loop fix.getIndFS(): FS_type = "ML" and
FS_type = "WeightedML" now work.
mxFactorScores() failed whenever the data stored in the
fitted model contained columns the model does not use (covariates,
identifiers), and the descriptive model names used by this package are
not valid identifiers for the scoring model. The data are now reduced to
the model’s manifest and definition variables and the model is renamed
before scoring; "Regression" scores are unchanged.getMIX() with paramOut = TRUE,
cluster_TIC, and three or more latent classes: the
class-membership coefficients in the estimates table were mislabelled.
Labels were listed class by class (beta20,
beta21, …, beta30, …) while the values were
read from the coefficient matrix column by column, so for example the
row named beta21 held the value of beta30.
Introduced in 0.4 with the generalised cluster-TIC labelling; two-class
models were unaffected. Values are now read row by row to match the
labels.getMGroup() /
getMIX() with sub_Model = "TVC",
curveFun = "BLS", intrinsic = TRUE): the
class-specific builders set the knot entry of the delta-method Jacobian
to 0 instead of mueta2s - mueta1s, so the outcome’s
growth-factor variances and covariances in the original parameter space
(and their standard errors and the plotted confidence bands) omitted the
knot-variance contribution. The single-group TVC builder and all LGCM
and MGM builders already had the term.getMGroup() accepts any coding of the grouping
variable. Downstream helpers index the groups 1..K, so
groups coded 0/1, as characters, or as factors failed with
subscript out of bounds. The distinct values are now sorted
and recoded to 1..K (a message reports the mapping) and
rows with a missing group value are dropped with a warning.getUNI.GF() crashed
(0 (non-NA) cases) for a person with fewer than two (three)
complete occasions; multivariate and mediation starts produced
NA means and covariances from such persons; and the k-means
step that forms provisional classes for getMIX() could not
run with incomplete growth factors. Such persons now contribute
NA growth factors that are skipped in the means and
covariances, and they are assigned to the nearest provisional
class.getSummary(..., HetModels = TRUE) returns a plain
data.frame, as documented and as
HetModels = FALSE already did, instead of a tibble.mxSE() are now silent.
Previously every getFigure() call and every fit with
paramOut = TRUE printed OpenMx’s “Treating first argument
…” message once per looked-up quantity (dozens of lines for
multiple-group and mixture models).inst/CITATION for Liu (2025), Behavior
Research Methods, 57(3), 87, doi:10.3758/s13428-025-02596-4.inst/extdata are stored
with xz compression (3.7 MB to 1.1 MB), which brings the installed
package size below 5 MB.nls/nls.control imports left
over from the pre-grid-search BLS starts implementation.getEstimateStats(), getSummary(),
getPosterior(), getLatentKappa()) using
synthetic inputs and the fitted example objects shipped in
inst/extdata.getFigure() across all seven sub-models (including both
mediation designs), the output helpers and automatic parameter names for
every shipped configuration, getIndFS() for all three score
types, getLRT(), getSummary(),
getPosterior() with cluster-forming covariates, and the
conf.level handling in
getEstimateStats().This release note reflects a direct comparison between the current
package and package_v0.3/nlpsem.
getPosterior() entropy
calculation when nClass = 1.getTVCmodel()
documentation for Jenss-Bayley curve function.memebership → membership in
getSummary().class column in
getPosterior() when label = TRUE.model NULL validation in
getEstimateStats() for likelihood, bootstrap, and combined
CIs.any() logic error in
validate_res_scale().[[]] list indexing on numeric vector in
getMULTI.initial().TVC.initial_helper.R.getFigure(): Mediation sub-model loop iterated
over y_var (always length 1) instead of
traj_var, and outcome was not indexed — only
the first trajectory was plotted.TVC.initial_helper.R: Hardcoded
t_var = "T" replaced with the actual t_var
function parameter when computing TVC initial values.MED.output_helper.R: Swapped y_var/m_var standard
error order in 3 output blocks (BLS baseline, LIN longitudinal, BLS
longitudinal) so SEs match their corresponding estimates.MED.mxModel_helper.R: Removed quoted strings from
mxAlgebra() calls in 4 places. mxAlgebra()
uses non-standard evaluation (bare names); quoted strings are for
mxAlgebraFromString().TVC.output_helper.R,
MIX.output_helper.R (2 places), and
MGroup.output_helper.R (2 places): Loop assumed consecutive
records starting at 1; replaced with seq_along() for
correct indexing with arbitrary record indices.getPosterior(): Protected entropy calculation
from log(0) using
pmax(prob, .Machine$double.xmin).getLatentKappa(): Added defensive guard for kappa
edge cases (kappa >= 1 or <= 0).getSummary(): Empty latent classes in mixture
models now handled correctly using
factor(membership, levels = 1:nClass) to ensure proportions
vector matches nClass.validate_res_scale(): List branch now uses
<= 0 / >= 1 to match non-list branch
(both enforce exclusive bounds as documented).MIX.initial_helper.R: TVC residual
(TVC_starts$residuals) now also inflated by 2× when
decompose != 0, matching the Y residual inflation for
mixture model convergence.MULTI.initial_helper.R: Residual covariance
matrix off-diagonal indexing used
res_cor[traj_i + traj_j - 1] which produced wrong indices
for 4+ longitudinal outcomes. Replaced with sequential counter to
correctly map pair (i,j) to res_cor vector position.TVC.initial_helper.R: Added
res_cor = NULL default to getTVC.initial()
signature. Callers in MIX.initial_helper.R and
MGroup.initial_helper.R omit res_cor when
decompose = 0 (where it is unused), but the missing default
could cause errors if the function is refactored.getMGM() / getMULTI.initial():
Multivariate models now preserve the full y_var prefixes
provided by the user instead of collapsing them to single-letter
uppercase prefixes.MGM.output_helper.R,
MGroup.output_helper.R, and
MIX.output_helper.R: between-process covariance output for
3+ outcomes/classes no longer appends cumulative estimate blocks or
corrupts paramOut tables.MGM.mxModel_helper.R,
MGroup.SUBMODEL.MGM_helper.R, and
MIX.SUBMODEL.MGM_helper.R: Fixed-parameter multivariate
covariance extraction now works for 3+ outcomes instead of assuming
exactly 2 longitudinal processes.MED.Initial_helper.R: In longitudinal-predictor
mediation models, the initial beta_x and
beta_m blocks are no longer swapped in the outcome
regression starts.TVC.initial_helper.R: decompose = 3
now computes kappa starts from the change-from-baseline
temporal state implied by the latent growth-factor starts, and the
nonparametric path now returns finite starts instead of falling through
to NA.MIX.output_helper.R: Cluster-TIC coefficient
names now scale to any number of predictors instead of hard-coding a
3-column naming pattern.I() wrappers from logical
conditions across 11 files.validation_helpers.R).getPosterior() using
sweep()/rowSums().starts[[1]][[2]])
to named access (starts$Y_starts$covMatrix) across all
helper files for maintainability.as.data.frame(dat) guard in all main functions
for tibble compatibility.validate_res_cor() helper and validation calls in
getTVCmodel(), getMGM(),
getMediation(), getMIX(), and
getMGroup().validate_curveFun_mediation()
function (logic already in validate_sub_Model()).getFigure() by removing
redundant is.null(nClass) / !is.null(nClass)
branching.else if conditions in
StandardMethods.R.getUNI.GF() now computes per-person mean squared residuals
from individual model fits and returns the median as an attribute. All
initial value helpers (UNI.initial_helper.R,
MULTI.initial_helper.R, TVC.initial_helper.R,
MED.Initial_helper.R) now use this data-driven estimate
instead of the var() * res_scale heuristic, with the
heuristic retained as a fallback. This provides more accurate starting
values, especially for nonlinear functional forms (EXP, JB, BLS) where
the heuristic could be orders of magnitude off.getEstimateStats() into local helpers
(.add_wald_CI(), .get_likelihood_CI(),
.get_bootstrap_CI()).&/| to
&&/|| in
validation_helpers.R (7 places) and
getFigure.R (3 places) for proper short-circuit
evaluation.MIX.initial_helper.R (duplicate
order_indices assignment).global_vars.R for bare names used in
mxAlgebra() calls.res_scale is now optional: With
data-driven residual estimation, users no longer need to specify
res_scale. When omitted (default NULL), the
data-driven estimate is used; if that fails, a fallback heuristic of
var() * 0.1 is applied. Users can still override with an
explicit res_scale value if desired.res_cor is now optional: Residual
correlation between longitudinal outcomes is now estimated from data
when res_cor is not specified. The estimation uses
within-person demeaned residuals correlated across trajectory pairs. If
estimation fails, a heuristic of 0.3 is applied as
fallback. This applies to getMGM(),
getMediation(), getTVCmodel() (when
decompose != 0), getMIX(), and
getMGroup(). Users can still override with explicit
res_cor values if desired.paramOut = TRUE, the names argument is now
optional. If omitted (default NULL), meaningful parameter
names are automatically generated based on the model configuration
(functional form, intrinsic flag, covariates, decompose level, etc.).
This eliminates the need for users to manually construct long parameter
name vectors for every model. Users can still supply custom names to
override the auto-generated labels. Auto-naming is supported across all
7 model functions: getLGCM(), getLCSM(),
getTVCmodel(), getMediation(),
getMGM(), getMIX(), and
getMGroup(). A safety net in all output helpers auto-pads
with generic labels if the name count doesn’t match the estimate
count.initial_helpers_common.R, added reusable validation helpers
in validation_helpers.R, and introduced shared helpers for
output naming and multivariate covariance reduction in
output_helpers_common.R and
multi_cov_helpers.R.@seealso cross-references linking related
functions across all 14 exported functions.@noRd to all 44 internal helper files to suppress
unnecessary .Rd generation.nlpsem_overview) with
package overview and quick-start guide.getMGroup_examples vignette with a second
example (quadratic LGCM with TICs).global_vars.R.NEWS.md changelog.\examples{} sections to ModelSummary
and printTable S4 generics.$95\%$) to plain text in
KappaOutput class documentation.LGCM.mxModel_helper.R.getEstimateStats.R roxygen.getLatentKappa.R example.validation_helpers.R.getMIX.R and
getMGroup.R (said “latent change score model” instead of
“mixture model” / “multiple group model”).inst/extdata.DESCRIPTION
(knitr, rmarkdown, and
VignetteBuilder) and added the introductory
nlpsem_overview vignette.tests/testthat suite covering validation
helpers, automatic parameter naming, nonlinear growth-factor starts,
mediation starts, and multivariate regression cases.inst/extdata/*.RData and aligned the vignettes with those
regenerated examples.getLGCM()) and
latent change score models (getLCSM()), with optional
time-invariant covariates (TICs).getTVCmodel()).getMGM()) and
longitudinal mediation models (getMediation()).getMGroup()) for comparing
trajectories across manifested groups.getMIX()) for identifying
latent trajectory classes.getSummary(): Model fit statistics (number of
parameters, -2LL, AIC, BIC).getEstimateStats(): P-values and confidence intervals
(Wald, likelihood-based, and bootstrap).getFigure(): Visualization of estimated growth
trajectories with confidence intervals.getLRT(): Likelihood ratio test for nested model
comparison.getIndFS(): Individual factor scores.getPosterior(): Posterior class probabilities for
mixture models.getLatentKappa(): Cohen’s kappa for latent class
agreement.RMS_dat, a processed sample from the
ECLS-K:2011 study for demonstration purposes.