Skip to content
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "StructuralEquationModels"
uuid = "383ca8c5-e4ff-4104-b0a9-f7b279deed53"
authors = ["Maximilian Ernst", "Aaron Peikert"]
version = "0.5.0"
version = "0.5.1"

[deps]
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
Expand All @@ -27,7 +27,7 @@ Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7"
SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b"

[compat]
julia = "1.10, 1.11, 1.12"
julia = "1.10, 1.11, 1.12, 1.13"
StenoGraphs = "0.5"
DataFrames = "1"
Distributions = "0.25"
Expand Down
3 changes: 3 additions & 0 deletions src/additional_functions/simulation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ Distributions.rand(loss::SemLoss, params, n::Integer) = rand(SEM.implied(loss),

Distributions.rand(model::Sem, params, n::Integer) = rand(sem_term(model), params, n)

Distributions.rand(wrapper::SemFiniteDiff, params, n::Integer) =
rand(wrapper.model, params, n)

# rand() overloads without SEM params
Distributions.rand(implied::Union{SemImplied, SemLoss, Sem}, n::Integer) =
Distributions.rand(implied, nothing, n)
2 changes: 1 addition & 1 deletion src/frontend/fit/fitmeasures/fit_measures.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const DEFAULT_FIT_MEASURES = [AIC, BIC, dof, χ², p_value, nparams, RMSEA, CFI]
const DEFAULT_FIT_MEASURES = [minus2ll, AIC, BIC, dof, χ², p_value, nparams, RMSEA, CFI]

fit_measures(fit, measures::AbstractVector) = Dict(Symbol(fn) => fn(fit) for fn in measures)
fit_measures(fit, measures...) = fit_measures(fit, measures)
Expand Down
23 changes: 10 additions & 13 deletions src/frontend/fit/standard_errors/hessian.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,19 @@ function se_hessian(fit::SemFit; method = :finitediff)
return [sqrt(c * H_inv[i]) for i in diagind(H_inv)]
end

# Addition functions -------------------------------------------------------------
H_scaling(loss::SemML) = 2 / (nsamples(loss) - 1)
# Additional functions -------------------------------------------------------------

function H_scaling(loss::SemWLS)
@warn "Standard errors for WLS are only correct if a GLS weight matrix (the default) is used."
return 2 / (nsamples(loss) - 1)
end

H_scaling(loss::SemFIML) = 2 / nsamples(loss)
H_nsamples(loss::SemML) = nsamples(loss) - 1
H_nsamples(loss::SemWLS) = nsamples(loss) - 1
H_nsamples(loss::SemFIML) = nsamples(loss)
H_nsamples(loss::SemLoss) = nsamples(loss)
H_nsamples(wrapper::SemLossFiniteDiff) = H_nsamples(_unwrap(wrapper))

function H_scaling(model::AbstractSem)
semterms = SEM.sem_terms(model)
if length(semterms) > 1
#@warn "Hessian scaling for multiple loss functions is not implemented yet"
return 2 / nsamples(model)
else
return length(semterms) >= 1 ? H_scaling(loss(semterms[1])) : 1.0
isempty(semterms) && return 1.0
if any(term -> _unwrap(loss(term)) isa SemWLS, semterms)
@warn "Standard errors for WLS are only correct if a GLS weight matrix (the default) is used."
end
return 2 / sum(term -> H_nsamples(loss(term)), semterms)
end
2 changes: 1 addition & 1 deletion src/frontend/specification/ParameterTable.jl
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ function update_partable!(

for (i, par) in enumerate(partable.columns[:label])
if par == :const
coldata[i] = !isnothing(default) ? (isvec_def ? default[i] : default) : zero(T)
coldata[i] = !isnothing(default) ? (isvec_def ? default[i] : default) : T(NaN)
elseif haskey(params, par)
coldata[i] = params[par]
else
Expand Down
16 changes: 10 additions & 6 deletions test/examples/helper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ fitmeasure_semjl_to_lavaan = Dict(
:nparams => "npar",
:RMSEA => "rmsea",
:CFI => "cfi",
:minus2ll => "logl",
)

function test_fitmeasures(
Expand All @@ -81,11 +82,16 @@ function test_fitmeasures(
@test ismissing(measure)
else
measure_lav = measures_lav.x[lav_ix]
measure_lav = name == :minus2ll ? -2measure_lav : measure_lav
@test measure ≈ measure_lav rtol = rtol atol = atol
end
end
end

# LinearAlgebra v1.13 ignores`norm` keyword for isapprox on arrays (issue #1675)
isapprox_infnorm(x::AbstractArray, y::AbstractArray; atol::Real = 0, rtol::Real = 0) =
norm(x - y, Inf) <= max(atol, rtol * max(norm(x, Inf), norm(y, Inf)))

function test_estimates(
partable::ParameterTable,
partable_lav;
Expand All @@ -103,10 +109,9 @@ function test_estimates(
@test !any(isnan, expected)

if skip # workaround skip=false not supported in earlier versions
@test actual ≈ expected rtol = rtol atol = atol norm = Base.Fix2(norm, Inf) skip =
skip
@test isapprox_infnorm(actual, expected; atol = atol, rtol = rtol) skip = skip
else
@test actual ≈ expected rtol = rtol atol = atol norm = Base.Fix2(norm, Inf)
@test isapprox_infnorm(actual, expected; atol = atol, rtol = rtol)
end
end

Expand Down Expand Up @@ -136,10 +141,9 @@ function test_estimates(
@test !any(isnan, expected)

if skip # workaround skip=false not supported in earlier versions
@test actual ≈ expected rtol = rtol atol = atol norm = Base.Fix2(norm, Inf) skip =
skip
@test isapprox_infnorm(actual, expected; atol = atol, rtol = rtol) skip = skip
else
@test actual ≈ expected rtol = rtol atol = atol norm = Base.Fix2(norm, Inf)
@test isapprox_infnorm(actual, expected; atol = atol, rtol = rtol)
end
end

Expand Down
4 changes: 2 additions & 2 deletions test/examples/multigroup/build_models.jl
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ end
test_estimates(
partable,
solution_lav[:parameter_estimates_ml];
atol = 1e-3,
atol = 1e-4,
col = :se,
lav_col = :se,
lav_groups = Dict(:Pasteur => 1, :Grant_White => 2),
Expand Down Expand Up @@ -118,7 +118,7 @@ end
test_estimates(
partable_s,
solution_lav[:parameter_estimates_ml];
atol = 1e-3,
atol = 1e-4,
col = :se,
lav_col = :se,
lav_groups = Dict(:Pasteur => 1, :Grant_White => 2),
Expand Down
9 changes: 7 additions & 2 deletions test/examples/political_democracy/constructor.jl
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,13 @@ if opt_engine == :Optim
end

@testset "ml_solution_hessian" begin
solution = fit(SemOptimizer(engine = :Optim, algorithm = Newton()), model_ml)

solution = fit(
SemOptimizer(
engine = :Optim,
algorithm = Newton(linesearch = BackTracking(order = 3)),
),
model_ml,
)
update_estimate!(partable, solution)
test_estimates(partable, solution_lav[:parameter_estimates_ml]; atol = 1e-2)
end
Expand Down
Loading