Changes in Version 0.16.9
  o The ordinal-data regression tests (GitHub issue #54) now probe whether the
    'pbv' package's C++ API is usable and are skipped when it is not. On some
    check setups (e.g. win-builder R-devel) a stale 'pbv' binary lacks the
    Rcpp validation hook, which made every ordinal-data call error and halted
    the whole test suite.

Changes in Version 0.16.8
  o The saturated log-likelihood method that was ultimately used is now
    recorded in the fitted object (GitHub issue #53):
    x@baseline_saturated$satMethodUsed is "numeric", "analytic", or
    "analytic_fallback" (the latter when saturated = "default" fell back to
    the analytic formula because the saturated model optimization failed),
    and the new 0/1 fit measure 'satLL_analytic' indicates whether the
    analytic formula was used. Being numeric, satLL_analytic is retained by
    aggregate_bootstraps(): its bootstrap average is the proportion of
    (converged) bootstrap samples in which the analytic saturated
    log-likelihood was used.
  o Consistent with its documentation, saturated = "model" no longer silently
    falls back to the analytic saturated log-likelihood when the saturated
    model optimization fails; the fallback only applies to
    saturated = "default".
  o Fixed a stepup() failure with ordinal data (GitHub issue #54): freeing a
    parameter using its expected parameter change (EPC) as starting value
    could place the model in an improper (non-positive definite) region,
    where the fit functions return a constant penalty with a flat gradient.
    The optimizer then stalled on that plateau, which surfaced as
    "missing value where TRUE/FALSE needed" or as spurious "model did not
    improve" results in stepup(). Three fixes:
    - freepar() now checks that EPC-based starting values leave the implied
      matrices proper, and falls back to small starting values otherwise.
    - runmodel() detects an optimizer that "converged" on the improper-region
      penalty plateau and retries once from emergency starting values.
    - stepup() now handles an NA chi-square difference test gracefully
      (returning the previous model with a message) and, when the requested
      criterion (e.g. the default "bic") is not available for the estimator
      (information criteria are not defined for the (D)WLS estimators used
      with ordinal data), skips the criterion check with a single informative
      warning instead of erroring.

Changes in Version 0.16.7
  o Fixes for R-devel (found via win-builder): the example covariance matrices
    in the dlvm1, panelvar, and latentgrowth help pages no longer use the
    deprecated structure(.Dim=, .Dimnames=) names (now dim= and dimnames=),
    and the unit tests that cross-validate against internal lavaan kernels
    (lav_mvnorm_cluster_information_expected, lav_model_h1_information_observed)
    now skip gracefully when those internals are absent, as in lavaan >= 0.7
    where they were renamed with changed interfaces. The same quantities remain
    covered by internal analytic-vs-numeric checks.
  o Naming cleanup: the plain names are now the primary names of several model
    families, with the historical *1 names (the "1" referred to the lag order;
    no lag-2 frameworks are planned) remaining fully supported aliases:
    dlvm() = dlvm1(), tsdlvm() = tsdlvm1(), meta_var() = meta_var1(), and
    ml_var()/ml_gvar() = ml_var1()/ml_gvar1() (see below). var1() and gvar()
    are unchanged: exporting var() would mask stats::var, and "var1" reads
    naturally as VAR(1). In addition, 'within' and 'between' are now the
    preferred argument names for the contemporaneous and between-person
    structures in the latent-free frameworks panelvar() and ml_var()
    (ml_var1() now accepts them too); the *_latent names remain accepted
    everywhere.
  o ml_var()/ml_gvar()/ml_var1()/ml_gvar1() default to estimator = "auto":
    full-information ML (through the panelvar framework) when the
    measurement-occasion grid has at most 10 occasions per subject - the
    panel-data regime, matching the behavior (and estimates) of ml_var() in
    psychonetrics 0.15 - and the two-level summary-statistics pseudo-ML for
    longer series, where full-information ML is infeasible. A message states
    which estimator was selected; set 'estimator' explicitly to override.
  o ml_var1()/ml_gvar1() now support estimator = "FIML": the model is dispatched
    to the panelvar() framework, with the measurement occasions (idvar / dayvar /
    beepvar) mapped to the waves of a panel model, so the exact multi-level
    VAR(1) likelihood is optimized with full-information ML (missing responses
    and missed beeps handled by FIML; a night break is bridged by one
    structurally missing occasion). This is exact but much slower than the
    default two-level summary-statistics estimator; it replaces the historical
    dlvm1-based FIML route of ml_var()/ml_gvar().
  o ml_var() and ml_gvar() are now aliases for ml_var1() and ml_gvar1() (their
    'contemporaneous' and 'between' arguments map to 'within_latent' and
    'between_latent'). They were deprecated wrappers around dlvm1() with
    estimator = "FIML"; the new default estimator = "auto" (see above) keeps
    the 0.15 behavior for panel-like data and switches to the summary-
    statistics estimator for longer series. ml_tsdlvm1() and ml_ts_lvgvar()
    (the latent versions) remain deprecated wrappers for dlvm1().
  o Added a summary() method for psychonetrics models: the print output extended
    with the main fit measures (log-likelihood, number of parameters, AIC, BIC,
    RMSEA, CFI, TLI). Previously summary() silently fell through to the default
    method and showed nothing useful.
  o meta_var1()/meta_gvar() now fail early with an informative error when more
    than 11 variables are used (the p(p+1)/2 + p^2 meta-level outcomes then
    exceed internal 32-bit sparse-matrix limits; estimation previously aborted
    mid-way with a cryptic "SpMat::init(): requested size is too large" error),
    and warn when the number of studies is smaller than the number of meta-level
    outcomes while a full random-effects structure is estimated (heavily
    overparameterized; results may be unstable). For subjects of a single
    intensive longitudinal study, ml_var1()/ml_gvar1() is recommended instead.
    The meta_var1 estimator itself was verified by simulation to recover the
    generating temporal and contemporaneous networks (see extra/).
  o NEW (experimental): ml_varcov() and its wrappers ml_ggm() and ml_corr() add
    a multi-level (two-level) variance-covariance family: the within-cluster and
    between-cluster (co)variance matrices of clustered data are modelled
    directly, with no latent layer. This is the multi-level analogue of the
    varcov() family, and the multi-level Gaussian graphical model (ml_ggm) is
    its headline use case. Each level is parameterized independently through the
    'within' and 'between' arguments (each "cov", "chol", "prec", "ggm" or
    "cor"), with native matrix names sigma_within / sigma_between,
    omega_within / omega_between, and so on. Both estimators of ml_lvm() are
    supported and behave identically to their ml_lvm() equivalents: the
    two-level sufficient-statistics ML estimator (estimator = "ML"; cost
    independent of the number of units per cluster) and wide-format
    full-information ML (estimator = "FIML"; supports missing data). It is the
    special case of ml_lvm() with loadings equal to the identity and no residual
    variance, but is implemented as its own framework with its own (simpler)
    analytic derivatives in both R and C++. Verification: analytic gradients
    match numeric differentiation for all 25 within x between type combinations
    under both estimators; the fit function and gradient agree with
    ml_lvm's to machine precision at identical parameter values; R and C++
    agree to machine precision; and parameter recovery / confidence-interval
    coverage were validated in a simulation study. The multi-level GGM is also
    available, via a different (nodewise multilevel regression) estimator, as
    mlGGM() in the mlVAR package.
  o Fixed ml_lvm() (and ml_varcov()) failing -- or, for exchangeable cluster
    positions, silently permuting observations -- when the data rows were not
    sorted by cluster: the internal within-cluster ID was assigned in
    cluster-sorted order rather than row order. Cluster labels reused across
    different groups of a multi-group analysis now also correctly denote
    different clusters.
  o Fixed samplestats() crashing with "missing value where TRUE/FALSE needed"
    when heavy missingness left some pairwise covariances undefined (e.g. a
    wide-format FIML analysis with sparsely observed cluster positions); the
    correlation-input auto-detection now treats undefined diagonals as
    "not correlation input" (the NA-covariance warning is still given).

Changes in Version 0.16.6
  o NEW (experimental): ml_var1() and its GGM-defaults wrapper ml_gvar1() add a
    multi-level (random intercept) lag-1 vector autoregression / graphical VAR
    for intensive longitudinal (ESM) data. A within-person stationary VAR(1)
    process (temporal network beta, contemporaneous network omega_zeta_within)
    is combined with a between-person random-intercept structure (network
    omega_zeta_between) - the three multi-level GVAR networks. The model targets
    the same estimand as panelvar()/panelgvar() but is estimated by applying the
    two-level sufficient-statistics ML machinery (ml_lvm) to the lag-embedded
    ("Toeplitz") data, so its per-evaluation cost is independent of the number of
    time points (2p x 2p matrices only) where the full-likelihood panelvar route
    becomes infeasible. This is a working-independence pseudo-likelihood (as in
    var1): use panelvar()/panelgvar() for few-waves panel data. Estimates of beta
    and omega_zeta_within are consistent and track the full-likelihood estimates
    closely; the between-person variances carry a small O(1/T) upward finite-length
    bias and the pseudo-likelihood standard errors are somewhat anti-conservative
    for the contemporaneous network (see ?ml_var1). Validated against lavaan
    two-level models (the saturated model, and the structured model in the
    saturated-innovations cov/cov case, which admits an exact lavaan
    reparameterization; the GGM/typed cases rest on numeric-derivative checks
    and the panelgvar benchmark) and against panelgvar. Complete-pairs
    (listwise) handling of incomplete lag pairs in this first version. The model
    layer is implemented in R only for now (no C++ acceleration).
  o Internal: tsData() gained an includeID argument (default FALSE) that attaches
    the subject id to each augmented lag-pair row; used by ml_var1() to define
    clusters. With includeID = FALSE the output is byte-identical to before, so
    var1()/gvar()/tsdlvm1() are unaffected.

Changes in Version 0.16.5
  o NEW: sampling weights (pseudo-maximum-likelihood) via the sampling_weights
    argument of varcov()/lvm() (and the ggm/precision/... wrappers). The moments
    are computed as weighted means/covariances and the model is fit with robust
    (MLR) Huber-White sandwich standard errors and a Yuan-Bentler-Mplus scaled
    test statistic, matching lavaan's sampling.weights= to numerical precision
    (single- and multi-group; estimates, SEs, scaled chi-square and robust fit
    indices). Complete-data continuous input only.
  o Documented that, unlike lavaan (auto.cov.lv.y = TRUE), psychonetrics does not
    free the residual covariances among endogenous latent variables by default;
    free them via sigma_zeta to reproduce a lavaan SEM (this also makes fixed_x
    models with latent regressions match lavaan exactly). Removed a now-obsolete
    caveat in the fixed_x documentation (the baseline is conditioned on x since
    this release).
  o Fixed fromlavaan(): a single-group lavaan fit WITHOUT a mean structure
    (the default for cfa()/sem()) was converted with colliding parameter
    indices, silently equating observed intercepts with loadings/variances
    (chi-square Inf, wrong df). Meanstructure = TRUE fits were unaffected.
  o Fixed fixed_x: the baseline (and saturated) reference models are now
    conditioned on the exogenous block, matching lavaan. Previously the
    baseline was an unconditioned independence model over all variables,
    producing a negative baseline.df, an inflated baseline chi-square, NaN
    TLI and incorrect CFI/TLI for every fixed_x model.
  o Fixed the scaled chi-square difference test in compare() for the
    scaled-shifted (MLMV/WLSMV) and mean-and-variance-adjusted (MLMVS)
    estimators. compare() now auto-selects the appropriate difference test
    (the exact Satorra (2000) test for those estimators, Satorra-Bentler
    (2001) for MLM/MLR), matching lavaan's lavTestLRT; previously the default
    Satorra-Bentler-2001 test used the wrong (test-specific) scaling factor
    for scaled-shifted/mean-var fits and returned an incorrect statistic. The
    mean-adjusted scaling factor is now also stored as
    chisq.scaling.factor.sb. A latent bug that returned NA for any scaled
    difference test against a saturated (df 0) reference model was also fixed.
  o Performance: the asymptotic covariance Gamma of the sample statistics is no
    longer computed for plain (non-robust) maximum-likelihood fits; it is
    computed only for the robust ML estimators that need it (and lazily by
    setestimator()). This removes a large constructor-time and memory cost for
    high-dimensional models (e.g. ~12 s and ~84 MB at p = 80).
  o Performance: the robust-ML sandwich building blocks (including the
    expensive numeric observed-information Jacobian for MLR) are now computed
    once per addfit() call and reused for the scaled test and the FIML-C robust
    fit indices, instead of being rebuilt several times.
  o Added a df <= 0 guard to the robust-ML scaled test (compute_ml_scaled_test)
    for consistency with the MLR path.

Changes in Version 0.16.4
  o NEW: the temporal effects of all lag-1 frameworks can now be
    parameterized directly in terms of partial directed correlations (PDC),
    the standardized temporal network that is usually interpreted in
    graphical VAR analyses: temporal = "PDC" in var1()/gvar(),
    panelvar()/panelgvar() and tsdlvm1()/ts_lvgvar(), and
    temporal_latent = "PDC" / temporal_residual = "PDC" in dlvm1() (the
    latter for the residual autoregression beta_epsilon, giving a
    PDC_epsilon matrix). The PDC parameter matrix encodes from = row and
    to = column (the transposed orientation relative to beta, matching
    getmatrix(..., "PDC")), and its elements are bounded in (-1, 1).
  o The PDC parameterization is a smooth, zero- and sign-preserving
    reparameterization of beta given the innovation structure
    (beta[j,i] = PDC[i,j] * sqrt(kappa_ii * sigma_jj) / sqrt(1 - PDC[i,j]^2)),
    so any pattern-constrained PDC model is fit-equivalent to the
    corresponding raw model: same likelihood, degrees of freedom and implied
    covariance structure, and the fitted PDC parameters equal
    computePDC()/getmatrix(raw fit, "PDC") exactly at the optimum (verified
    numerically per family). The gain is inference on the interpreted
    scale: Wald standard errors, confidence intervals (CIplot()),
    modification indices, prune()/stepup(), and cross-group equality
    constraints (groupequal("PDC")) all apply directly to the PDC elements
    - e.g. testing whether the standardized temporal network is equal
    across groups while innovation variances differ.
  o Implemented in both the R and C++ engines via a block-triangular
    reparameterization of the model Jacobian (the beta column block is
    post-multiplied by d vec(beta)/d vec(PDC), and the dependence of beta on
    the innovation (co)variances is routed into the innovation columns).
    Analytic gradients verified against numeric ones for all innovation
    types (cov, chol, prec, ggm, cor) in every family, and the PDC standard
    errors verified against delta-method standard errors from the raw
    parameterization.
  o CIplot() displays PDC edges in the correct direction (from = row ->
    to = column); prune(), stepup(), modelsearch(), unionmodel(),
    intersectionmodel(), partialprune() and penalized estimation select the
    PDC matrix by default for PDC-parameterized models. transmod() does not
    transform temporal parameterizations (re-specify the model instead; a
    saturated PDC model is fit-equivalent to the saturated raw model).
  o checkFisher() is now exact for FIML models: the expected-model reference
    (expectedmodel()) also replaces the scatter matrices of single-row
    missingness patterns (previously stored as zero and skipped) by their
    implied expectations. Before this fix, FIML models containing
    single-row patterns - notably var1()/tsdlvm1(), whose lag-augmented
    data always contain one boundary row - showed a spurious O(1/n)
    deviation (about 0.5 percent) between the analytic and numeric Fisher
    information in checkFisher(), even though the analytic information was
    correct. Estimation, standard errors and fit measures are unaffected
    (expectedmodel() is only used by the numeric Fisher verification
    tooling).

Changes in Version 0.16.3
  o NEW: dlvm1() gained a 'beta_epsilon' argument: lag-1 temporal effects at
    the residual level, epsilon_t = beta_epsilon epsilon_(t-1) + u_t. The
    default "zero" reproduces the previous white-noise residual structure
    exactly (verified against 0.16.2 reference fits: identical estimates,
    standard errors, degrees of freedom and likelihoods). "diag" gives each
    indicator its own residual autoregression, modeling item-specific
    carry-over (autocorrelated uniquenesses) with a geometrically decaying
    structure; "full" and custom pattern matrices are also supported, as are
    cross-group equality constraints (equal = "beta_epsilon" and
    groupequal("beta_epsilon")).
  o When beta_epsilon is non-zero, sigma_epsilon_within (and its typed
    variants: chol, prec, ggm, cor) parameterizes the innovation covariance
    of the residual process; the implied stationary residual covariance is
    available via getmatrix(model, "sigma_epsilon_within_stationary").
    Together with sigma_epsilon_between (the perfectly stable item
    component), this gives each indicator a STARTS-like decomposition:
    stable trait + autoregressive part + latent dynamics.
  o Identification: a non-zero beta_epsilon requires at least three waves
    (a warning is issued with two); four or more are recommended. Since the
    white-noise model is nested (beta_epsilon = 0), modification indices for
    the beta_epsilon elements provide a direct diagnostic for residual
    autocorrelation, and the LRT against the default model is the natural
    test.
  o Implemented in both the R and C++ engines; analytic gradients verified
    against numeric ones for diag/full beta_epsilon across residual types
    (cov, chol, ggm, cor), multi-group models with equality constraints,
    FIML, and missing-wave designs, and the implied covariance structure
    verified against an independent implementation and a large Monte Carlo
    simulation of the generating process.

Changes in Version 0.16.2
  o NEW: panelvar() and panelgvar() are now implemented as their own model
    framework ("panelvar") rather than as a dlvm1 model with dummy matrices
    (identity factor loadings and zero residual variances). The model is
    unchanged -- a within-person stationary VAR(1) process plus a
    between-person (random intercept) structure -- and results are identical
    to the previous route (same estimates, standard errors, degrees of
    freedom and fit measures; the existing panel reference values reproduce
    exactly). Estimation skips all factor-loading algebra in both the R and
    C++ engines, making runmodel() roughly 1.3-2.6 times faster (the gain
    grows with the number of variables and waves). Analytic gradients of the
    new framework are verified against numeric ones for all within/between
    types (cov, chol, prec, ggm, cor), including multi-group models with
    equality constraints, FIML, and designs with missing waves.
  o The panelvar/panelgvar model matrices keep their dlvm1 names (beta,
    sigma_zeta_within, sigma_zeta_between and their typed variants), so
    getmatrix(), prune(), stepup(), transmod(), CIplot(), esa() and
    modelsearch() keep working unchanged. The mean structure is now a single
    'mu' matrix of observed stationary means (previously the observed
    intercepts appeared as 'nu' with latent means 'mu_eta' fixed to zero);
    equality constraints requested on 'nu' are mapped to 'mu' for backward
    compatibility. Variable names now default to the design-matrix rownames
    (or V1, V2, ...) instead of Eta_1, Eta_2, ....
  o panellvgvar() (the latent panel GVAR) is unaffected and still uses the
    dlvm1 framework.

Changes in Version 0.16.1
  o NEW: the correlation parameterization (type = "cor"; a correlation matrix
    rho with a diagonal matrix of standard deviations SD, such that
    sigma = SD (I + rho) SD) is now available for every typed covariance
    structure in the package, alongside "cov", "chol", "prec" and "ggm":
    - lvm() latent and residual structures (rho_zeta/SD_zeta and
      rho_epsilon/SD_epsilon), including lnm()/rnm()/lrnm() and meta_lvm()
    - var1()/gvar() contemporaneous (innovation) structure (rho_zeta/SD_zeta),
      including meta_var1()/meta_gvar()
    - tsdlvm1()/ts_lvgvar() contemporaneous and residual structures
    - dlvm1() and ml_lvm() within/between latent and residual structures,
      including panelvar()/panelgvar()/panellvgvar() and ml_lnm()/ml_rnm()/
      ml_lrnm(), and ri_clpm() (type = "cor")
    No new derivatives were needed: the existing analytic rho/SD Jacobian
    blocks (used by varcov(type = "cor") and the meta-analytic random
    effects) are chained into each family's gradient, in both the R and
    C++ engines. transmod() can transform saturated/diagonal structures
    to and from "cor", and CIplot() selects rho matrices for cor-typed
    models.
  o With identification = "variance", cor-typed latent structures are
    identified by fixing the latent standard deviations (the diagonal of
    the SD matrix) to 1, so the latent covariance structure becomes a
    correlation matrix (I + rho).
  o The "cor" parameterization is fit-equivalent to the "cov"
    parameterization when saturated (verified numerically for all families:
    identical log-likelihoods, degrees of freedom and implied covariance
    structures; analytic gradients verified against numeric ones in both
    the R and C++ engines).

Changes in Version 0.16
  o Version 0.16 is a major feature release that consolidates the 0.15.x
    development series. It bundles a large internal code audit (numerous
    bug fixes, robustness and performance improvements across all model
    families, and a new automated test suite) together with substantial new
    modeling functionality, all validated against lavaan. The detailed
    per-version changelog is retained below.

  o NEW: a fast sufficient-statistics two-level maximum-likelihood estimator for
    multi-level latent variable models (ml_lvm(estimator = "ML")), supporting
    complete and within-cluster-missing data.

  o NEW: robust maximum-likelihood estimators MLM, MLMV, MLMVS and MLR
    (robust/sandwich standard errors, scaled chi-square statistics and robust
    RMSEA/CFI/TLI), with Satorra-Bentler-family scaled chi-square difference
    tests in compare().

  o NEW: lavaan interoperability via fromlavaan() and tolavaan(), standardized
    (std.all) solutions with delta-method standard errors, the wishart
    likelihood, and fixed exogenous covariates (fixed_x).

  o The estimators, fit measures, standard errors and converters listed above
    were validated against lavaan as a numerical oracle. Several of the new
    methods carry an experimental note (shown when verbose = TRUE) and may still
    change.

Changes in Version 0.15.31
  o This release adds new modeling functionality validated against lavaan: a
    fast two-level maximum-likelihood estimator for multi-level latent variable
    models, robust maximum-likelihood estimators (MLM/MLMV/MLMVS/MLR) with
    scaled test statistics and robust fit indices, scaled chi-square difference
    tests, converters to and from lavaan, standardized solutions, the wishart
    likelihood, and fixed exogenous covariates. All new features are opt-in;
    existing analyses are unaffected. Several new methods carry an experimental
    note (shown when verbose = TRUE).

  o MULTI-LEVEL: ml_lvm() gains a sufficient-statistics two-level ML estimator
    (estimator = "ML"; McDonald & Goldstein, 1989; Muthen, 1990), which
    optimizes the numerically identical objective as the wide-format FIML
    estimator but is dramatically faster for large clusters (e.g. a 100-cluster
    model with cluster sizes 18-25 fits in under a second versus ~90 seconds
    with FIML). Standard errors and modification indices use an analytic
    expected Fisher information (verified against lavaan's kernel to ~1e-13),
    with full C++ implementations. Within-cluster missing data (MCAR/MAR) is
    supported (matching lavaan::sem(..., missing = "ml"); R-based, numeric
    information). The estimator argument now defaults to "default", which picks
    "ML" for complete data with largest cluster > 5 units and "FIML" otherwise;
    explicit estimator = "FIML" reproduces previous results exactly. Saturated
    and baseline reference models, chi-square, df, CFI and TLI agree with
    lavaan's two-level SEM; sample-size-dependent measures (RMSEA, BIC) use the
    number of clusters J (so RMSEA is larger than lavaan's by a factor
    sqrt(N/J)).

  o ROBUST ML: added robust maximum-likelihood estimators selectable via the
    model constructors or setestimator(): "MLM", "MLMV", "MLMVS" (Browne-1984
    robust.sem sandwich standard errors with the Satorra-Bentler,
    scaled-and-shifted, and mean-and-variance-adjusted scaled chi-square,
    respectively) and "MLR" (Huber-White sandwich standard errors with the
    Yuan-Bentler-Mplus scaled chi-square), plus robust RMSEA/CFI/TLI. Point
    estimates are unchanged maximum likelihood. "MLR" supports missing data via
    FIML, with the FIML-corrected (Savalei, 2010) robust fit indices, matching
    lavaan's estimator = "MLR", missing = "fiml". The Satorra-Bentler family is
    complete-data only. All quantities were validated against lavaan.

  o The multi-group scaled (WLSMV / Satorra-Bentler) test statistic now
    accumulates trace((U Gamma)^2) on the global block-diagonal stack rather
    than per group. The per-group form was only correct without cross-group
    equality constraints; multi-group equality-constrained scaled chi-squares
    now match lavaan (the same correction lavaan made in 0.6-13). Single-group
    output is unchanged.

  o compare() now reports a Satorra-Bentler-family scaled chi-square difference
    test (columns Chisq_diff_scaled, p_value_scaled) for nested models fitted
    with a robust or scaled estimator. A new scaled.test.method argument selects
    "satorra.bentler.2001" (default), "satorra.bentler.2010", or "satorra.2000"
    (scaled-and-shifted; appropriate for MLMV/WLSMV). Output for non-robust
    estimators is unchanged. Reproduces lavaan's lavTestLRT().

  o LAVAAN INTEROPERABILITY: added fromlavaan(), which converts a fitted lavaan
    object (or model syntax + data) into an equivalent psychonetrics lvm
    (latent = "cov", residual = "cov"), reproducing estimates, standard errors,
    chi-square, df and log-likelihood for standard ML fits (multi-group,
    equality constraints, std.lv and FIML supported; unsupported features raise
    informative errors). Added tolavaan(), which converts a psychonetrics lvm
    (cov/cov) into a fitted lavaan object or a lavaan parameter table.

  o parameters(x, standardized = TRUE) now returns the completely standardized
    (std.all) solution with delta-method standard errors for the lvm and varcov
    families (new std and se_std columns), matching lavaan::standardizedSolution().

  o Added likelihood = "wishart" to lvm() and varcov() (complete-data Gaussian
    ML): uses the unbiased (n-1) sample covariance with (n-1)-scaled chi-square
    and standard errors, matching lavaan(likelihood = "wishart"). The default
    likelihood = "normal" (n denominator) is unchanged.

  o Added fixed_x to lvm() and varcov(): fixes declared exogenous covariates'
    means and mutual (co)variances to their sample values (conditioning on x),
    matching lavaan(fixed.x = TRUE); estimates, standard errors, chi-square, df
    and the conditional log-likelihood match lavaan. Incremental fit indices use
    psychonetrics' unconditional baseline.

  o usecpp() keeps the two-level ML estimator with missing data on the R path
    (it has no C++ implementation) instead of failing with a cryptic error.

Changes in Version 0.15.30
  o This release implements the remaining findings of a large internal code
    audit: bug fixes across all model families, robustness and performance
    improvements, a documentation overhaul, and a new automated test suite.

  o ESTIMATION AND OPTIMIZATION:
  o Fixed an inverted positive-definiteness flag and re-enabled the optimizer
    penalty for improper (non-positive-definite) implied covariance matrices.
    Previously the ML objective was unbounded below at such points, which
    could make optimizers (notably nlminb, the default) diverge to absurd
    estimates on panel models (panelvar/panelgvar) and SEM models. Both the
    C++ and R fit paths now return a large penalty instead.
  o Fixed the experimental "LBFGS++" optimizer reporting convergence at
    infeasible points (non-finite objective, penalty bound, NA parameters, or
    iteration limit); such runs now trigger runmodel's emergency restart or a
    clear error. Also fixed runmodel's recovery logic for the C++ optimizer
    branches: NA optimizer output now triggers an emergency restart, and
    failed recoveries stop with an informative error.
  o Fixed emergencystart() constraining the wrong edges to zero in its glasso
    restart (free edges were zeroed instead of structurally fixed ones).
  o latentgrowth() (and lvm models with fully fixed nu and free nu_eta) now
    uses least-squares starting values for the latent means, fixing frequent
    non-convergence on growth data with nonzero means.
  o Fixed elastic-net penalized ML (0 < alpha < 1) applying the ridge
    component twice in the proximal-gradient optimizer; LASSO (alpha = 1)
    results are unaffected. The default beta_min for penalized-model lambda
    selection is now "numerical" (1e-05), matching the documentation.
  o Fixed an operator-precedence bug in the internal spectral shift used for
    start values (it added 0.001 to every element instead of shifting the
    diagonal and could leave matrices exactly singular). Start values of
    dlvm1/panelgvar-type models on difficult data may change slightly
    (typically improving convergence). The GGM correlation-input runtime path
    no longer applies a spectral shift, aligning the R and C++ paths.
  o solve_symmetric: the log-determinant plugin for non-positive-definite
    matrices now also catches NaN. Fixed a potential stale-cache issue in the
    C++ optimization workspace (cached model now protected from garbage
    collection). refit()-ed models now select the same default optimizer as
    freshly specified models; runmodel() warns when bounded = TRUE is ignored
    by ucminf.

  o GENERAL BUG FIXES:
  o fullFIML = TRUE (row-wise FIML) errored with "Not a matrix." for all
    standard model families; the C++ FIML fit, gradient, expected Hessian and
    log-likelihood now handle both single implied matrices and per-row lists.
  o Fixed equal = "omega_zeta" being silently ignored (and "lowertri_zeta"
    wrongly applied instead) in lvm/lnm/lrnm models with a latent network.
  o factorscores() now correctly accounts for structural regressions (beta)
    in the regression method and adds the model-implied latent means to both
    methods, fixing wrong scores in SEM-style and multi-group models.
  o Fixed varcov(covtype = "choose"): detected unbiased (n-1) input
    covariances are now correctly rescaled to ML.
  o Fixed multiple-comparison p-value adjustment (prune/adjust): the
    correction is now over unique parameters rather than parameter-table
    rows, so equality-constrained parameters are no longer over-penalized.
  o Fixed prune(recursive = TRUE) resetting alpha to 0.01 (and dropping
    mode/startreduce/limit) in passes after the first.
  o Likelihood-based fit measures (logl, AIC, BIC, ebic) are now retained
    when chisq/baseline measures are unavailable (e.g. baseline_saturated =
    FALSE). Fixed the ebic.75 fit measure (used gamma 0.7 instead of 0.75).
  o Fixed getmatrix(..., diag = FALSE) zeroing the wrong elements.
  o runmodel(analyticFisher = FALSE) now actually uses the numeric Fisher
    information on the default C++ path. Fixed the identification check in
    greedy stepup() inspecting the previous model's information.
  o Standard errors: inversion-failure/pseudoinverse warnings are no longer
    suppressed when verbose = FALSE; negative inverse-information variances
    now yield NA standard errors with a warning (R and C++ paths).
  o setestimator() now resets the computed flag, propagates the estimator to
    baseline/saturated models, and validates required sample statistics.
  o varcov-family models (ggm, corr, ...) now give an informative error when
    corinput = TRUE is combined with a meanstructure under (penalized) ML,
    instead of crashing during optimization.
  o Fixed parequal() argument handling (named runmodel arguments leaked into
    the constraint set). fixpar() and friends now error on mistyped variable
    labels instead of silently doing nothing. compare() reports NA p-values
    for equal-df models and warns on negative chi-square differences.
  o Fixed lvm/lnm/meta_lvm with latent = "ggm" and a single latent variable
    crashing at construction. lvm() now warns when there are more latent
    than observed variables. bifactor() uses deterministic starting values.

  o ISING / BLUME-CAPEL:
  o The partition function and moment computations now use log-sum-exp
    accumulation: extreme parameter values no longer produce Inf/NaN fits,
    gradients, or Fisher information (results unchanged in the normal
    regime). Long state-space enumerations can now be interrupted.
  o Raw data are now validated against the responses argument (stray values
    error instead of silently biasing the fit); only missing = "listwise" is
    supported and other options error at construction; the free inverse
    temperature beta is now bounded at 0; groupequal("delta") now correctly
    frees the group-specific beta in Blume-Capel models.

  o TIME SERIES / PANEL MODELS:
  o Fixed sigma_within (and sigma_crosssection) reporting in dlvm1-family
    models: the between-person residual covariance was added instead of the
    within-person residual covariance (estimation was not affected).
  o ri_clpm stationarity constraints ("innovation"/"contemporaneous") now
    select parameters structurally per wave; pruned/fixpar'd models
    previously produced corrupted equality constraints and now constrain
    correctly or stop with an informative error. Unpruned behavior unchanged
    (wave 1 and the random-intercept block remain free).
  o tsData(): grouped data previously ignored lags, scale, center and
    centerWithin; lags other than 1 erroneously produced lag-1 columns. Both
    fixed. var1()/gvar() gain a centerWithin argument (default FALSE).
  o ml_lvm: variable names containing regex metacharacters (e.g. "y.1") are
    no longer silently dropped (same fix in dlvm1/ri_clpm wide matching);
    the C++ implied structure now correctly subsets by the design matrix for
    incomplete designs, matching the R implementation; the default vars now
    excludes the groups column. dlvm1-family constructors error informatively
    with fewer than two waves. dlvm1 "version2" start values now use the
    between-person residual covariance estimate for epsilon_between.

  o META-ANALYTIC MODELS:
  o meta_varcov now genuinely supports covariance matrices as input (covs=
    defaults to corinput = FALSE as documented); previously covariances were
    silently modeled as correlations and corinput = FALSE crashed.
  o New Vmethod options "OS_individual" and "OS_pooled" in meta_varcov: the
    Olkin-Siotani (1976) sampling covariance of sample correlations
    (validated against metaSEM::asyCov), recommended when heterogeneity
    (random-effects) variances are of substantive interest. The default
    Vmethod is unchanged for backward compatibility with published analyses.
  o Fixed meta_varcov/meta_ggm with estimator = "ML" and meta_lvm with
    residual = "prec" crashing at the first gradient evaluation. Fixed
    bivariate (single-correlation) meta_varcov crashing at construction.
    Meta models now error informatively when two variables are never
    observed together in any study. meta_lvm uses the C++ Jacobian for the
    Fisher information when cpp = TRUE.

  o PERFORMANCE:
  o The joint equality score test used by addMIs/runmodel on multi-group
    models factorizes the bordered information system once: up to several
    hundred times faster on large equality-constrained models (identical
    results). The matrices argument of addMIs() is now implemented.
  o Commutation matrices are constructed directly in sparse form and, with
    the other algebra helper matrices, cached per dimension: substantially
    faster model construction (about 3x for a 30-node GGM). The sparse
    Fisher-information path is now reachable for sparse model Jacobians.
  o Ising state enumeration avoids per-state matrix copies and the
    log-likelihood reuses the prepared partition function (about 1.3-1.6x
    faster Ising runmodel). Plus assorted micro-optimizations.

  o INFRASTRUCTURE, DOCUMENTATION AND TESTS:
  o Added an automated test suite (tinytest) covering the psychonetrics.org
    examples plus targeted regression tests for the fixes in this release,
    with reference values cross-checked against lavaan. tinytest added to
    Suggests; removed unused dependencies GA, combinat, VCA and abind.
  o loop_psychonetrics() and replicator() gain a seed argument for
    reproducible (parallel) simulations; aggregate_bootstraps()'s
    remove_problematic argument is now respected.
  o Large documentation cleanup: fixed a broken example in partialprune.Rd,
    corrected the documented MIs() type values, documented lambda = "full",
    corrected Cholesky equations and stale paragraphs in the family pages,
    added missing \value sections, fixed broken examples, removed the
    unimplemented "MUML" option from ml_lvm(), added a ggplot2 guard to
    CIplot(), and fixed many typos.
  o Added inst/COPYRIGHTS and a DESCRIPTION Copyright field documenting the
    bundled LBFGS++ (MIT) headers; removed a linker flag that stripped debug
    symbols (CRAN policy); dev-checklist files excluded from the build.

Changes in Version 0.15.29
  o Bugfix: the "loadings" identification used by lvm(), ml_lvm(), dlvm1() and
    tsdlvm1() could silently overwrite a user-fixed (or built-in) nonzero
    factor loading. To identify a factor's scale the identifier fixed the first
    "eligible" loading in each column to 1, where "eligible" included loadings
    already FIXED at a nonzero value. When the first such loading was fixed at a
    value other than 1, it was overwritten -- changing the model. This notably
    affected latentgrowth() with non-default time scores: e.g.
    latentgrowth(vars, data, time = c(0,2,4,6)) had its slope loadings altered
    from 0,2,4,6 to 0,1,4,6, fitting a different growth model. The identifier
    now skips a factor whose column already contains a fixed nonzero loading
    (the scale is already identified) and otherwise fixes the first FREE loading
    to 1. Standard CFA behaviour (first free loading fixed to 1) is unchanged,
    and a user-supplied marker loading fixed at any nonzero value is now
    preserved.

Changes in Version 0.15.28
  o Bugfix: the multi-level latent variable model (ml_lvm) analytic Jacobian
    omitted the derivative of the implied means with respect to the
    between-subject regressions (beta_between). The implied mean is
    mu = nu + lambda %*% (I - beta_between)^-1 %*% nu_eta, which depends on
    beta_between whenever the latent intercepts nu_eta are nonzero, but the
    d/d-beta_between block of the mean rows was never filled (in both the R and
    C++ derivative paths). This produced an incorrect gradient/Jacobian column
    for any ml_lvm with free between-subject regressions and free (nonzero)
    latent means -- reachable since the 0.15.18 nu_eta identification fix. The
    analogous block is now filled via d_mu_beta_lvm() / d_mu_beta_lvm_cpp(),
    matching the single-level lvm. Within-cluster latents have mean zero, so
    beta_within correctly contributes no mean term.

Changes in Version 0.15.27
  o Bugfix: runmodel(addInformation = FALSE) crashed the whole call in the C++
    standard-error path with "Mat::operator(): index out of bounds". addSEs_cpp()
    read the @information slot unconditionally, but with addInformation = FALSE
    that slot is empty (a 0x0 matrix), so indexing into the inverted matrix went
    out of bounds. It now recomputes the Fisher information on demand when the
    slot is absent, mirroring getVCOV(). The R getVCOV() path had the same latent
    bug -- its is.null() check never fired for the default empty "matrix" slot,
    silently returning all-NA standard errors -- and now also treats a zero-size
    information matrix as absent. Both paths produce identical, correct SEs.

Changes in Version 0.15.26
  o Bugfix: changedata() unconditionally accessed colnames(data), so the
    documented summary-statistics usage changedata(x, covs=, nobs=) crashed
    with 'argument "data" is missing'. The data-dependent label check is now
    guarded, and the covs/means/nobs input path is forwarded to samplestats()
    and the baseline/saturated constructors, mirroring how the model
    constructors accept either raw data or summary statistics.
  o Bugfix: changedata() always recomputed @sample@nobs including the mean
    block (nVar*(nVar+1)/2*nGroup + nVar*nGroup), which corrupted the degrees
    of freedom for models built with meanstructure = FALSE (e.g. a saturated
    model reported df = 4 instead of 0). The mean block is now counted only
    when the model has a mean structure, matching bootstrap() and the model
    constructors.

Changes in Version 0.15.25
  o Bugfix: transmod() attached incorrect standard errors and p-values to the
    transformed model (e.g. transmod(type="ggm") on a covariance model, or the
    reverse). The transformed estimates were correct, but addSEs() reused the
    Fisher information copied verbatim from the original model - which still
    described the pre-transformation parameterization - because getVCOV()
    reuses a model's stored @information whenever it is non-NULL. transmod()
    now recomputes the Fisher information on the transformed model before
    adding standard errors, so the SEs and p-values match those of an
    equivalent model fit directly. Point estimates are unchanged.

Changes in Version 0.15.24
  o Bugfix: in multi-group models, the type="free" and type="equal" modification
    index passes in addMIs() wrote their expected parameter changes to the
    "normal" epc column instead of epc_free and epc_equal. As a result, in any
    multi-group runmodel() (which runs normal -> free -> equal passes) the normal
    EPC column was overwritten twice while epc_free and epc_equal remained NA.
    MIs(type="free") and MIs(type="equal") now report their correct EPC columns,
    and the normal EPC is no longer clobbered. Single-group results are unchanged.

Changes in Version 0.15.23
  o Bugfix: bootstrap() resampled rows from the pooled data, ignoring the
    grouping structure of multi-group models. This had three consequences:
    (1) resampling was not stratified, so group sizes drifted between
    replications; (2) the rebuilt sample derived its group order from the
    first resampled row, which - whenever that row belonged to a different
    group than the original first group - permuted the group ids relative to
    x@parameters, so a group's parameters were fit to another group's data
    (e.g. a two-group model with very different group means produced nonsense
    estimates and failed to converge); and (3) the recomputed @sample@nobs
    always included a mean block, corrupting the degrees of freedom for
    meanstructure = FALSE models. bootstrap() now resamples within each group
    (preserving group sizes), rebuilds the data in the original group order so
    group ids stay aligned with the parameter table, and computes nobs as
    nVar*(nVar+1)/2*nGroup + meanstructure*nVar*nGroup (matching the model
    constructor). Single-group models are unaffected.

Changes in Version 0.15.22
  o Bugfix: for multi-group Ising() and BlumeCapel() models specified as
    Ising(data, groups = "group") without an explicit 'vars' argument, the
    grouping column was included when auto-detecting the response options (and
    in the 'min_sum' sum-score check). With numeric group codes this silently
    added the codes as extra response options - e.g. binary 0/1 data with a
    numeric group column was fit as a 3-state {0,1,2} model, giving wrong
    thresholds - and with character group labels it failed later with an
    opaque type error. The grouping column is now excluded from the response
    detection and the min_sum check (matching how samplestats() derives
    'vars'), and a non-numeric response set is now rejected up front with a
    clear error message. Single-group models, and models with explicit 'vars'
    or 'responses' arguments, are unaffected.

Changes in Version 0.15.21
  o Bugfix: the C++ derivative routine for variance-covariance models
    (used by the gradient and Fisher information when cpp = TRUE) wrote the
    precision-matrix (kappa) derivative block into the wrong Jacobian columns:
    it reused the row offset of the variance block instead of the parameter-
    column offset. When the mean structure was present this happened to
    coincide with the correct columns, but for precision() models fitted
    without a mean structure (e.g. precision(covs = S, nobs = n) or
    precision(data, meanstructure = FALSE)) the column indices exceeded the
    width of the Jacobian and Armadillo aborted with "Mat::submat(): indices
    out of bounds" for every estimator. The column range now matches the
    sigma/cholesky blocks (and the R implementation). precision() models
    without a mean structure now run; mean-structure models are unaffected.

Changes in Version 0.15.20
  o Bugfix: standard errors for the ULS and DWLS estimators (used e.g. for
    ordinal data via estimator = "DWLS"/"WLSMV") were computed with the naive
    (Delta'WDelta)^-1 / n formula, which is only correct when the weight
    matrix W equals the inverse of the asymptotic covariance Gamma of the
    sample statistics. For ULS and (especially) DWLS this is not the case, so
    the reported standard errors were biased (e.g. ordinal DWLS loading and
    threshold SEs deviated by roughly 10-12% from lavaan's robust WLSMV
    standard errors, while the point estimates matched). psychonetrics now
    computes the robust sandwich covariance
      (Delta'WDelta)^-1 (Delta'W Gamma W Delta) (Delta'WDelta)^-1 / n
    for ULS and DWLS, matching lavaan's se = "robust" output. Full WLS
    (W = Gamma^-1) and the ML/FIML estimators are unaffected, as the sandwich
    reduces to the naive form there. When Gamma is unavailable (for instance
    when a model is fit to a covariance matrix rather than raw data) the
    non-robust standard errors are returned together with a warning. The
    robust covariance also propagates to confidence intervals, prune() and all
    other consumers of the parameter standard errors.

Changes in Version 0.15.19
  o Bugfix: for multi-group models with ordinal data (e.g. ggm() and other
    models with ordered = ... , groups = ... and estimator = "WLS"/"DWLS"),
    the per-group polychoric correlations, thresholds and WLS weight
    matrices were computed on the full pooled sample instead of each group's
    own rows. Every group therefore received identical (pooled) sample
    statistics, so multi-group ordinal models were silently fit to pooled
    data. Each group is now correctly computed from its own observations.
    Single-group ordinal models and continuous (non-ordinal) multi-group
    models were unaffected.

Changes in Version 0.15.18
  o Bugfix: the identification routine for the multi-level latent variable
    models (ml_lvm() and the ml_lnm/ml_rnm/ml_lrnm wrappers) referred to the
    latent-intercept matrix by the wrong name ("mu_eta" instead of
    "nu_eta"), so the latent intercepts were never fixed for identification.
    This left the mean structure rank-deficient: latent-intercept standard
    errors were nonsensical (~1e6) and the model degrees of freedom were
    inflated. The latent intercepts are now correctly fixed (in group 1 only
    for multi-group models, as for lvm()), matching the corresponding
    two-level SEM. Estimates and log-likelihood of previously fitted models
    are unaffected; only the standard errors and degrees of freedom change.

Changes in Version 0.15.17
  o BlumeCapel() now gives an error (instead of a warning) when fewer than
    three response options are supplied. With only two responses the
    quadratic 'delta' term is not identifiable (x^2 is constant across the
    two states), so it is confounded with the thresholds; use Ising() for
    binary data.

Changes in Version 0.15.16
  o New function allequal(): constrains all free elements of a model
    matrix to be equal within each group (a single shared parameter per
    group), separately per group (not across groups; use groupequal() in
    addition for that). Useful e.g. to model all thresholds (tau) or all
    Blume-Capel quadratic potentials (delta) equal, or to fit an
    equal-edge-weight network (omega). Optional 'row', 'col' and 'group'
    arguments restrict which elements/groups are affected.

Changes in Version 0.15.15
  o New model: BlumeCapel(). The Ising distribution has been generalized
    to the 'Spin' distribution, which adds a per-node quadratic potential
    delta:
        P(X = x) propto exp( sum_i tau_i x_i - sum_i delta_i x_i^2
                             + sum_{i<j} omega_ij x_i x_j ).
    Two models are built on this distribution: the Ising model (delta
    fixed to zero, exactly reproducing the previous Ising behavior) and
    the new BlumeCapel model (delta freely estimated). BlumeCapel()
    shares the Ising() engine, fit, gradient, expected Hessian and
    standard errors. For the ternary -1/0/1 coding this is the usual
    Blume-Capel model; any ordered response set is allowed (a warning is
    raised when the coding departs from the conventional one).
  o Ising() now warns when 'responses' is not -1/1 or 0/1, since the
    behavior is then not exactly that of the classical Ising model.
  o Internally, the model distribution previously labelled "Ising" is now
    labelled "Spin"; both the Ising and BlumeCapel models use it. This is
    transparent to users.

Changes in Version 0.15.14
  o Ising() no longer requires the response options to be integers. Any
    set of two or more distinct ordered response values is accepted in
    'responses' (or detected from the data), integer or not.

Changes in Version 0.15.13
  o The Ising model has been generalized to more than two response
    options. Ising() now accepts any number of ordered integer response
    options in 'responses' (e.g., c(-1,0,1) or seq(-5,5)), encoded
    identically across all variables and detected automatically from the
    data when not supplied. The parameterization is unchanged (one
    threshold per variable in 'tau' and a pairwise network in 'omega');
    only the partition function, expected values and (expected) Hessian
    now sum over all length(responses)^nNode response patterns. With two
    response options the results are identical to before. Only
    non-integer responses raise an error.
  o New argument 'maxStates' to Ising() (default 2^maxNodes) caps the
    number of response patterns the exact ML estimator may enumerate,
    since this cost grows as length(responses)^nNode. For binary data
    the default reproduces the historical 'maxNodes' limit; raise it to
    fit larger multi-state models when feasible.

Changes in Version 0.15.12
  o The random intercept cross-lagged panel model (RI-CLPM) framework
    has been finalized. ri_clpm() now accepts the same data input as
    the dlvm1 family: wide-format data with a design matrix, or
    long-format data via 'idvar' (and optionally 'beepvar'), with the
    format auto-detected. A 'standardize' argument ("z", "z_per_wave"
    or "quantile") was added. ri_clpm() models now also build an
    independence baseline so that incremental fit indices (CFI/TLI)
    are available.
  o New function ri_clpm_search() runs a sequential stationarity
    search: starting from the unconstrained model it adds, one at a
    time, stationarity of intercepts, temporal effects, contemporaneous
    relations and innovation variances, and finally the panelVAR model
    (wave 1 endogenous), retaining each constraint only if it does not
    worsen fit according to AIC, BIC, or a chi-square difference test.
  o Fix: parequal() constrained parameters incorrectly (a misplaced
    parenthesis passed the indices as unlist()'s 'recursive'
    argument), which broke the temporal / contemporaneous / innovation
    branches of ri_clpm_stationary().

Changes in Version 0.15.11
  o New convenience setters update_baseline() and update_saturated()
    overwrite the @baseline_saturated$baseline / $saturated slots of
    a psychonetrics model. Together with `baseline_saturated = FALSE`
    on the model constructor they make it straightforward to attach
    hand-built reference models before calling runmodel().

Changes in Version 0.15.10
  o Fix: the internal saturated reference model no longer inherits
    cross-group equality from the target. Previously the saturated
    builders in 10 model files (varcov / ggm, lvm, ml_lvm, var1,
    dlvm1, tsdlvm1, Ising, plus changedata() / bootstrap()) passed
    `equal = equal` into the saturated, so a multi-group model fit
    with e.g. `equal = "omega"` produced a saturated whose covariance
    structure was constrained equal across groups too. The saturated
    is meant to be a fully-unrestricted per-group reference, so this
    inflated chi-square pvalues and could produce nonsensical
    negative df (the target was no longer nested in the saturated:
    e.g. ggm(equal="omega") on 4 vars / 2 groups gave df = -4).

    The recommended pipeline of imposing cross-group equality via
    groupequal() after model construction was unaffected by this bug;
    only the `equal = ...` shortcut at construction time triggered it.
    After the fix, both pipelines produce sensible chi-square tests
    and incremental fit indices.

  o Fix (issue #51, jwe4ec): baseline.df was not adjusted for the
    case where the saturated reference model has fewer free
    parameters than nobs. The target df already had this adjustment;
    baseline.df now mirrors it. With the saturated-equal fix above
    this correction is usually zero, but it now also handles the
    case where the user supplies a custom non-fully-saturated
    saturated via @baseline_saturated$saturated. Affects the chi-
    square reference for the baseline test and any incremental fit
    indices that depend on baseline.df (CFI, TLI, NFI, RFI, IFI,
    RNI, NNFI).

Changes in Version 0.15.9
  o Performance: the expected Fisher information for Ising models is
    now computed entirely in C++ in a single 2^N state-enumeration pass
    (psychonetrics_FisherInformation_cpp -> expected_hessian_Ising_full_cpp).
    The previous path made an R callback to expected_hessian_Ising_group,
    which itself called IsingSampler::IsingLikelihood (one full state
    enumeration plus materialization of a 2^N x N states matrix) and
    then expHessianCpp (which made three more passes through all
    2^N states: expHcpp, expH2cpp, and the main moment-accumulation
    loop). The new single-pass implementation also fills only the
    canonical lower-tri shapes of the 3rd- and 4th-order moment
    tensors that the Hessian assembly actually reads, eliminating the
    O(2^N N^4) dense fill in favour of O(2^N N^4 / 8).

    Per-call benchmarks (multi-group Ising):
      N=10 G=2  : 11.8x
      N=12 G=2  : 21.2x
      N=14 G=2  : 28.2x
    End-to-end partialprune() at N=15 G=2 (sample 800/group) drops
    from ~700s (0.15.7) to ~43s. Output is bit-identical (max abs
    diff in the @information slot: 6.7e-16, machine epsilon).

    The R-level expected_hessian_Ising / expected_hessian_Ising_group
    functions and the Rcpp::export expHessianCpp are kept as-is for
    callers outside the cpp Fisher-information path.

Changes in Version 0.15.8
  o partialprune() gains an 'identify' argument (default TRUE) that is
    threaded through prune(), unionmodel(), intersectionmodel(),
    groupequal(), and groupfree(). Setting identify = FALSE prevents
    identify() from being called on the intermediate models, so user-
    fixed parameters (e.g. fixpar(\"beta\", 1, 1, value = 1) in a multi-
    group Ising) are not re-freed when omega equality constraints are
    introduced. Default behavior (identify = TRUE) is unchanged.

Changes in Version 0.15.7
  o Performance: partialprune() inner refits skip addMIs(). The
    repeat-loop only consumes the BIC and the joint score-test
    statistic mi_free_joint, so addMIs's three Fisher-information
    builds (type = normal / free / equal) are wasted on every
    iteration. mi_free_joint is repopulated directly via
    .equalityScoreTestInner(joint_only = TRUE) on each candidate fit
    so the next selection still has the data it needs.

    addSEs and addInformation are kept enabled because the post-loop
    prune() reads p-values (from addSEs) and @information (from
    addInformation) on the final curMod; disabling them changed the
    post-loop prune behavior on multi-group cases where the inner
    loop iterated more than once.

    For useMIs = "simple" we fall back to a full addMIs() call inside
    the loop so the legacy per-parameter mi_free column is still
    populated (the simple branch reads it).

    Final partialprune() output is unchanged: the user-facing returned
    model is produced by the regular post-loop runmodel() / prune()
    chain, which uses default flags and populates MIs / SEs / @information.

Changes in Version 0.15.6
  o Performance: addMIs() now skips the univariate score-test loop in
    .equalityScoreTestInner() (added in 0.15.4), which was computed on
    every runmodel() call but never consumed -- only the joint score-test
    statistic populates the parameter table. The univariate output is
    still produced on demand when equalityScoreTest() or MIs(type="free")
    are called. partialprune() no longer pays this cost on every inner
    refit. Speedup scales with the number of groups: ~1.2x for G=2,
    ~1.5x for G=3, ~2.5x for G=4 on a small Ising example. Final
    partialprune() output is bit-identical to 0.15.5.

Changes in Version 0.15.5
  o partialprune() gains a new argument release_model = c("pruned",
    "saturated"), defaulting to "pruned". This changes the alternative
    model used in the stepwise BIC comparison when deciding whether to
    release an equality constraint: with "pruned" the just-released
    parameter is immediately fixed to zero in any group where the
    initial per-group prune (Step 1) had already removed it, before
    refitting and comparing BIC. The equality-constrained model is
    therefore tested against the asymmetric structure suggested by the
    initial per-group prune rather than against a fully saturated
    per-group alternative, which improves specificity at low sample
    sizes. Set release_model = "saturated" to reproduce the 0.15.4
    behavior.

Changes in Version 0.15.4
  o New function equalityScoreTest() that computes the score (Lagrange
    multiplier) test for cross-group equality constraints in a multi-group
    model. Returns both per-group univariate score statistics and a joint
    multivariate score statistic (one per equality-constrained parameter,
    df = G - 1). With the default method = "jacobian" the test
    reproduces lavaan::lavTestScore exactly (to numerical precision)
    for both balanced and unbalanced multi-group samples. A faster
    method = "schur" alternative is available for complex models; it
    inverts only the smaller "currently free" block of the information
    matrix and matches lavaan in balanced samples.
  o The parameter table now carries three new columns: mi_free_joint,
    pmi_free_joint, and df_free_joint, populated whenever modification
    indices of type "free" are computed.
  o MIs() now prints two tables for type = "free": the existing summed
    per-group univariate statistics (header updated to "Top N summed
    equality-free modification indices") and a new "Top N joint
    equality-free modification indices" table based on the multivariate
    score test.
  o partialprune() gains a new argument useMIs = c("joint","simple"). The
    default "joint" ranks candidate equality releases by the joint score
    test (correctly accounting for cross-group covariance for G >= 3).
    Set useMIs = "simple" to reproduce the psychonetrics <= 0.15.3
    behavior of summing per-group univariate MIs. The two options are
    equivalent for two-group models.

Changes in Version 0.15.3
  o Fixed meta_ggm() crashing with "non-numeric argument to mathematical
    function" when using Vestimation = "per_study". The SRMR computation
    in addfit() assumed sigma was a numeric matrix, but for meta_varcov
    models with per-study estimation it is a list of per-study matrices.
    SRMR now gracefully returns NA for such models.
  o Fixed stepup() breaking equality constraints set by groupequal() in
    multi-group models (GitHub issue #50). stepup() previously relied on
    the @equal slot to detect cross-group equality constraints, but
    groupequal() never populated it, so stepup() always freed parameters
    for a single group - silently breaking the constraint. stepup() now
    infers equality constraints directly from the parameters table
    (shared non-zero par indices), which is the canonical representation.
    groupequal() and groupfree() also now maintain the @equal slot for
    backwards compatibility with model reconstruction code.
  o stepup(greedy = TRUE) now falls back to the non-greedy path when
    any searched matrix has cross-group equality constraints. The greedy
    branch previously bypassed freepar()/groupequal() and assigned unique
    par indices to each added parameter, which would silently break
    equality constraints. Single-group models and multi-group models
    without equality constraints take the fast greedy path as before.

Changes in Version 0.15.2
  o Fixed incorrect (negative) chi-square and fit indices for FIML
    estimation with high-dimensional panel data. By default, the saturated
    model is still estimated numerically (using nlminb for robustness), but
    if the optimizer fails (saturated LL < model LL), the saturated
    log-likelihood is now automatically computed analytically from
    pattern-specific sample statistics, following the approach used by
    lavaan and Mplus.
  o Added 'saturated' argument to runmodel() with options "default"
    (model-based with automatic analytical fallback), "analytic" (skip
    optimization, use analytical formula directly -- recommended for
    high-dimensional panel data), and "model" (force numerical optimization
    with no fallback).

Changes in Version 0.15.1
  o panelgvar() and panelvar() now accept both wide-format (design matrix)
    and long-format (character vector of variable names) data, matching the
    flexibility of the underlying dlvm1(). Previously these convenience
    wrappers only accepted wide-format data. For long-format usage, pass
    vars as a character vector and provide idvar (and optionally beepvar)
    via ... (GitHub issue #48).
  o Updated dlvm1 family documentation to reflect long-format support in
    panelgvar() and panelvar(), with examples.
  o Replaced all Rf_error() calls in C++ code with Rcpp::stop() for proper
    C++ stack unwinding and destructor calls, avoiding potential memory
    leaks (GitHub issue #49).

Changes in Version 0.15
  o The package has gone through a large overhaul in the 0.14.x series of
    package versions. The most important changes are listed below.
  o Major C++ optimization of the estimation pipeline, resulting in ~45%
    cumulative speedup. This includes direct parameter-to-matrix
    mapping, OptimWorkspace caching of constant data across iterations,
    cache-aware prepareModel to skip redundant computations, and the new
    LBFGS++ pure C++ optimizer.
  o Added ordinal data support and WLSMV estimation for the lvm framework.
    lvm() now accepts ordinal variables via the 'ordered' argument, with
    WLS, DWLS, and ULS estimators. The WLSMV scaled test statistic
    (Asparouhov & Muthen, 2010) provides scaled chi-square, scaled RMSEA,
    and scaled incremental fit indices matching lavaan output.
  o Restructured dlvm1() to accept both wide-format and long-format data,
    unifying the interface for panel data models. Deprecated ml_tsdlvm1(),
    ml_gvar(), ml_var(), and related wrappers in favor of dlvm1() and
    panelvar() / panelgvar().
  o Penalized maximum likelihood (PML) and penalized FIML (PFIML) estimation
    with automatic lambda selection via EBIC-based grid search. Includes
    beta_min thresholding, warm starts across the lambda path, and refit()
    for refitting parameters without regularization.
  o New meta_lvm() model family for single-stage meta-analytic latent variable
    modeling (extending the MAGNA framework to CFA/SEM), and new meta_var1()
    / meta_gvar() for meta-analytic VAR(1) network models pooling temporal
    and contemporaneous structures across studies.
  o New write_psychonetrics() function for exporting comprehensive
    Mplus/LISREL-style output to a text file, intended for supplementary
    materials in publications.
  o Standardized input arguments across all model families: new 'cors',
    'groupvar', and 'corinput' arguments with consistent validation via
    standardize_input(). Meta-analytic models now accept raw data input.
  o Added SRMR (Standardized Root Mean Square Residual) fit measure using
    Bentler (1995) standardization. Ising models now also compute
    model-implied means and covariances enabling SRMR computation.
  o Improved starting values: OLS-based beta starting values for SEM models,
    fixed the 'equal' argument in multi-group lvm() models.
  o New loop_psychonetrics() convenience function for parallel bootstrapping
    and simulations with automatic variable export and progress bars.
  o Changed default missing data handling to missing = "auto", automatically
    switching to FIML/PFIML when missing data is detected.
  o Automatic fallback to approximate standard errors when the Fisher
    information matrix cannot be inverted.
  o lvm() now accepts lambda = "full" to specify a full loading matrix where
    all cross-loadings are free. Requires the 'latents' argument to be
    specified. FA-based starting values use promax-rotated loadings directly
    (no permutation matching needed for full lambda).
  o penalize() now accepts a matrix for the 'lambda' argument, enabling
    per-element penalty specification: 0 = don't penalize, NA = auto-select
    via EBIC grid search, numeric > 0 = fixed penalty strength. Parameters
    that are fixed at zero in the model are automatically freed when a
    non-zero penalty is specified, combining freepar() and penalize() in
    one step. Works for both symmetric (e.g., sigma_epsilon) and
    non-symmetric (e.g., lambda) matrices.
  o Various bugfixes for Ising Fisher information, dlvm1 Jacobian
    construction, dplyr/ggplot2 deprecation warnings, and CRAN compliance.

Changes in Version 0.14.28
  o Ising models now compute model-implied means (mu) and covariances (sigma)
    from the exact Ising expectations after optimization. This enables SRMR
    and other residual-based fit measures for Ising models (previously returned
    NA). The computation uses the existing isingExpectation() C++ function:
    mu = E[X] and sigma = E[XX'] - E[X]E[X]'.

Changes in Version 0.14.27
  o Removed roptim-based C++ optimizers (cpp_L-BFGS-B, cpp_BFGS, cpp_CG,
    cpp_SANN, cpp_Nelder-Mead) from the public interface. These optimizers
    are no longer available via setoptimizer() or documented in help pages.
    Users should use "nlminb" (default), "ucminf", "nloptr_TNEWTON", or
    "LBFGS++" instead.
  o Replaced deprecated ggplot2::aes_string() calls with
    ggplot2::aes(.data[[]]) in CIplot() and ESA plot methods.
  o Fixed delta matrix starting values when using delta_* = "zero":
    diagonal elements fixed at zero now correctly show est = 0 in the
    parameter table instead of data-derived starting values.
  o Added SRMR (Standardized Root Mean Square Residual) fit measure.
    Uses Bentler (1995) standardization, matching lavaan's default
    srmr_bentler. Includes mean residuals when a mean structure is
    present. Under FIML, uses the saturated model's EM-estimated
    covariance as the observed statistic (matching lavaan's h1 approach).
    Multi-group models use an N-weighted average across groups.
  o The 'beta_min' argument in find_penalized_lambda() and runmodel() now
    accepts "numerical" (default, uses 1e-05), "theoretical"
    (uses sqrt(log(p)/n)), or a numeric value. The previous default was
    the theoretical formula; the new default is the fixed numerical threshold.
  o Added C++ pipeline for meta_var1/meta_gvar models. The C++ implementation
    (cpp=TRUE, the default) uses OptimWorkspace caching and
    formModelMatrices_direct for efficient parameter-to-matrix mapping.
  o C++ implied model (implied_meta_var1_cpp) computes BetaStar, Sigma0,
    Sigma1, and meta-analytic mu/sigma/kappa entirely in C++.
  o C++ derivatives (d_phi_theta_meta_var1_cpp) reuse existing var1 C++
    derivative helpers for the mean part and varcov helpers for the
    random effects variance part.
  o C++ prepare (prepare_meta_var1_cpp) follows the meta_lvm_prepare_cpp
    pattern with D_c override for the ML gradient.
  o Registered meta_var1 in all C++ dispatchers: prepareModel_cpp,
    impliedModel_cpp, psychonetrics_gradient_cpp_inner, and
    psychonetrics_FisherInformation_cpp_inner.

Changes in Version 0.14.26
  o New meta_var1() model family: meta-analytic VAR(1) model for pooling
    temporal and contemporaneous network structures across multiple studies.
    Uses a one-stage random effects framework. Accepts either raw time-series
    data (via data + studyvar) or pre-computed Toeplitz covariance matrices
    (via covs + nobs).
  o New meta_gvar() wrapper: convenience function that calls meta_var1()
    with contemporaneous = "ggm" for graphical VAR estimation.
  o meta_var1/meta_gvar model the stationary covariance (Sigma0) and lag-1
    cross-covariance (Sigma1) blocks, excluding the nuisance exogenous
    covariance block from the Toeplitz matrix.
  o Derivatives for meta_var1 reuse existing var1 Jacobian helpers
    (d_sigma0_beta_var1_cpp, etc.) for the mean part, and meta_lvm random
    effects Jacobians for the variance part.
  o R-only implementation (no C++ yet); model@cpp is set to FALSE.
  o If studyvar is not assigned but idvar is, studyvar is set to idvar
    with a warning. If both are assigned, idvar functions within each study
    for collating time series.

Changes in Version 0.14.24
  o Standardized input arguments across model families. All model constructors
    (varcov, lvm, var1, tsdlvm1, dlvm1, meta_varcov, meta_lvm) now accept a
    consistent set of input arguments:
    - New 'cors' argument: supply correlation matrices directly (with 'nobs').
      In varcov/meta_varcov, 'corinput' defaults to TRUE. In other families,
      correlations are treated as covariances with a warning.
    - New 'groupvar' argument: replaces the deprecated 'groups' argument. If both
      are supplied, 'groupvar' takes precedence with a deprecation warning.
    - New 'corinput' argument added to lvm, var1, tsdlvm1, and dlvm1 signatures
      (errors if set to TRUE, as correlation input is only supported in varcov
      and meta_varcov).
  o Meta-analytic models (meta_varcov, meta_lvm) now support raw data input via
    'data' + 'studyvar' arguments. Correlation/covariance matrices and sample
    sizes are computed internally per study.
  o meta_varcov() now also accepts covariance matrices via 'covs' argument.
  o meta_lvm() now also accepts correlation matrices via 'cors' argument.
  o Shared input validation consolidated into standardize_input() helper function,
    providing consistent error messages for mutual exclusion violations, missing
    required arguments, and unsupported combinations.
  o Fixed pre-existing bug in meta_varcov() baseline/saturated model construction
    where R's missing() function was passed as the 'missing' argument to varcov()
    instead of a string value.

Changes in Version 0.14.23
  o Strip debug symbols from compiled shared library via linker flags in Makevars,
    reducing installed libs/ size on CRAN macOS builds (from ~29MB to ~2MB).
  o Added C++ pipeline for meta_lvm (implied, prepare, derivatives), providing
    ~2.8x speedup over the R-only implementation.
  o Fixed meta_lvm.Rd documentation to match actual function signature.

Changes in Version 0.14.22
  o meta_lvm() now accepts covariance matrices (not just correlations). The data vector
    includes diagonal elements (variances) alongside off-diagonal covariances, with free
    sigma_epsilon diagonal. V matrix computation, implied model, and derivatives updated
    to handle the full vech (with diagonal).
  o Fixed checkJacobian() and checkFisher() to respect the model's @cpp slot: models
    with cpp=FALSE (e.g., meta_lvm) now correctly use R-only fit/gradient/Fisher functions
    instead of always defaulting to C++ versions.
  o Fixed R CMD check --as-cran issues:
    o panelvar()/panelgvar() now handle missing data argument correctly
    o dlvm1() guards against missing data in auto-detect and samplestats calls
    o penalty_lambda documentation updated to match code default (NA, not 0)
    o Removed stray browser() calls from var1 implied and identify functions
    o Fixed non-ASCII character in NA2020.Rd reference
  o Removed continuous-data corinput from lvm(): correlations passed as covs are now
    treated as covariances with free diagonal. Ordinal corinput retained internally.

Changes in Version 0.14.20
  o Added meta_lvm() function for single-stage meta-analytic latent variable modeling.
    Combines a latent variable model (CFA/SEM) for the mean structure of pooled
    covariance/correlation matrices with a random-effects model for between-study
    heterogeneity. Extends the MAGNA framework to latent variable models. Supports all
    standard parameterizations for latent (cov/chol/prec/ggm), residual (cov/chol/prec/ggm),
    and random effects (chol/cov/prec/ggm/cor) structures.

Changes in Version 0.14.18
  o Standard errors now auto-fallback to approximate SEs when the Fisher information matrix
    cannot be inverted (e.g., due to zero cells in crosstables in multi-group Ising models).
    Previously this returned NA standard errors; now approximate SEs are computed automatically
    with an informative warning

Changes in Version 0.14.17
  o Fixed the 'equal' argument in multi-group lvm() models:
    o The equal= argument (e.g., equal="lambda") now correctly constrains parameters across groups
    o Previously, the equality flag was passed as a positional argument to fixMatrix, causing it to
      be silently ignored
    o Additionally fixed corrupted starting values when equal= is used: the lambda structure matrix
      contains integer codes (>1) for equality constraints, but these were used as multipliers for
      FA-derived starting values, producing extreme values that prevented convergence
    o Both loadings and variance identification now work correctly with equal=
    o Results from equal= now match those from the groupequal() workflow
  o Added OLS-based starting values for the beta (structural) matrix in lvm()/sem():
    o For each endogenous latent, OLS regression on FA-implied latent covariance provides initial
      beta coefficients
    o Disturbance covariance (sigma_zeta) is adjusted via (I-B)*Cov(eta)*(I-B)' instead of using
      the raw FA factor correlations
    o Guard rails for near-singular covariance, ill-conditioned predictor blocks, extreme
      coefficients, and near-singular (I-B)
    o Falls back to previous zero-initialization on any numerical issue
    o New 'start' argument: "default" for OLS-based, "simple" for zero-initialization
  o Added loop_psychonetrics() convenience function for parallel bootstrapping and simulations:
    o Replaces boilerplate of makeCluster / clusterExport / parLapply / stopCluster
    o Variables referenced in the expression are automatically detected and exported to workers
    o Uses pbapply progress bars and safe cluster cleanup via on.exit
    o Works with aggregate_bootstraps() and CIplot() for bootstrap analysis
  o Added the NA2020 dataset: 8 self-report items (n=501) from Isvoranu (2020) for GGM examples

Changes in Version 0.14.16
  o Added automatic lambda selection for PML/PFIML estimation via EBIC-based grid search:
    o New function find_penalized_lambda() searches over a log-spaced grid of 50 lambda values
    o Lambda now defaults to NA in all model constructors, signaling automatic selection
    o runmodel() automatically triggers the grid search when NA lambdas are detected
    o Lambda_max computed analytically from KKT conditions at the null-penalized model
    o Warm starts across the lambda path for efficient computation
    o Supports criterion selection: "bic", "ebic.25", "ebic.5" (default), "ebic.75", "ebic1"
    o Users can still specify a fixed lambda (e.g., penalty_lambda = 0.1) for manual control
  o Added beta_min thresholding for penalized estimates:
    o Near-zero estimates below beta_min are set to exactly zero after convergence
    o Default threshold: sqrt(log(p)/n) where p = number of penalized parameters
    o Applied both during EBIC evaluation (for degree-of-freedom counting) and on the final model
    o Reduces numerical noise from ISTA optimizer, improving specificity
    o Exposed as user argument in runmodel() and find_penalized_lambda()
  o EBIC for penalized model selection uses unpenalized log-likelihood at penalized estimates
    (Convention A), with npar = unpenalized free params + nonzero penalized params after threshold
  o Print and write methods now display lambda search results (selected lambda, criterion value,
    beta_min) and show "auto (not yet selected)" for models with NA lambda before estimation
  o NA-safe comparisons throughout the codebase for penalty_lambda column (print, write, refit,
    penalize, unpenalize, C++ workspace builder)

Changes in Version 0.14.15
  o Added PFIML (Penalized Full Information Maximum Likelihood) estimator:
    o Combines FIML with L1/L2 penalty for penalized estimation with missing data
    o Available for all Gaussian models (varcov, lvm, var1, dlvm1, tsdlvm1)
    o Uses proximal gradient optimizer (same as PML)
    o refit() switches PFIML back to FIML for post-selection inference
  o Changed default missing data handling to missing = "auto" for all Gaussian models:
    o ML automatically switches to FIML when missing data is detected
    o PML automatically switches to PFIML when missing data is detected
    o LS estimators (ULS, WLS, DWLS) default to listwise deletion
    o Users can still override with explicit missing = "listwise" or missing = "pairwise"
    o No change for Ising models (missing = "listwise" default remains)
  o Penalty setup in model constructors now supports PFIML:
    o estimator = "PML" and estimator = "PFIML" both trigger penalty initialization
    o Baseline/saturated models use the appropriate unpenalized estimator (ML or FIML)
  o Added experimental feature warnings for features introduced in the 0.14.x branch:
    o PML, PFIML, LBFGS++ optimizer, ordinal data in lvm()/varcov(), WLSMV scaled test statistic
    o Warnings appear once per session and only while version < 0.15
    o Users are directed to report unexpected behavior to GitHub issues

Changes in Version 0.14.14
  o Unified dlvm1() to accept both wide-format and long-format data:
    o New 'datatype' argument with options "auto" (default), "wide", and "long"
    o Auto-detects format from 'vars': matrix = wide, character vector = long
    o New 'idvar' and 'beepvar' arguments for long-format data
    o Long-to-wide conversion handled internally (previously required ml_tsdlvm1)
  o Added 'standardize' argument to dlvm1() with options:
    o "none" (default): no standardization
    o "z": global z-scores per variable across all waves (does not impose stationarity)
    o "quantile": quantile normalization across all waves
    o "z_per_wave": independent z-scores per wave column (imposes stationarity)
  o Added panellvgvar() as the canonical wrapper for panel latent variable GVAR models
  o Deprecated functions (still work but emit warnings):
    o ml_tsdlvm1() - use dlvm1() with long-format data instead
    o ml_gvar() - use dlvm1() with within_latent="ggm", between_latent="ggm"
    o ml_var() - use dlvm1() with desired within_latent and between_latent
    o ml_ts_lvgvar() - use panellvgvar() instead
    o panel_lvgvar() - use panellvgvar() instead

Changes in Version 0.14.13
  o Added write_psychonetrics() function for exporting comprehensive model output to a text file:
    o Produces Mplus/LISREL-style output with logo, sample info, model specification, parameter
      estimates, fit measures, model matrices, modification indices, and logbook
    o Intended for sharing as supplementary materials to papers
    o Supports uncomputed models: shows starting values with clear warnings, omitting SEs, p-values,
      fit measures, and modification indices
    o Model matrices section shows only modeled matrices (not derived/implied quantities)
    o Matrix output uses 2-digit rounding with dots for fixed zeros
  o Fixed prune() error when no parameters to test in the requested matrix:
    o adjust_p_values() now correctly returns a numeric vector (not the S4 model object) when nTest == 0
  o Fixed backward compatibility for models saved without the WLS.Gamma slot:
    o Added .hasSlot() guard to prevent "no slot of name 'WLS.Gamma'" warnings

Changes in Version 0.14.12
  o Added WLSMV scaled test statistic (mean-and-variance adjusted chi-square) for WLS/DWLS/ULS estimators:
    o Implements the scaled-shifted test (Asparouhov & Muthen, 2010) matching lavaan's WLSMV output
    o New fit measures: chisq.scaled, df.scaled, pvalue.scaled, chisq.scaling.factor
    o Scaled incremental fit indices: cfi.scaled, tli.scaled, nfi.scaled, rfi.scaled, ifi.scaled, rni.scaled, nnfi.scaled
    o Scaled RMSEA: rmsea.scaled
    o Baseline model also receives scaled test statistic (baseline.chisq.scaled, etc.)
    o Added WLS.Gamma slot to store the full asymptotic covariance matrix (Gamma) per group
    o Added LS_Gamma() helper function for computing Gamma from raw data
    o Scaling factors match lavaan to 6+ decimal places for both ordinal and continuous data
    o Supports single-group and multi-group models
  o Changed the default estimator for ordinal data from WLS to DWLS, matching lavaan's convention
  o Added "WLSMV" as an estimator alias for "DWLS" in lvm() and varcov()
  o Fixed the DWLS weight matrix for ordinal data:
    o Previously used diag(Gamma^{-1}) (diagonal of the full inverse), which differs from standard
    o Now uses 1/diag(Gamma) (reciprocal of the diagonal), matching the lavaan convention
  o Fixed (n+1)/n scaling bug in ULS fit function, gradient, and expected Hessian (R and C++):
    o The weight matrix normalisation factor was (n+1)/n instead of 1, causing small systematic bias

Changes in Version 0.14.11
  o Added corinput support to the lvm framework:
    o lvm() now accepts a 'corinput' argument to specify that the input is a correlation matrix
    o When corinput=TRUE, residual variances (diagonal of sigma_epsilon) are derived from the constraint diag(sigma) = 1
    o Diagonal sigma_epsilon parameters are fixed and derived in the implied function, not freely estimated
    o Supports WLS, DWLS, and ULS estimators; defaults to WLS when corinput=TRUE
    o Variance identification is automatically enforced
    o Both R and C++ code paths updated (implied and derivatives)
  o Fixed a bug in the identity weight matrix dimension in samplestats for corinput/meanstructure combinations
  o Fixed a bug in lvm Jacobian row removal when both corinput=TRUE and meanstructure=FALSE for continuous data

Changes in Version 0.14.10
  o Added ordinal data support to the lvm framework (Muthen, 1984 three-stage estimator):
    o lvm() now accepts an 'ordered' argument (character vector or TRUE) to specify ordinal variables
    o Supports WLS, DWLS, and ULS estimators for ordinal CFA/SEM
    o Polychoric correlations and thresholds are estimated from ordinal data
    o Variance identification is automatically enforced for ordinal models
    o Both R and C++ code paths (implied, prepare, derivatives) handle ordinal/continuous seamlessly
  o Added nloptr_TNEWTON as a new optimizer, available via setoptimizer(x, "nloptr_TNEWTON"):
    o Uses NLopt's preconditioned truncated Newton with restarts (NLOPT_LD_TNEWTON_PRECOND_RESTART)
    o Builds a local quadratic model and solves the Newton system via preconditioned conjugate-gradient
    o Supports box constraints and analytic gradients
    o Added nloptr to Imports
  o Fixed CRAN NOTE: commented out stray std::cout in vendored LBFGS++ header (LineSearchMoreThuente.h)

Changes in Version 0.14.9
  o Tuned LBFGS++ convergence parameters for better performance on ill-conditioned models:
    o Switched primary convergence criterion from gradient norm to relative function change (delta = 1e-8), matching nlminb behavior
    o Increased L-BFGS history size from 6 to 10 for better Hessian approximation on larger models
    o Relaxed gradient tolerance (epsilon = 1e-5) to avoid unnecessary iterations on ill-conditioned FIML problems
    o ~4x faster on complex dlvm1/panel models (e.g., 8.6s -> 2.0s on 84-parameter panelgvar)
  o Removed unused variable declarations to eliminate all compiler warnings from psychonetrics C++ source

Changes in Version 0.14.8
  o Added LBFGS++ (LBFGSpp) as a new pure C++ optimizer, available via setoptimizer(x, "LBFGS++"):
    o Vendored header-only LBFGS++ library (MIT license) into inst/include/
    o Combined function+gradient evaluation exploits the prepareModel cache for efficiency
    o ~1.6x faster than the existing roptim-based cpp_L-BFGS-B optimizer on benchmark models
    o Added RcppEigen to LinkingTo for Eigen header support

Changes in Version 0.14.7
  o Fixed R CMD check --as-cran issues:
    o Guarded semPlot example with requireNamespace() since it is a Suggested package
    o Replaced defunct dplyr::summarize_each_() with summarize(across()) in lvm example
    o Added build artifacts and non-package files to .Rbuildignore

Changes in Version 0.14.6
  o Fixed Ising model Fisher information for the beta (inverse temperature) parameter:
    o Added missing beta factor in beta-threshold and beta-network cross-derivative blocks of the expected Hessian
    o Fixed expectedmodel() to replace sample sufficient statistics with model-implied expectations for Ising models

Changes in Version 0.14.5
  o Optimized dlvm1 Jacobian construction (d_phi_theta_dlvm1_group_cpp):
    o Added dense Kronecker helpers (kronecker_I_X_dense, kronecker_X_I_dense) to avoid slow sparse element-by-element insertion when the result is used as a dense matrix
    o Precompute the constant "between" Kronecker block once instead of recomputing it for every lag
    o Pre-extract all grouplist values before the loop to eliminate repeated string-based hash lookups
    o Fixed copy-paste bug: removed duplicate Jac.submat write for sigma_zeta_between in the lag loop

Changes in Version 0.14.4
  o Fixed dplyr 1.1.0 deprecation warning about one-column matrices in filter() calls

Changes in Version 0.14.3
  o Cache-aware prepareModel_cpp: skip redundant prepare when fit and gradient are called at the same parameter vector
  o ~24% speedup in full model fitting (e.g., 9-variable CFA: ~155ms -> ~118ms)

Changes in Version 0.14.2
  o Expanded OptimWorkspace to cache all constant S4 model/sample data (extramatrices, types, sample stats, etc.)
  o All prepare_*_cpp and implied_*_cpp_core functions now read from cached workspace instead of S4 slots
  o prepareModel_cpp post-processing now reads from workspace instead of S4 slots

Changes in Version 0.14.1
  o Performance optimization: replaced O(nMat * nPar) parameter-to-matrix mapping with O(nFreePar) direct index mapping
  o Eliminated S4 model cloning during optimization iterations by bypassing updateModel_cpp in all prepare functions
  o Refactored all 8 implied_*_cpp functions into _core variants that accept pre-formed matrices
  o All 8 prepare_*_cpp functions now use formModelMatrices_cpp_direct + implied_*_cpp_core pipeline
  o Added OptimWorkspace: static cache for MatrixMapping, M matrix, and parameter indices across optimization iterations
  o ~25% speedup in full model fitting (e.g., 9-variable CFA: ~200ms -> ~155ms)

Changes in Version 0.13.2
  o Fixed a bug in 'intersectionmodel' not properly clearing removed parameters
  o Improved starting values for identity model in partialprune
  o Fixed a bug in partialprune resulting in an uninformative error
  o Switch from deprecated arma::is_finite() to std::isfinite()

Changes in Version 0.13.1
  o Bootstrapping now occurs before standardizing the data (thanks to Yiwei Yang and Amanda Montoya)
  o Improved support for N=1 models in dlvm1 (e.g., for true ML estimation)
  o Fixed a bug with missing values in the beta starting values matrix for dlvm1 models
  o Changed partialprune() default for 'return' to "partialprune"; set to "best" to mimic old behavior
  o Added 'final_prune' argument to partialprune() and changed the default to "saturated"; set to "partialprune" to mimic old behavior

Changes in Version 0.13
  o Added bootstrap support:
    o Every model now supports 'bootstrap' (to enable bootstrap), 'boot_sub' (for subsampling) and 'boot_resample' (to resample)
    o Enabling these will bootstrap the data *once* and create a psychonetrics object for the bootstrap sample
    o The function 'aggregate_bootstraps' can be used to aggregate bootstraps:
      o use print(...) for some general info
      o use parameters(...) for the bootstrap results on the parameters
      o use fit(...) for the bootstrap results on the fit indices
      o use CIplot(...) for the bootstrap results on the confidence intervals (enable split0=TRUE if model selection is used)

Changes in Version 0.12
  o Updates for the dlvm1 model family:
    o Changed the starting values for dlvm1 models for better convergence
    o Added the 'start' argument to the dlvm1 model family. Use start = "version1" to use pre 0.12 start values
    o Added the 'baseline' argument to dlvm1 model family, which now defaults to "stationary_random_intercept". Note: this will change incremental fit indices! To recover old behavior, set baseline = "independence"
  o The log(det(kappa)) value used in fit function is now computed in FIML in the same way as computed in ML framework, this should lead to more stability, but might also lead to some different chi-square values
  o The startreduce argument in prune() now defaults to 1
  o Using "empty" for matrix specification is now deprecated; use "diag" or "zero" instead.
  o getmatrix will no longer evaluate a model if it is not computed (now returns start values)
  o the 'startreduce' argument in 'prune' now defaults to 1
  o Added the 'fixstart' function that can aide in fixing startvalues
  o The 'getmatrix' function now supports thresholded PDC matrices
  o Diagnostic functions 'checkJacobian' and 'checkFischer' are now exported
  o Added the transmod function, which can be used to transform one model type (e.g., using a Cholesky decomposition) to another model type (e.g., a GGM).
  o Improved support for models with only one (latent) variable
  o Added the 'logbook' function which can be used to more easily retrieve the psychonetrics logbook
  o The 'partialprune' function has been updated and now allows for pruning specific matrices as well as returning different models
  o Fixed some instances where the 'equal' argument wasn't working
  o 'partialprune' now supports multiple matrices
  o 'unionmodel' and 'intersectionmodel' now support the 'matrices' argument
  o Changed lambda starting value choice to the following: Run an EFA, set the starting values to the loadigs of the factor that has the strongest loadings in the indicators, and set correlations between factors that use the same EFA factor for starting values to 0.8
  o added warning for non computed model in parameters()

Changes in Version 0.11.5
  o Fixes to support Matrix 1.7-0
  o The groupequal, groupfree, fixpar and freepar functions can now handle multiple rows/columns for symmetric matrices
  o Fixed a bug in the identification function for ml_lvm models
  o Implemented min_sum argument for estimating Ising models on data with a minimum sum score due to selection bias. This method is further discussed in  Boot, de Ron, Haslbeck & Epskamp (in preperation). Correcting for selection bias after sum score selection in the Ising model.
  o The 'warn_improper' argument in runmodel now defaults to FALSE, omitting the warning that a non-positive defnite matrix was encountered in parameter estimation.
  o SEs are now set to NA if the Fischer information matrix could not be inverted normally
  o Added an argument 'approximate_SEs' to runmodel to allow for SEs to be approximated using a pseudoinverse

Changes in Version 0.11
  o Optimised lambda starting value choice
  o Changed warning for when a peusdoinverse was used in model estimation.
  o Added 'threshold' argument to the matrix(...) function for significance thresholding
  o Fixed a bug in psychonetrics:::emergencystart

Changes in Version 0.10.3
  o Added a 'bootstrap' argument to var1() family to facilitate bootstrapping the data
  o Fixed a bug where the saturated log-likelihood under FIML estimation could be incorrect

Changes in Version 0.10.2
  o Built in a check to find NA sample covs
  o added diag argument to getmatrix to obtain diagonal
  o diagonal elements are now also thresholded when using getmatrix(..., threshold = ...)
  o Added 'threshold' argument to getmatrix()
  o Adjusting p-values is now done by default only for the tested p-values. This means that the number of tests is assumed to be the number of parameters of interest (matrices that are to be pruned), not the total number of parameters. Previous behavior can be reproduced with mode = "all" in prune(..).
  o panelvar and panelgvar now support arguments within_latent and between_latent

Changes in Version 0.10.1
  o Fixed a bug in 'prune' which did not allow intercept/mean vectors to be pruned

Changes in Version 0.10
  o ml_tsdlvm1 now orders data by idvar if beepvar is not supplied.
  o Added 'CIplot' function
  o Standardizing in ts_dlvm1 is now more stable.
  o dlvm1 now uses observed variable names as latent variable names when appropriate.
  o Fixed a bug with the beepvar argument in psychonetrics

Changes in Version 0.9:
  o Changed 'rel.tol=1e-10' in the nlminb optimizer to be in line with lavaan
  o Added 'warn_improper' argument to runmodel(..)
  o The argument 'return_improper' now defaults to TRUE
  o Optimizer no longer uses a spectral shift when inverting a semi-positive definite matrix
  o Detection of non positive definite matrices is now done faster and should lead to less problems
  o Added the 'bounded' argument to runmodel() to define if bounded estimation should be used (defaults to TRUE)
  o Diagonals of symmetrical matrices can no longer be estimated to be negative (avoiding Heywood cases, although doing this with a Cholesky decomposition is nicer!)
  
Changes in Version 0.8.1:
  o The nlminb estimator now uses more iterations.

Changes in Version 0.8:
  o The log determinant is now computer better to be able to include fit measures at higher model complexities
  o The 'return_improper' argument in runmodel( ) now returns improper estimates without trying different starting values
  o Fixed a bug in meta_varcov when using individual estimates of the sampling variation
  o Updated optimizer default control parameters

Changes in version 0.7.6:
  o Model estimations that used improper estimation (pseudoinverse of variance-covariance matrix) now return with an error unless return_improper = TRUE in runmodel()
  o Equality-free MIs are now also computed when all edges are included

Changes in version 0.7.5:
  o Fixed a bug with nu_zeta being constrained in multigroup LGC models.
  o factorscores now supports multi group models

Changes in version 0.7.3:
  o Added the 'covariates_as' argument to latentgrowth() to model covariates with regressions (now default) or covariances

Changes in version 0.7.2:
  o Fixed a bug with removing diagonal elements of sigma_epsilon with single indicators
  o Fixed another bug with models with only one free indicator
  o Added the partialprune function for partially pruning multi-group models


Changes in version 0.7.1:
  o Fixed a bug for Solaris

Changes in version 0.7:
  o Major changes
    o Major restructuring of underlying core code. Most vital functions have been translated to C++, leading to a large speedup!
    o Added C++ based optimizers cpp_L-BFGS-B, cpp_CG, cpp_SANN, and cpp_Nelder. These are faster but slightly less stable than nlminb (now the default optimizer).
    o Verbose now defaults to FALSE everywhere. This can be set with the setverbose function for a model
    o Added meta-analysis model for varcov family (meta_varcov)
    o The ml_tsdlvm1 function now allows for multi-kevek tsdlvm1 models (dlvm1 models) to be specified using syntax familiar to those using mlVAR and graphicalVAR
  o Minor changes
    o Numerous small bugfixes and improvements (e.g., better starting values)
    o Added function 'fullFIML' to use true FIML computing the likelihood per row
    o Changed 'WLS.V' to 'WLS.W'
    o Several warning messages have been updated
  

Changes in version 0.6:
  o The 'dlvm1' model family now also returns the implied latent variance-covarriance matrix as 'sigma_eta_within'
      o The latent lag-1 matrix is also returned as 'sigma_eta_within_lag1'
  o The dlvm1 model family now no longer requires a 'lambda' matrix to be specified (will default to a panelvar model)
  o Most model families now support the 'standardized' argument, which can be set to 'z' for z-scores (helps convergence) or 'quantile' for a semiparametric transformation
  o Added the ml_lvm family for two-level random intercept latent variable models
  o Added the simplestructure function to easily make a lambda matrix
  o addalpha in modelsearch now works as expected
  o addalpha and prunealpha now default to 0.01 in 'modelsearch'
  o recursive now defaults to FALSE in 'prune'

Changes in version 0.5.1:
  o Fixed a bug with responses being missing when summary statistics are used in Ising()
  o Fixed a bug with covtype sometimes being set to UB when corinput = TRUE
  o Added DOI to description field

Changes in version 0.5.0:
  o The Ising model is now supported (only ML estimation) through the Ising() model function
  o Added the covtype option to several models, controlling if maximum likelihood or unbiased estimates are used for the input covariances
  o Added the function 'covML' for maximum likelihood covariance estimates

Changes in version 0.4.1:
  o Small fix for CRAN checks

Changes in version 0.4:
  o type = "cor" is now supported in varcov, with rho representing the correlations and SD the diagonal standard deviations matrix.
      o The 'corr' function is now implemented as shorthand for varcov(..., type = "cor")
  o The scale of the Fisher information matrix has been adjusted to portray unit information to be similar to Lavaan
  o The getVCOV function has been added to obtain the estimated asymptotic var-cov matrix of the parameters.
  o The meanstructure can now be ignored using meanstructure = TRUE in the following model families:
    - varcov
  o A correlation matrix can now be used as input (detected or set with corinput = TRUE) for the following families:
    - varcov (type = "ggm" and type = "cor")
  o The WLS estimator will now not investigate means when meanstructure is ignored, and variances when a correlaton matrix is used as input.
    - The WLS weights matrix must be of the appropriate dimensions!
    - The WLS.V matrix will no longer be adjusted for missing means.
    - Added 'startEPC' argument to stepup and freepar
  o Added the 'modelsearch' function for extensive stepwise model search
  o Fixed several bugs and improved starting values in several models

Changes in version 0.3.3:
  o prune() now removes diagonal values of temporal effects
  o psychonetrics now requires R 3.6
  o Some C++ fixes for Solaris
  
Changes in version 0.3.2:
	o The parameters function now invisibly returns the parameter estimate data frame
	o The MIs function now invisibly returns a data frame with MI estimates
	o fit now invisibly returns a data frame with fit measure estimates


Changes in version 0.3.1:
	o Several help-files are now updated with executable examples

Changes in version 0.3.0: 
  o First version to be submitted to CRAN