Hi all!
I’m excited to share AccessibleModels.jl – the easiest way to fit/optimize models with parameters being arbitrary Julia objects and create quick UIs for those objects.
I highlighted AccessibleModels.jl in my Accessors talk at this year JuliaCon – see the relevant part here:
Motivation and Design 
When fitting models, we often have parameters naturally represented as complex Julia objects (structs, nested data, etc), but optimization/sampling packages expect flat parameter vectors. This leads to lots of boilerplate code for parameter extraction and reconstruction.
The same applies to interactive UIs: it’s often natural to represent multiple relevant parameters as a single object, but UI libraries tend to work with individual independent controls or vectors.
AccessibleModels.jl uses Accessors.jl to automatically handle parameter management, letting you optimize/sample any Julia object directly. The exact same model and parameter definitions can be used for both model fitting and quick UIs!
Key features:
- Universal: Works with basically any Julia struct, flat or nested
- Wide ecosystem support: Works with Optimization.jl, Pigeons.jl (MCMC), and more
- Zero boilerplate: Just define your model - no parameter extraction/reconstruction code
- Bonus
: Can create instant interactive UIs for any AccessibleModels model using Makie!
Usage 
Define your model object as an arbitrary struct (no dependency on AccessibleModels required at this stage):
julia> struct ExpFunction{A,B}
scale::A
shift::B
end
julia> struct SumFunction{T}
comps::T
end
julia> (m::ExpFunction)(x) = m.scale * exp(-(x - m.shift)^2)
julia> (m::SumFunction)(x) = sum(c -> c(x), m.comps)
Create an interactive Makie UI for adjusting model parameters:
julia> using AccessibleModels, IntervalSets
julia> using GLMakie
julia> mod0 = SumFunction((
ExpFunction(1., 1.),
ExpFunction(2., 2.),
))
julia> amodel = AccessibleModel(mod0, (
(@o _.comps[∗].shift) => 0..10,
(@o _.comps[∗].scale) => 0..4,
))
julia> obj, = SliderGrid(fig[1,1], amodel)
julia> lines(fig[1,2], 0..10, @lift x -> $obj(x))
Use the same AccessibleModel for optimization by adding a loss function:
# Generate example data using a "true" model
julia> data = [(x=x, y=true_model(x) + 0.2 * randn()) for x in 0:0.5:10]
21-element Vector{@NamedTuple{x::Float64, y::Float64}}:
(x = 0.0, y = 0.158)
(x = 0.5, y = -0.172)
(x = 1.0, y = -0.138)
<...>
julia> loglike(m::SumFunction, data) = sum(r -> logpdf(Normal(m(r.x), 0.3), r.y), data)
# The only change: add the log-likelihood function
julia> amodel = AccessibleModel(Base.Fix2(loglike, data), mod0, (
(@o _.comps[∗].shift) => 0..10,
(@o _.comps[∗].scale) => 0..4,
))
julia> using Optimization, OptimizationMetaheuristics
julia> op = OptimizationProblem(amodel)
julia> sol = solve(op, ECA(), amodel)
# Get the fitted model:
julia> getobj(sol)
SumFunction((
ExpFunction(1.983, 3.098),
ExpFunction(1.574, 7.013)))
The same model definition works for MCMC sampling. This is especially convenient with MonteCarloMeasurements to hold the results:
julia> using Pigeons
julia> pt = pigeons(target=amodel, record=[traces; round_trip; record_default()])
julia> using MonteCarloMeasurements
julia> mcmc_fitted = samples(Particles, pt)
SumFunction((ExpFunction(1.5 ± 0.5, 5.35 ± 2.0), ExpFunction(1.6 ± 0.6, 4.66 ± 2.0)))
julia> lines(0..10, x -> mcmc_fitted(x))
julia> band!(0..10, x -> mcmc_fitted(x))
More examples and explanations in the docs.
More 
This package is a thin layer of plumbing that builds heavily on the Accessors.jl and AccessorsExtra.jl functionality. See the docs and code for more details.
Related works:
- AccessibleOptimization.jl: same concept, but Optimization.jl-only; effectively deprecated, all functionality is available in AccessibleModels.jl with a more unified design.
- PlutoTables.jl: same concept, but for Pluto notebook UIs. Currently, AccessibleModels.jl supports Makie UIs, Pluto backend can be added in the future.
Would love to hear your thoughts and feedback!
AccessibleModels.jl is pending registration in General.