diff --git a/Project.toml b/Project.toml index af66226..0520c19 100644 --- a/Project.toml +++ b/Project.toml @@ -13,6 +13,15 @@ Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" StatsFuns = "4c63d2b9-4356-54db-8cca-17b64c39e42c" +[weakdeps] +Optim = "429524aa-4258-5aef-a3af-852621145aeb" + +[extras] +Optim = "429524aa-4258-5aef-a3af-852621145aeb" + +[extensions] +DistributionFitsOptimExt = "Optim" + [compat] Distributions = "0.25" FillArrays = "0.12, 0.13" @@ -22,3 +31,5 @@ StaticArrays = "1.2" StatsBase = "0.33" StatsFuns = "0.9, 1" julia = "1.6" +Optim = "1.7" + diff --git a/ext/DistributionFitsOptimExt.jl b/ext/DistributionFitsOptimExt.jl new file mode 100644 index 0000000..84c1217 --- /dev/null +++ b/ext/DistributionFitsOptimExt.jl @@ -0,0 +1,19 @@ +module DistributionFitsOptimExt + +isdefined(Base, :get_extension) ? (using Optim) : (using ..Optim) +using DistributionFits: DistributionFits, optimize, AbstractDistributionFitOptimizer + +struct OptimOptimizer <: AbstractDistributionFitOptimizer; end + +function DistributionFits.optimize(f, ::OptimOptimizer, lower, upper) + result = Optim.optimize(f, lower, upper) + (;minimizer = result.minimizer, converged = result.converged, result) +end + +function __init__() + @info "DistributionFits: setting OptimOptimizer" + #DistributionFits.set_optimizer(DistributionFitsOptimExt.OptimOptimizer()) + DistributionFits.set_optimizer(OptimOptimizer()) +end + +end \ No newline at end of file diff --git a/src/DistributionFits.jl b/src/DistributionFits.jl index 2359c8f..34e288b 100644 --- a/src/DistributionFits.jl +++ b/src/DistributionFits.jl @@ -5,7 +5,10 @@ using Reexport using FillArrays, StaticArrays using StatsFuns: logit, logistic, normcdf -using Requires: @require + +if !isdefined(Base, :get_extension) + using Requires +end # for extension import Distributions: mean, var, mode @@ -21,6 +24,11 @@ export @qs_cf90, @qs_cf95, fit_mean_relerror +# document but do not export - need to qualify by 'DistributionFits.' +# export +# # Optimizer detail +# optimize, set_optimizer + # LogNormal export AbstractΣstar, Σstar, σstar @@ -30,11 +38,13 @@ export fit_mode_flat, shifloNormal # dependency inversion: need to define DistributionFits.optimize by user export AbstractDistributionFitOptimizer, optimize - include("optimizer.jl") + function __init__() - @require Optim="429524aa-4258-5aef-a3af-852621145aeb" include("requires_optim.jl") + @static if !isdefined(Base, :get_extension) + @require Optim="429524aa-4258-5aef-a3af-852621145aeb" include("../ext/DistributionFitsOptimExt.jl") + end end # fitting distributions to stats diff --git a/src/optimizer.jl b/src/optimizer.jl index a899c02..1a52a38 100644 --- a/src/optimizer.jl +++ b/src/optimizer.jl @@ -11,7 +11,8 @@ function `f` on bounded interval `[lower,upper]`: Returning an object with fields `minimizer` and `converged`. `DistributionFits.set_optimizer(::AbstractDistributionFitOptimizer)` -sets the optimizer used in calling the optimize function. +sets the specific AbstractDistributionFitOptimizer that is used +throughout the package for calling the optimize function. Specializing this function with a concrete type, allows using different optimization packages. @@ -20,21 +21,25 @@ package is in scope, the `OptimOptimizer` is set, which implements the interface by using Optim's [optimize](https://julianlsolvers.github.io/Optim.jl/stable/#user/minimization/#minimizing-a-univariate-function-on-a-bounded-interval) function. -Hence, the module using DistribuitonFits.jl has to either +Hence, the module using `DistributionFits.jl` has to either - explicitly invoke `using Optim`, or - call `set_optimizer` with the concrete subtype of `AbstractDistributionFitOptimizer` - for which the corresponding optimize method is implemented. + for which the corresponding `optimize` method is implemented. + +Developers implementing usage of a different specific optimizer see code in +ext/DistributionFitsOptimExt. """ abstract type AbstractDistributionFitOptimizer end -struct OptimOptimizer <: AbstractDistributionFitOptimizer; end struct NotSetOptimizer <: AbstractDistributionFitOptimizer; end +# see to ext/DistributionFitsOptimExt.jl optimize(f, ::NotSetOptimizer, lower, upper) = error( "Optimizer not set yet. Either invoke 'using Optim' or 'DistributionFits.set_optimizer(...)'.") -optimizer = NotSetOptimizer(); -optimize(f, lower, upper) = optimize(f, optimizer, lower, upper) -set_optimizer(opt::AbstractDistributionFitOptimizer) = (global optimizer = opt) +df_optimizer = NotSetOptimizer(); +optimize(f, lower, upper) = optimize(f, df_optimizer, lower, upper) +set_optimizer(opt::AbstractDistributionFitOptimizer) = (global df_optimizer = opt) + diff --git a/src/requires_optim.jl b/src/requires_optim.jl deleted file mode 100644 index ec28fe3..0000000 --- a/src/requires_optim.jl +++ /dev/null @@ -1,7 +0,0 @@ -function optimize(f, ::OptimOptimizer, lower, upper) - result = Optim.optimize(f, lower, upper) - (;minimizer = result.minimizer, converged = result.converged, result) -end - -@info "DistributionFits: setting OptimOptimizer" -DistributionFits.set_optimizer(OptimOptimizer()) diff --git a/test/runtests.jl b/test/runtests.jl index 46f2db8..7d868fe 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,16 +3,20 @@ using Test using Random: Random @testset "optimize error" begin - @test_throws Exception DistributionFits.optimize(x -> x*x, DistributionFits.optimizer, -1, 1) + @test_throws Exception DistributionFits.optimize(x -> x*x, -1, 1) end # Optim package for interactive testing i_loadlibs = () -> begin push!(LOAD_PATH, expanduser("~/julia/scimltools/")) # access local package repo + push!(LOAD_PATH, expanduser("~/julia/18_tools/scimltools/")) # access local package repo end using Optim: Optim, optimize + +DistributionFitsOptimExt = isdefined(Base, :get_extension) ? Base.get_extension(DistributionFits, :DistributionFitsOptimExt) : DistributionFits.DistributionFitsOptimExt + @testset "optimize set in __init__ after using Optim" begin # set in __init__ - @test DistributionFits.optimizer isa DistributionFits.OptimOptimizer + @test DistributionFits.df_optimizer isa DistributionFitsOptimExt.OptimOptimizer end const tests = [