Skip to content

Commit b107f2d

Browse files
Merge pull request #249 from StructuralEquationModels/devel
Devel
2 parents 1dd07a7 + 4091804 commit b107f2d

File tree

104 files changed

+2949
-2632
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

104 files changed

+2949
-2632
lines changed

Project.toml

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "StructuralEquationModels"
22
uuid = "383ca8c5-e4ff-4104-b0a9-f7b279deed53"
33
authors = ["Maximilian Ernst", "Aaron Peikert"]
4-
version = "0.2.4"
4+
version = "0.3.0"
55

66
[deps]
77
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
@@ -12,7 +12,6 @@ LazyArtifacts = "4af54fe1-eca0-43a8-85a7-787d91b784e3"
1212
LineSearches = "d3d80556-e9d4-5f37-9878-2ab0fcc64255"
1313
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
1414
NLSolversBase = "d41bc354-129a-5804-8e4c-c37616107c6c"
15-
NLopt = "76087f3c-5699-56af-9a33-bf431cd00edd"
1615
Optim = "429524aa-4258-5aef-a3af-852621145aeb"
1716
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
1817
PrettyTables = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d"
@@ -25,8 +24,8 @@ Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7"
2524
SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b"
2625

2726
[compat]
28-
julia = "1.9, 1.10"
29-
StenoGraphs = "0.2, 0.3"
27+
julia = "1.9, 1.10, 1.11"
28+
StenoGraphs = "0.2 - 0.3, 0.4.1 - 0.5"
3029
DataFrames = "1"
3130
Distributions = "0.25"
3231
FiniteDiff = "2"
@@ -35,12 +34,21 @@ NLSolversBase = "7"
3534
NLopt = "0.6, 1"
3635
Optim = "1"
3736
PrettyTables = "2"
37+
ProximalAlgorithms = "0.7"
3838
StatsBase = "0.33, 0.34"
39-
Symbolics = "4, 5"
40-
SymbolicUtils = "1.4 - 1.5"
39+
Symbolics = "4, 5, 6"
40+
SymbolicUtils = "1.4 - 1.5, 1.7, 2, 3"
4141

4242
[extras]
4343
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
4444

4545
[targets]
4646
test = ["Test"]
47+
48+
[weakdeps]
49+
NLopt = "76087f3c-5699-56af-9a33-bf431cd00edd"
50+
ProximalAlgorithms = "140ffc9f-1907-541a-a177-7475e0a401e9"
51+
52+
[extensions]
53+
SEMNLOptExt = "NLopt"
54+
SEMProximalOptExt = "ProximalAlgorithms"

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ It is still *in development*.
1111
Models you can fit include
1212
- Linear SEM that can be specified in RAM (or LISREL) notation
1313
- ML, GLS and FIML estimation
14-
- Regularization
14+
- Regularized SEM (Ridge, Lasso, L0, ...)
1515
- Multigroup SEM
1616
- Sums of arbitrary loss functions (everything the optimizer can handle).
1717

@@ -35,6 +35,7 @@ The package makes use of
3535
- Symbolics.jl for symbolically precomputing parts of the objective and gradients to generate fast, specialized functions.
3636
- SparseArrays.jl to speed up symbolic computations.
3737
- Optim.jl and NLopt.jl to provide a range of different Optimizers/Linesearches.
38+
- ProximalAlgorithms.jl for regularization.
3839
- FiniteDiff.jl and ForwardDiff.jl to provide gradients for user-defined loss functions.
3940

4041
# At the moment, we are still working on:

docs/Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
[deps]
22
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
33
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
4+
NLopt = "76087f3c-5699-56af-9a33-bf431cd00edd"
5+
ProximalAlgorithms = "140ffc9f-1907-541a-a177-7475e0a401e9"
46
ProximalOperators = "a725b495-10eb-56fe-b38b-717eba820537"

