Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More sophisticated checks in restoreoptsum. #775

Merged
merged 11 commits into from
Jun 27, 2024
5 changes: 5 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
MixedModels v4.25.1 Release Notes
==============================
- Use more sophisticated checks on property names in `restoreoptsum` to allow for optsums saved by pre-v4.25 versions to be used with this version and later. [#775]

MixedModels v4.25 Release Notes
==============================
- Add type notations in `pwrss(::LinearMixedModel)` and `logdet(::LinearMixedModel)` to enhance type inference. [#773]
Expand Down Expand Up @@ -538,3 +542,4 @@ Package dependencies
[#769]: https://github.com/JuliaStats/MixedModels.jl/issues/769
[#772]: https://github.com/JuliaStats/MixedModels.jl/issues/772
[#773]: https://github.com/JuliaStats/MixedModels.jl/issues/773
[#775]: https://github.com/JuliaStats/MixedModels.jl/issues/775
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "MixedModels"
uuid = "ff71e718-51f3-5ec2-a782-8ffcbfa3c316"
author = ["Phillip Alday <[email protected]>", "Douglas Bates <[email protected]>", "Jose Bayoan Santiago Calderon <[email protected]>"]
version = "4.25.0"
version = "4.25.1"

[deps]
Arrow = "69666777-d1a9-59fb-9406-91d4454c9d45"
Expand Down
31 changes: 24 additions & 7 deletions src/serialization.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,24 @@ function restoreoptsum!(
) where {T}
dict = JSON3.read(io)
ops = m.optsum
okay =
(setdiff(propertynames(ops), keys(dict)) == [:lowerbd]) &&
all(ops.lowerbd .≤ dict.initial) &&
all(ops.lowerbd .≤ dict.final)
if !okay
allowed_missing = (
:lowerbd, # never saved, -Inf not allowed in JSON
:xtol_zero_abs, # added in v4.25.0
:ftol_zero_abs, # added in v4.25.0
:sigma, # added in v4.1.0
:fitlog, # added in v4.1.0
)
nmdiff = setdiff(
propertynames(ops), # names in freshly created optsum
union!(Set(keys(dict)), allowed_missing), # names in saved optsum plus those we allow to be missing
)
if !isempty(nmdiff)
throw(ArgumentError(string("optsum names: ", nmdiff, " not found in io")))
end
if length(setdiff(allowed_missing, keys(dict))) > 1 # 1 because :lowerbd
@warn "optsum was saved with an older version of MixedModels.jl: consider resaving."
end
if any(ops.lowerbd .> dict.initial) || any(ops.lowerbd .> dict.final)
throw(ArgumentError("initial or final parameters in io do not satisfy lowerbd"))
end
for fld in (:feval, :finitial, :fmin, :ftol_rel, :ftol_abs, :maxfeval, :nAGQ, :REML)
Expand All @@ -33,12 +46,16 @@ function restoreoptsum!(
end
ops.optimizer = Symbol(dict.optimizer)
ops.returnvalue = Symbol(dict.returnvalue)
# provides compatibility with fits saved before the introduction of fixed sigma
# compatibility with fits saved before the introduction of various extensions
for prop in [:xtol_zero_abs, :ftol_zero_abs]
fallback = getproperty(ops, prop)
setproperty!(ops, prop, get(dict, prop, fallback))
end
ops.sigma = get(dict, :sigma, nothing)
fitlog = get(dict, :fitlog, nothing)
ops.fitlog = if isnothing(fitlog)
# compat with fits saved before fitlog
[(ops.initial, ops.finitial, ops.final, ops.fmin)]
[(ops.initial, ops.finitial), (ops.final, ops.fmin)]
else
[(convert(Vector{T}, first(entry)), T(last(entry))) for entry in fitlog]
end
Expand Down
100 changes: 100 additions & 0 deletions test/pls.jl
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,106 @@ end
@test loglikelihood(fm) ≈ loglikelihood(m)
@test bic(fm) ≈ bic(m)
@test coef(fm) ≈ coef(m)

# check restoreoptsum from older versions
m = LinearMixedModel(
@formula(reaction ~ 1 + days + (1 + days | subj)),
MixedModels.dataset(:sleepstudy),
)
iob = IOBuffer(
"""
{
"initial":[1.0,0.0,1.0],
"finitial":1784.642296192436,
"ftol_rel":1.0e-12,
"ftol_abs":1.0e-8,
"xtol_rel":0.0,
"xtol_abs":[1.0e-10,1.0e-10,1.0e-10],
"initial_step":[0.75,1.0,0.75],
"maxfeval":-1,
"maxtime":-1.0,
"feval":57,
"final":[0.9292213195402981,0.01816837807519162,0.22264487477788353],
"fmin":1751.9393444646712,
"optimizer":"LN_BOBYQA",
"returnvalue":"FTOL_REACHED",
"nAGQ":1,
"REML":false
}
"""
)
@test_logs((:warn,
r"optsum was saved with an older version of MixedModels.jl: consider resaving"),
restoreoptsum!(m, seekstart(iob)))
@test loglikelihood(fm) ≈ loglikelihood(m)
@test bic(fm) ≈ bic(m)
@test coef(fm) ≈ coef(m)
iob = IOBuffer(
"""
{
"initial":[1.0,0.0,1.0],
"finitial":1784.642296192436,
"ftol_rel":1.0e-12,
"xtol_rel":0.0,
"xtol_abs":[1.0e-10,1.0e-10,1.0e-10],
"initial_step":[0.75,1.0,0.75],
"maxfeval":-1,
"maxtime":-1.0,
"feval":57,
"final":[0.9292213195402981,0.01816837807519162,0.22264487477788353],
"fmin":1751.9393444646712,
"optimizer":"LN_BOBYQA",
"returnvalue":"FTOL_REACHED",
"nAGQ":1,
"REML":false,
"sigma":null,
"fitlog":[[[1.0,0.0,1.0],1784.642296192436]]
}
"""
)
@test_throws(ArgumentError("optsum names: [:ftol_abs] not found in io"),
restoreoptsum!(m, seekstart(iob)))

iob = IOBuffer(
"""
{
"initial":[1.0,0.0,1.0],
"finitial":1784.642296192436,
"ftol_rel":1.0e-12,
"ftol_abs":1.0e-8,
"xtol_rel":0.0,
"xtol_abs":[1.0e-10,1.0e-10,1.0e-10],
"initial_step":[0.75,1.0,0.75],
"maxfeval":-1,
"maxtime":-1.0,
"feval":57,
"final":[-0.9292213195402981,0.01816837807519162,0.22264487477788353],
"fmin":1751.9393444646712,
"optimizer":"LN_BOBYQA",
"returnvalue":"FTOL_REACHED",
"nAGQ":1,
"REML":false,
"sigma":null,
"fitlog":[[[1.0,0.0,1.0],1784.642296192436]]
}
"""
)
@test_throws(ArgumentError("initial or final parameters in io do not satisfy lowerbd"),
restoreoptsum!(m, seekstart(iob)))

# make sure new fields are correctly restored
mktemp() do path, io
m = deepcopy(last(models(:sleepstudy)))
m.optsum.xtol_zero_abs = 0.5
m.optsum.ftol_zero_abs = 0.5
saveoptsum(io, m)
m.optsum.xtol_zero_abs = 1.0
m.optsum.ftol_zero_abs = 1.0
restoreoptsum!(m, seekstart(io))
@test m.optsum.xtol_zero_abs == 0.5
@test m.optsum.ftol_zero_abs == 0.5
end

end

@testset "profile" begin
Expand Down
Loading