Skip to content

Commit 397f518

Browse files
authored
Support symbolic arrays in @mtkmodel (#2282)
1 parent df0bd9a commit 397f518

File tree

12 files changed

+199
-95
lines changed

12 files changed

+199
-95
lines changed

docs/src/basics/MTKModel_Connector.md

Lines changed: 38 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ equations.
1919
`ModelingToolkit.Model`, which includes a constructor that returns the ODESystem, a
2020
`structure` dictionary with metadata, and flag `isconnector` which is set to `false`.
2121

22-
## What can an MTK-Model definition have?
22+
### What can an MTK-Model definition have?
2323

2424
`@mtkmodel` definition contains begin blocks of
2525

@@ -37,8 +37,8 @@ using ModelingToolkit
3737
3838
@mtkmodel ModelA begin
3939
@parameters begin
40-
k1
41-
k2
40+
k
41+
k_array[1:2]
4242
end
4343
end
4444
@@ -59,17 +59,17 @@ end
5959
@variables begin
6060
v(t) = v_var
6161
end
62-
@extend p1, p2 = model_b = ModelB(; p1)
62+
@extend ModelB(; p1)
6363
@components begin
64-
model_a = ModelA(; k1)
64+
model_a = ModelA(; k_array)
6565
end
6666
@equations begin
67-
model_a.k1 ~ f(v)
67+
model_a.k ~ f(v)
6868
end
6969
end
7070
```
7171

72-
### `@parameters` and `@variables` begin block
72+
#### `@parameters` and `@variables` begin block
7373

7474
- Parameters and variables are declared with respective begin blocks.
7575
- Variables must be functions of an independent variable.
@@ -78,49 +78,54 @@ end
7878
- Whenever a parameter or variable has initial value, for example `v(t) = 0.0`, a symbolic variable named `v` with initial value 0.0 and a keyword argument `v`, with default value `nothing` are created. <br> This way, users can optionally pass new value of `v` while creating a component.
7979

8080
```julia
81-
julia > @named model_c = ModelC(; v = 2.0);
81+
julia> @mtkbuild model_c1 = ModelC(; v = 2.0);
8282

83-
julia > ModelingToolkit.getdefault(model_c.v)
83+
julia> ModelingToolkit.getdefault(model_c.v)
8484
2.0
8585
```
8686

87-
### `@structural_parameters` begin block
87+
#### `@structural_parameters` begin block
8888

8989
- This block is for non symbolic input arguements. These are for inputs that usually are not meant to be part of components; but influence how they are defined. One can list inputs like boolean flags, functions etc... here.
9090
- Whenever default values are specified, unlike parameters/variables, they are reflected in the keyword argument list.
9191

9292
#### `@extend` begin block
9393

94-
To extend a partial system,
95-
96-
- List the variables to unpack. If there is a single variable, explicitly specify it as a tuple.
97-
- Give a name to the base system
98-
- List the kwargs of the base system that should be listed as kwargs of the main component.
99-
- Note that in above example, `p1` is promoted as an argument of `ModelC`. Users can set the value of `p1` as
94+
- Partial systems can be extended in a higher system as `@extend PartialSystem(; kwargs)`.
95+
- Keyword arguments pf partial system in the `@extend` definition are added as the keyword arguments of the base system.
96+
- Note that in above example, `p1` is promoted as an argument of `ModelC`. Users can set the value of `p1`. However, as `p2` isn't listed in the model definition, its initial guess can't be specified while creating an instance of `ModelC`.
10097

10198
```julia
102-
julia> @named model_c = ModelC(; p1 = 2.0)
99+
julia> @mtkbuild model_c2 = ModelC(; p1 = 2.0)
103100

104101
```
105102

106-
However, as `p2` isn't listed in the model definition, its initial guess can't
107-
specified while creating an instance of `ModelC`.
108-
109-
### `@components` begin block
103+
#### `@components` begin block
110104

111105
- Declare the subcomponents within `@components` begin block.
112106
- The arguments in these subcomponents are promoted as keyword arguments as `subcomponent_name__argname` with `nothing` as default value.
113107
- Whenever components are created with `@named` macro, these can be accessed with `.` operator as `subcomponent_name.argname`
114-
- In the above example, `k1` of `model_a` can be set in following ways:
108+
- In the above example, as `k` of `model_a` isn't listed while defining the sub-component in `ModelC`, its default value can't be modified by users. While `k_array` can be set as:
115109

116-
```julia
117-
julia> @named model_c1 = ModelC(; model_a.k1 = 1);
110+
```@example mtkmodel-example
111+
using ModelingToolkit: getdefault
118112
119-
```
113+
@mtkbuild model_c3 = ModelC(; model_a.k_array = [1.0, 2.0])
114+
115+
getdefault(model_c3.model_a.k_array[1])
116+
# 1.0
117+
getdefault(model_c3.model_a.k_array[2])
118+
# 2.0
120119
121-
And as `k2` isn't listed in the sub-component definition of `ModelC`, its default value can't be modified by users.
120+
@mtkbuild model_c4 = ModelC(model_a.k_array = 3.0)
121+
122+
getdefault(model_c4.model_a.k_array[1])
123+
# 3.0
124+
getdefault(model_c4.model_a.k_array[2])
125+
# 3.0
126+
```
122127

123-
### `@equations` begin block
128+
#### `@equations` begin block
124129

125130
- List all the equations here
126131

@@ -192,8 +197,10 @@ end
192197
- `:components`: List of sub-components in the form of [[name, sub_component_name],...].
193198
- `:extend`: The list of extended states, name given to the base system, and name of the base system.
194199
- `:structural_parameters`: Dictionary of structural parameters mapped to their default values.
195-
- `:parameters`: Dictionary of symbolic parameters mapped to their metadata.
196-
- `:variables`: Dictionary of symbolic variables mapped to their metadata.
200+
- `:parameters`: Dictionary of symbolic parameters mapped to their metadata. For
201+
parameter arrays, length is added to the metadata as `:size`.
202+
- `:variables`: Dictionary of symbolic variables mapped to their metadata. For
203+
variable arrays, length is added to the metadata as `:size`.
197204
- `:kwargs`: Dictionary of keyword arguments mapped to their default values.
198205
- `:independent_variable`: Independent variable, which is added while generating the Model.
199206
- `:equations`: List of equations (represented as strings).
@@ -204,8 +211,8 @@ For example, the structure of `ModelC` is:
204211
julia> ModelC.structure
205212
Dict{Symbol, Any} with 6 entries:
206213
:components => [[:model_a, :ModelA]]
207-
:variables => Dict{Symbol, Dict{Symbol, Any}}(:v=>Dict(:default=>:v_var))
208-
:kwargs => Dict{Symbol, Any}(:f=>:sin, :v=>:v_var, :p1=>nothing, :model_a__k1=>nothing)
214+
:variables => Dict{Symbol, Dict{Symbol, Any}}(:v=>Dict(:default=>:v_var), :v_array=>Dict(:size=>(4,)))
215+
:kwargs => Dict{Symbol, Any}(:f=>:sin, :v=>:v_var, :v_array=>nothing, :model_a__k_array=>nothing, :p1=>nothing)
209216
:independent_variable => t
210217
:extend => Any[[:p1, :p2], :model_b, :ModelB]
211218
:equations => ["model_a.k1 ~ f(v)"]

docs/src/systems/JumpSystem.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ structural_simplify
2626
```@docs; canonical=false
2727
DiscreteProblem(sys::ModelingToolkit.DiscreteSystem, u0map, tspan)
2828
```
29+
2930
```@docs
3031
JumpProblem(sys::JumpSystem, prob, aggregator)
3132
```

docs/src/systems/SDESystem.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ sde = SDESystem(ode, noiseeqs)
2626
structural_simplify
2727
alias_elimination
2828
```
29+
2930
```@docs
3031
ModelingToolkit.Girsanov_transform
3132
```
Lines changed: 63 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,91 @@
11
# [Bifurcation Diagrams](@id bifurcation_diagrams)
2+
23
Bifurcation diagrams describes how, for a dynamic system, the quantity and quality of its steady states changes with a parameter's value. These can be computed through the [BifurcationKit.jl](https://github.com/bifurcationkit/BifurcationKit.jl) package. ModelingToolkit provides a simple interface for creating BifurcationKit compatible `BifurcationProblem`s from `NonlinearSystem`s and `ODESystem`s. All teh features provided by BifurcationKit can then be applied to these systems. This tutorial provides a brief introduction for these features, with BifurcationKit.jl providing [a more extensive documentation](https://bifurcationkit.github.io/BifurcationKitDocs.jl/stable/).
34

45
### Creating a `BifurcationProblem`
6+
57
Let us first consider a simple `NonlinearSystem`:
8+
69
```@example Bif1
710
using ModelingToolkit
811
@variables t x(t) y(t)
912
@parameters μ α
10-
eqs = [0 ~ μ*x - x^3 + α*y,
11-
0 ~ -y]
13+
eqs = [0 ~ μ * x - x^3 + α * y,
14+
0 ~ -y]
1215
@named nsys = NonlinearSystem(eqs, [x, y], [μ, α])
1316
```
17+
1418
we wish to compute a bifurcation diagram for this system as we vary the parameter `μ`. For this, we need to provide the following information:
15-
1. The system for which we wish to compute the bifurcation diagram (`nsys`).
16-
2. The parameter which we wish to vary (`μ`).
17-
3. The parameter set for which we want to compute the bifurcation diagram.
18-
4. An initial guess of the state of the system for which there is a steady state at our provided parameter value.
19-
5. The variable which value we wish to plot in the bifurcation diagram (this argument is optional, if not provided, BifurcationKit default plot functions are used).
19+
20+
1. The system for which we wish to compute the bifurcation diagram (`nsys`).
21+
2. The parameter which we wish to vary (`μ`).
22+
3. The parameter set for which we want to compute the bifurcation diagram.
23+
4. An initial guess of the state of the system for which there is a steady state at our provided parameter value.
24+
5. The variable which value we wish to plot in the bifurcation diagram (this argument is optional, if not provided, BifurcationKit default plot functions are used).
2025

2126
We declare this additional information:
27+
2228
```@example Bif1
2329
bif_par = μ
2430
p_start = [μ => -1.0, α => 1.0]
2531
u0_guess = [x => 1.0, y => 1.0]
2632
plot_var = x;
2733
```
34+
2835
For the initial state guess (`u0_guess`), typically any value can be provided, however, read BifurcatioKit's documentation for more details.
2936

3037
We can now create our `BifurcationProblem`, which can be provided as input to BifurcationKit's various functions.
38+
3139
```@example Bif1
3240
using BifurcationKit
33-
bprob = BifurcationProblem(nsys, u0_guess, p_start, bif_par; plot_var=plot_var, jac=false)
41+
bprob = BifurcationProblem(nsys,
42+
u0_guess,
43+
p_start,
44+
bif_par;
45+
plot_var = plot_var,
46+
jac = false)
3447
```
35-
Here, the `jac` argument (by default set to `true`) sets whenever to provide BifurcationKit with a Jacobian or not.
3648

49+
Here, the `jac` argument (by default set to `true`) sets whenever to provide BifurcationKit with a Jacobian or not.
3750

3851
### Computing a bifurcation diagram
3952

40-
Let us consider the `BifurcationProblem` from the last section. If we wish to compute the corresponding bifurcation diagram we must first declare various settings used by BifurcationKit to compute the diagram. These are stored in a `ContinuationPar` structure (which also contain a `NewtonPar` structure).
53+
Let us consider the `BifurcationProblem` from the last section. If we wish to compute the corresponding bifurcation diagram we must first declare various settings used by BifurcationKit to compute the diagram. These are stored in a `ContinuationPar` structure (which also contain a `NewtonPar` structure).
54+
4155
```@example Bif1
4256
p_span = (-4.0, 6.0)
4357
opt_newton = NewtonPar(tol = 1e-9, max_iterations = 20)
4458
opts_br = ContinuationPar(dsmin = 0.001, dsmax = 0.05, ds = 0.01,
45-
max_steps = 100, nev = 2, newton_options = opt_newton,
46-
p_min = p_span[1], p_max = p_span[2],
47-
detect_bifurcation = 3, n_inversion = 4, tol_bisection_eigenvalue = 1e-8, dsmin_bisection = 1e-9);
59+
max_steps = 100, nev = 2, newton_options = opt_newton,
60+
p_min = p_span[1], p_max = p_span[2],
61+
detect_bifurcation = 3, n_inversion = 4, tol_bisection_eigenvalue = 1e-8,
62+
dsmin_bisection = 1e-9);
4863
```
64+
4965
Here, `p_span` sets the interval over which we wish to compute the diagram.
5066

5167
Next, we can use this as input to our bifurcation diagram, and then plot it.
68+
5269
```@example Bif1
53-
bf = bifurcationdiagram(bprob, PALC(), 2, (args...) -> opts_br; bothside=true)
70+
bf = bifurcationdiagram(bprob, PALC(), 2, (args...) -> opts_br; bothside = true)
5471
```
72+
5573
Here, the value `2` sets how sub-branches of the diagram that BifurcationKit should compute. Generally, for bifurcation diagrams, it is recommended to use the `bothside=true` argument.
74+
5675
```@example Bif1
5776
using Plots
58-
plot(bf; putspecialptlegend=false, markersize=2, plotfold=false, xguide="μ", yguide = "x")
77+
plot(bf;
78+
putspecialptlegend = false,
79+
markersize = 2,
80+
plotfold = false,
81+
xguide = "μ",
82+
yguide = "x")
5983
```
84+
6085
Here, the system exhibits a pitchfork bifurcation at *μ=0.0*.
6186

6287
### Using `ODESystem` inputs
88+
6389
It is also possible to use `ODESystem`s (rather than `NonlinearSystem`s) as input to `BifurcationProblem`. Here follows a brief such example.
6490

6591
```@example Bif2
@@ -68,25 +94,38 @@ using BifurcationKit, ModelingToolkit, Plots
6894
@variables t x(t) y(t)
6995
@parameters μ
7096
D = Differential(t)
71-
eqs = [D(x) ~ μ*x - y - x*(x^2+y^2),
72-
D(y) ~ x + μ*y - y*(x^2+y^2)]
97+
eqs = [D(x) ~ μ * x - y - x * (x^2 + y^2),
98+
D(y) ~ x + μ * y - y * (x^2 + y^2)]
7399
@named osys = ODESystem(eqs, t)
74100
75101
bif_par = μ
76102
plot_var = x
77103
p_start = [μ => 1.0]
78-
u0_guess = [x => 0.0, y=> 0.0]
104+
u0_guess = [x => 0.0, y => 0.0]
79105
80-
bprob = BifurcationProblem(osys, u0_guess, p_start, bif_par; plot_var=plot_var, jac=false)
106+
bprob = BifurcationProblem(osys,
107+
u0_guess,
108+
p_start,
109+
bif_par;
110+
plot_var = plot_var,
111+
jac = false)
81112
82113
p_span = (-3.0, 3.0)
83114
opt_newton = NewtonPar(tol = 1e-9, max_iterations = 20)
84115
opts_br = ContinuationPar(dsmin = 0.001, dsmax = 0.05, ds = 0.01,
85-
max_steps = 100, nev = 2, newton_options = opt_newton,
86-
p_max = p_span[2], p_min = p_span[1],
87-
detect_bifurcation = 3, n_inversion = 4, tol_bisection_eigenvalue = 1e-8, dsmin_bisection = 1e-9)
116+
max_steps = 100, nev = 2, newton_options = opt_newton,
117+
p_max = p_span[2], p_min = p_span[1],
118+
detect_bifurcation = 3, n_inversion = 4, tol_bisection_eigenvalue = 1e-8,
119+
dsmin_bisection = 1e-9)
88120
89-
bf = bifurcationdiagram(bprob, PALC(), 2, (args...) -> opts_br; bothside=true)
90-
plot(bf; putspecialptlegend=false, markersize=2, plotfold=false, xguide="μ", yguide = "x")
121+
bf = bifurcationdiagram(bprob, PALC(), 2, (args...) -> opts_br; bothside = true)
122+
using Plots
123+
plot(bf;
124+
putspecialptlegend = false,
125+
markersize = 2,
126+
plotfold = false,
127+
xguide = "μ",
128+
yguide = "x")
91129
```
130+
92131
Here, the value of `x` in the steady state does not change, however, at `μ=0` a Hopf bifurcation occur and the steady state turn unstable.

ext/MTKBifurcationKitExt.jl

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,23 @@ import BifurcationKit
99
### Creates BifurcationProblem Overloads ###
1010

1111
# When input is a NonlinearSystem.
12-
function BifurcationKit.BifurcationProblem(nsys::NonlinearSystem, u0_bif, ps, bif_par, args...; plot_var=nothing, record_from_solution=BifurcationKit.record_sol_default, jac=true, kwargs...)
12+
function BifurcationKit.BifurcationProblem(nsys::NonlinearSystem,
13+
u0_bif,
14+
ps,
15+
bif_par,
16+
args...;
17+
plot_var = nothing,
18+
record_from_solution = BifurcationKit.record_sol_default,
19+
jac = true,
20+
kwargs...)
1321
# Creates F and J functions.
14-
ofun = NonlinearFunction(nsys; jac=jac)
22+
ofun = NonlinearFunction(nsys; jac = jac)
1523
F = ofun.f
1624
J = jac ? ofun.jac : nothing
1725

1826
# Computes bifurcation parameter and plot var indexes.
1927
bif_idx = findfirst(isequal(bif_par), parameters(nsys))
20-
if !isnothing(plot_var)
28+
if !isnothing(plot_var)
2129
plot_idx = findfirst(isequal(plot_var), states(nsys))
2230
record_from_solution = (x, p) -> x[plot_idx]
2331
end
@@ -26,12 +34,22 @@ function BifurcationKit.BifurcationProblem(nsys::NonlinearSystem, u0_bif, ps, bi
2634
u0_bif = ModelingToolkit.varmap_to_vars(u0_bif, states(nsys))
2735
ps = ModelingToolkit.varmap_to_vars(ps, parameters(nsys))
2836

29-
return BifurcationKit.BifurcationProblem(F, u0_bif, ps, (@lens _[bif_idx]), args...; record_from_solution = record_from_solution, J = J, kwargs...)
37+
return BifurcationKit.BifurcationProblem(F,
38+
u0_bif,
39+
ps,
40+
(@lens _[bif_idx]),
41+
args...;
42+
record_from_solution = record_from_solution,
43+
J = J,
44+
kwargs...)
3045
end
3146

3247
# When input is a ODESystem.
3348
function BifurcationKit.BifurcationProblem(osys::ODESystem, args...; kwargs...)
34-
nsys = NonlinearSystem([0 ~ eq.rhs for eq in equations(osys)], states(osys), parameters(osys); name=osys.name)
49+
nsys = NonlinearSystem([0 ~ eq.rhs for eq in equations(osys)],
50+
states(osys),
51+
parameters(osys);
52+
name = osys.name)
3553
return BifurcationKit.BifurcationProblem(nsys, args...; kwargs...)
3654
end
3755

src/ModelingToolkit.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ using PrecompileTools, Reexport
5656
using Symbolics: _parse_vars, value, @derivatives, get_variables,
5757
exprs_occur_in, solve_for, build_expr, unwrap, wrap,
5858
VariableSource, getname, variable, Connection, connect,
59-
NAMESPACE_SEPARATOR
59+
NAMESPACE_SEPARATOR, set_scalar_metadata, setdefaultval
6060
import Symbolics: rename, get_variables!, _solve, hessian_sparsity,
6161
jacobian_sparsity, isaffine, islinear, _iszero, _isone,
6262
tosymbol, lower_varname, diff2term, var_from_nested_derivative,

0 commit comments

Comments
 (0)