Custom implied types

We recommend to first read the part Custom loss functions, as the overall implementation is the same and we will describe it here more briefly.

Implied types are of subtype SemImplied. To implement your own implied type, you should define a struct

struct MyImplied <: SemImplied
    ...
end

and a method to update!:

import StructuralEquationModels: objective!

function update!(targets::EvaluationTargets, implied::MyImplied, model::AbstractSemSingle, params)

    if is_objective_required(targets)
        ...
    end

    if is_gradient_required(targets)
        ...
    end
    if is_hessian_required(targets)
        ...
    end

end

As you can see, update gets passed as a first argument targets, which is telling us whether the objective value, gradient, and/or hessian are needed. We can then use the functions is_..._required and conditional on what the optimizer needs, we can compute and store things we want to make available to the loss functions. For example, as we have seen in Second example - maximum likelihood, the RAM implied type computes the model-implied covariance matrix and makes it available via implied.Σ.

Just as described in Custom loss functions, you may define a constructor. Typically, this will depend on the specification = ... argument that can be a ParameterTable or a RAMMatrices object.

We implement an ImpliedEmpty type in our package that does nothing but serving as an implied field in case you are using a loss function that does not need any implied type at all. You may use it as a template for defining your own implied type, as it also shows how to handle the specification objects:

############################################################################################
### Types
############################################################################################
"""
Empty placeholder for models that don't need an implied part.
(For example, models that only regularize parameters.)

# Constructor

    ImpliedEmpty(;specification, kwargs...)

# Arguments
- `specification`: either a `RAMMatrices` or `ParameterTable` object

# Examples
A multigroup model with ridge regularization could be specified as a `SemEnsemble` with one
model per group and an additional model with `ImpliedEmpty` and `SemRidge` for the regularization part.

# Extended help

## Interfaces
- `params(::RAMSymbolic) `-> Vector of parameter labels
- `nparams(::RAMSymbolic)` -> Number of parameters

## Implementation
Subtype of `SemImplied`.
"""
struct ImpliedEmpty{A, B, C} <: SemImplied
    hessianeval::A
    meanstruct::B
    ram_matrices::C
end

############################################################################################
### Constructors
############################################################################################

function ImpliedEmpty(;specification, meanstruct = NoMeanStruct(), hessianeval = ExactHessian(), kwargs...)
    return ImpliedEmpty(hessianeval, meanstruct, convert(RAMMatrices, specification))
end

############################################################################################
### methods
############################################################################################

update!(targets::EvaluationTargets, implied::ImpliedEmpty, par, model) = nothing

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

update_observed(implied::ImpliedEmpty, observed::SemObserved; kwargs...) = implied

As you see, similar to Custom loss functions we implement a method for update_observed.