docs/make.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ makedocs(
3232
"Developer documentation" => [
3333
"Extending the package" => "developer/extending.md",
3434
"Custom loss functions" => "developer/loss.md",
35-
"Custom imply types" => "developer/imply.md",
35+
"Custom implied types" => "developer/implied.md",
3636
"Custom optimizer types" => "developer/optimizer.md",
3737
"Custom observed types" => "developer/observed.md",
3838
"Custom model types" => "developer/sem.md",

docs/src/assets/concept.svg

Lines changed: 0 additions & 26 deletions
Loading

docs/src/assets/concept_typed.svg

Lines changed: 0 additions & 26 deletions
Loading

docs/src/developer/extending.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Extending the package
22

3-
As discussed in the section on [Model Construction](@ref), every Structural Equation Model (`Sem`) consists of four parts:
3+
As discussed in the section on [Model Construction](@ref), every Structural Equation Model (`Sem`) consists of three (four with the optimizer) parts:
44

55
![SEM concept typed](../assets/concept_typed.svg)
66

docs/src/developer/implied.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Custom implied types
2+
3+
We recommend to first read the part [Custom loss functions](@ref), as the overall implementation is the same and we will describe it here more briefly.
4+
5+
Implied types are of subtype `SemImplied`. To implement your own implied type, you should define a struct
6+
7+
```julia
8+
struct MyImplied <: SemImplied
9+
...
10+
end
11+
```
12+
13+
and a method to update!:
14+
15+
```julia
16+
import StructuralEquationModels: objective!
17+
18+
function update!(targets::EvaluationTargets, implied::MyImplied, model::AbstractSemSingle, params)
19+
20+
if is_objective_required(targets)
21+
...
22+
end
23+
24+
if is_gradient_required(targets)
25+
...
26+
end
27+
if is_hessian_required(targets)
28+
...
29+
end
30+
31+
end
32+
```
33+
34+
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.
35+
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](@ref), the `RAM` implied type computes the model-implied covariance matrix and makes it available via `implied.Σ`.
36+
37+
38+
39+
Just as described in [Custom loss functions](@ref), you may define a constructor. Typically, this will depend on the `specification = ...` argument that can be a `ParameterTable` or a `RAMMatrices` object.
40+
41+
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:
42+
43+
```julia
44+
############################################################################################
45+
### Types
46+
############################################################################################
47+
"""
48+
Empty placeholder for models that don't need an implied part.
49+
(For example, models that only regularize parameters.)
50+
51+
# Constructor
52+
53+
ImpliedEmpty(;specification, kwargs...)
54+
55+
# Arguments
56+
- `specification`: either a `RAMMatrices` or `ParameterTable` object
57+
58+
# Examples
59+
A multigroup model with ridge regularization could be specified as a `SemEnsemble` with one
60+
model per group and an additional model with `ImpliedEmpty` and `SemRidge` for the regularization part.
61+
62+
# Extended help
63+
64+
## Interfaces
65+
- `params(::RAMSymbolic) `-> Vector of parameter labels
66+
- `nparams(::RAMSymbolic)` -> Number of parameters
67+
68+
## Implementation
69+
Subtype of `SemImplied`.
70+
"""
71+
struct ImpliedEmpty{A, B, C} <: SemImplied
72+
hessianeval::A
73+
meanstruct::B
74+
ram_matrices::C
75+
end
76+
77+
############################################################################################
78+
### Constructors
79+
############################################################################################
80+
81+
function ImpliedEmpty(;specification, meanstruct = NoMeanStruct(), hessianeval = ExactHessian(), kwargs...)
82+
return ImpliedEmpty(hessianeval, meanstruct, convert(RAMMatrices, specification))
83+
end
84+
85+
############################################################################################
86+
### methods
87+
############################################################################################
88+
89+
update!(targets::EvaluationTargets, implied::ImpliedEmpty, par, model) = nothing
90+
91+
############################################################################################
92+
### Recommended methods
93+
############################################################################################
94+
95+
update_observed(implied::ImpliedEmpty, observed::SemObserved; kwargs...) = implied
96+
```
97+
98+
As you see, similar to [Custom loss functions](@ref) we implement a method for `update_observed`.

docs/src/developer/imply.md

Lines changed: 0 additions & 87 deletions
This file was deleted.

0 commit comments

Comments
 (0)