Custom optimizer types

The optimizer part of a model connects it to the optimization backend. The first part of the implementation is very similar to loss functions, so we just show the implementation of SemOptimizerOptim here as a reference:

############################################################################
### Types and Constructor
############################################################################

mutable struct SemOptimizerOptim{A, B} <: SemOptimizer
    algorithm::A
    options::B
end

function SemOptimizerOptim(;
        algorithm = LBFGS(), 
        options = Optim.Options(;f_tol = 1e-10, x_tol = 1.5e-8), 
        kwargs...)
    return SemOptimizerOptim(algorithm, options)
end

############################################################################
### Recommended methods
############################################################################

update_observed(optimizer::SemOptimizerOptim, observed::SemObserved; kwargs...) = optimizer

############################################################################
### additional methods
############################################################################

algorithm(optimizer::SemOptimizerOptim) = optimizer.algorithm
options(optimizer::SemOptimizerOptim) = optimizer.options

Now comes a part that is a little bit more complicated: We need to write methods for sem_fit:

function sem_fit(
        model::AbstractSemSingle{O, I, L, D}; 
        start_val = start_val, 
        kwargs...) where {O, I, L, D <: SemOptimizerOptim}
    
    if !isa(start_val, Vector)
        start_val = start_val(model; kwargs...)
    end

    optimization_result = ...

    ...

    return SemFit(minimum, minimizer, start_val, model, optimization_result)
end

The method has to return a SemFit object that consists of the minimum of the objective at the solution, the minimizer (aka parameter estimates), the starting values, the model and the optimization result (which may be anything you desire for your specific backend).

If we want our type to also work with SemEnsemble models, we also have to provide a method for that:

function sem_fit(
        model::SemEnsemble{N, T , V, D, S}; 
        start_val = start_val, 
        kwargs...) where {N, T, V, D <: SemOptimizerOptim, S}

    if !isa(start_val, Vector)
        start_val = start_val(model; kwargs...)
    end


    optimization_result = ...

    ...

    return SemFit(minimum, minimizer, start_val, model, optimization_result)

end

In addition, you might want to provide methods to access properties of your optimization result:

optimizer(res::MyOptimizationResult) = ...
n_iterations(res::MyOptimizationResult) = ...
convergence(res::MyOptimizationResult) = ...