-
Notifications
You must be signed in to change notification settings - Fork 6
Refactor SemOptimizer API #299
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
Merged
Maximilian-Stefan-Ernst
merged 28 commits into
StructuralEquationModels:devel
from
alyst:refactor_semopt_api
Feb 8, 2026
Merged
Changes from all commits
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
453fcc9
Revert "fix Proximal extension"
alyst abc2847
Revert "fix NLopt extension"
alyst 56cdef1
Revert "fix exporting structs from package extensions"
alyst 421927e
types.jl: move SemOptimizer API into abstract.jl
alyst 84bd7bd
NLoptResult should not be mutable
alyst 930e0e5
SemNLOpt: use f or f => tol pair for constraints
alyst 230af39
NLopt: cleanup docstring
d1355a0
NLopt: update/simplify docs
6f3ccd5
Optim.md: SemOptimizerOptim => SemOptimizer
alyst 242c602
regulariz.md: SemOptimProx => SemOptimizer
alyst 3e5c9ac
optimizer_engine(): rename and fix signature
9471fbe
optimizer_engines(): new method
f4f9280
export optmizer_engine()
41c1308
sem_optimizer_subtype(engine)
a1cf400
streamline engine error throwing
efd4911
SemOptimizer{E}: remove docstring
3410853
SemOptimizer: cleanup docstrings
6ba91f4
optimizer_engine_doc()
2e5c9b3
fix proximal extension
Maximilian-Stefan-Ernst e47a94f
don't export SemOptimizerOptim
309c578
SemFit: add opt_engine() to the output
38939b7
SemOptimizerResult: streamline optim results
ccbf55f
docs: fix optimizer engine docs
1327236
docs/make.jl: disable doctest
alyst 2f2293c
optimizer.md: rename to SemOptimizerMyopt
5cdcbb1
docs: apply suggestions
f1f453c
dovs/optimizer.md: more updates for the new API
1f8d2a9
.gh/FormatCheck: run on pull_request
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,74 +1,83 @@ | ||
| # Custom optimizer types | ||
|
|
||
| The optimizer part of a model connects it to the optimization backend. | ||
| Let's say we want to implement a new optimizer as `SemOptimizerName`. The first part of the implementation is very similar to loss functions, so we just show the implementation of `SemOptimizerOptim` here as a reference: | ||
| The optimizer part of a model connects it to the optimization engine. | ||
| Let's say we want to implement a new optimizer as `SemOptimizerMyopt`. | ||
|
|
||
| ```julia | ||
| ############################################################################################ | ||
| ### Types and Constructor | ||
| ############################################################################################ | ||
| mutable struct SemOptimizerName{A, B} <: SemOptimizer{:Name} | ||
| struct SemOptimizerMyopt{A, B} <: SemOptimizer{:Myopt} | ||
| algorithm::A | ||
| options::B | ||
| end | ||
|
|
||
| SemOptimizer{:Name}(args...; kwargs...) = SemOptimizerName(args...; kwargs...) | ||
| SEM.sem_optimizer_subtype(::Val{:Myopt}) = SemOptimizerMyopt | ||
|
|
||
| SemOptimizerName(; | ||
| algorithm = LBFGS(), | ||
| options = Optim.Options(; f_reltol = 1e-10, x_abstol = 1.5e-8), | ||
| SemOptimizerMyopt(; | ||
| algorithm = ..., | ||
| options = ..., | ||
| kwargs..., | ||
| ) = SemOptimizerName(algorithm, options) | ||
| ) = SemOptimizerMyopt(algorithm, options) | ||
|
|
||
| struct MyoptResult{O <: SemOptimizerMyopt} <: SEM.SemOptimizerResult{O} | ||
| optimizer::O | ||
| ... | ||
| end | ||
|
|
||
| ############################################################################################ | ||
| ### Recommended methods | ||
| ############################################################################################ | ||
|
|
||
| update_observed(optimizer::SemOptimizerName, observed::SemObserved; kwargs...) = optimizer | ||
| update_observed(optimizer::SemOptimizerMyopt, observed::SemObserved; kwargs...) = optimizer | ||
|
|
||
| ############################################################################################ | ||
| ### additional methods | ||
| ############################################################################################ | ||
|
|
||
| algorithm(optimizer::SemOptimizerName) = optimizer.algorithm | ||
| options(optimizer::SemOptimizerName) = optimizer.options | ||
| options(optimizer::SemOptimizerMyopt) = optimizer.options | ||
| ``` | ||
|
|
||
| Note that your optimizer is a subtype of `SemOptimizer{:Name}`, where you can choose a `:Name` that can later be used as a keyword argument to `fit(engine = :Name)`. | ||
| Similarly, `SemOptimizer{:Name}(args...; kwargs...) = SemOptimizerName(args...; kwargs...)` should be defined as well as a constructor that uses only keyword arguments: | ||
| Note that `SemOptimizerMyopt` is defined as a subtype of [`SemOptimizer{:Myopt}`](@ref SEM.SemOptimizer)`, | ||
| and `SEM.sem_optimizer_subtype(::Val{:Myopt})` returns `SemOptimizerMyopt`. | ||
| This instructs *SEM.jl* to use `SemOptimizerMyopt` when `:Myopt` is specified as the engine for | ||
| model fitting: `fit(..., engine = :Myopt)`. | ||
|
|
||
| ```julia | ||
| SemOptimizerName(; | ||
| algorithm = LBFGS(), | ||
| options = Optim.Options(; f_reltol = 1e-10, x_abstol = 1.5e-8), | ||
| kwargs..., | ||
| ) = SemOptimizerName(algorithm, options) | ||
| ``` | ||
| A method for `update_observed` and additional methods might be usefull, but are not necessary. | ||
|
|
||
| Now comes the substantive part: We need to provide a method for `fit`: | ||
| Now comes the essential part: we need to provide the [`fit`](@ref) method with `SemOptimizerMyopt` | ||
| as the first positional argument. | ||
|
|
||
| ```julia | ||
| function fit( | ||
| optim::SemOptimizerName, | ||
| optim::SemOptimizerMyopt, | ||
| model::AbstractSem, | ||
| start_params::AbstractVector; | ||
| kwargs..., | ||
| ) | ||
| optimization_result = ... | ||
| # ... prepare the Myopt optimization problem | ||
|
|
||
| ... | ||
| myopt_res = ... # fit the problem with the Myopt engine | ||
| minimum = ... # extract the minimum from myopt_res | ||
| minimizer = ... # extract the solution (parameter estimates) | ||
| optim_result = MyoptResult(optim, myopt_res, ...) # store the original Myopt result and params | ||
|
|
||
| return SemFit(minimum, minimizer, start_params, model, optimization_result) | ||
| return SemFit(minimum, minimizer, start_params, model, optim_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). | ||
| This method is responsible for converting the SEM into the format required by your optimization engine, | ||
| running the optimization, extracting the solution and returning the `SemFit` object, which should package: | ||
| * the minimum of the objective at the solution | ||
| * the minimizer (the vector of the SEM parameter estimates) | ||
| * the starting values | ||
| * the SEM model | ||
| * `MyoptResult` object with any relevant engine-specific details you want to preserve | ||
|
|
||
| In addition, you might want to provide methods to access properties of your optimization result: | ||
| In addition, you might want to provide methods to access engine-specific properties stored in `MyoptResult`: | ||
|
|
||
| ```julia | ||
| optimizer(res::MyOptimizationResult) = ... | ||
| n_iterations(res::MyOptimizationResult) = ... | ||
| convergence(res::MyOptimizationResult) = ... | ||
| ``` | ||
| algorithm_name(res::MyoptResult) = ... | ||
| n_iterations(res::MyoptResult) = ... | ||
| convergence(res::MyoptResult) = ... | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.