Skip to content
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

Add event support mtkmodel #2427

Merged
merged 6 commits into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions docs/src/basics/MTKModel_Connector.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ equations.
- `@parameters`: for specifying the symbolic parameters
- `@structural_parameters`: for specifying non-symbolic parameters
- `@variables`: for specifying the unknowns
- `@continuous_events`: for specifying a list of continuous events
- `@discrete_events`: for specifying a list of discrete events

Let's explore these in more detail with the following example:

Expand Down Expand Up @@ -182,6 +184,59 @@ getdefault(model_c3.model_a.k_array[2])
- Default values can be passed as pairs.
- This is equivalent to passing `defaults` argument to `ODESystem`.

#### `@continuous_events` begin block

- Defining continuous events as described [here](https://docs.sciml.ai/ModelingToolkit/stable/basics/Events/#Continuous-Events).
- If this block is not defined in the model, no continuous events will be added.

```@example mtkmodel-example
using ModelingToolkit

@mtkmodel M begin
@parameters begin
k
end
@variables begin
x(t)
y(t)
end
@equations begin
x ~ k * D(x)
D(y) ~ -k
end
@continuous_events begin
[x ~ 1.5] => [x ~ 5, y ~ 5]
[t ~ 4] => [x ~ 10]
end
end
```

#### `@discrete_events` begin block

- Defining discrete events as described [here](https://docs.sciml.ai/ModelingToolkit/stable/basics/Events/#Discrete-events-support).
- If this block is not defined in the model, no discrete events will be added.

```@example mtkmodel-example
using ModelingToolkit

@mtkmodel M begin
@parameters begin
k
end
@variables begin
x(t)
y(t)
end
@equations begin
x ~ k * D(x)
D(y) ~ -k
end
@discrete_events begin
(t == 1.5) => [x ~ x + 5, y ~ 5]
end
end
```

#### A begin block

- Any other Julia operations can be included with dedicated begin blocks.
Expand Down
39 changes: 37 additions & 2 deletions src/systems/model_parsing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
eqs = Expr[]
icon = Ref{Union{String, URI}}()
ps, sps, vs, = [], [], []
c_evts = []
d_evts = []
kwargs = Set()
where_types = Expr[]

Expand All @@ -61,7 +63,7 @@
for arg in expr.args
if arg.head == :macrocall
parse_model!(exprs.args, comps, ext, eqs, icon, vs, ps,
sps, dict, mod, arg, kwargs, where_types)
sps, c_evts, d_evts, dict, mod, arg, kwargs, where_types)
elseif arg.head == :block
push!(exprs.args, arg)
elseif arg.head == :if
Expand Down Expand Up @@ -116,6 +118,16 @@
isconnector && push!(exprs.args,
:($Setfield.@set!(var"#___sys___".connector_type=$connector_type(var"#___sys___"))))

!(c_evts == []) && push!(exprs.args,
:($Setfield.@set!(var"#___sys___".continuous_events=$SymbolicContinuousCallback.([

Check warning on line 122 in src/systems/model_parsing.jl

View check run for this annotation

Codecov / codecov/patch

src/systems/model_parsing.jl#L122

Added line #L122 was not covered by tests
$(c_evts...)
]))))

!(d_evts == []) && push!(exprs.args,
:($Setfield.@set!(var"#___sys___".discrete_events=$SymbolicDiscreteCallback.([

Check warning on line 127 in src/systems/model_parsing.jl

View check run for this annotation

Codecov / codecov/patch

src/systems/model_parsing.jl#L127

Added line #L127 was not covered by tests
$(d_evts...)
]))))

f = if length(where_types) == 0
:($(Symbol(:__, name, :__))(; name, $(kwargs...)) = $exprs)
else
Expand All @@ -124,6 +136,7 @@
:($(Symbol(:__, name, :__))(; name, $(kwargs...))), where_types...)
:($f_with_where = $exprs)
end

:($name = $Model($f, $dict, $isconnector))
end

Expand Down Expand Up @@ -341,7 +354,7 @@
end
end

function parse_model!(exprs, comps, ext, eqs, icon, vs, ps, sps,
function parse_model!(exprs, comps, ext, eqs, icon, vs, ps, sps, c_evts, d_evts,
dict, mod, arg, kwargs, where_types)
mname = arg.args[1]
body = arg.args[end]
Expand All @@ -359,6 +372,10 @@
parse_equations!(exprs, eqs, dict, body)
elseif mname == Symbol("@constants")
parse_constants!(exprs, dict, body, mod)
elseif mname == Symbol("@continuous_events")
parse_continuous_events!(c_evts, dict, body)
elseif mname == Symbol("@discrete_events")
parse_discrete_events!(d_evts, dict, body)
elseif mname == Symbol("@icon")
isassigned(icon) && error("This model has more than one icon.")
parse_icon!(body, dict, icon, mod)
Expand Down Expand Up @@ -753,6 +770,24 @@
end
end

function parse_continuous_events!(c_evts, dict, body)
dict[:continuous_events] = []
Base.remove_linenums!(body)
for arg in body.args
push!(c_evts, arg)
push!(dict[:continuous_events], readable_code.(c_evts)...)
end
end

function parse_discrete_events!(d_evts, dict, body)
dict[:discrete_events] = []
Base.remove_linenums!(body)
for arg in body.args
push!(d_evts, arg)
push!(dict[:discrete_events], readable_code.(d_evts)...)
end
end

function parse_icon!(body::String, dict, icon, mod)
icon_dir = get(ENV, "MTK_ICONS_DIR", joinpath(DEPOT_PATH[1], "mtk_icons"))
dict[:icon] = icon[] = if isfile(body)
Expand Down
30 changes: 30 additions & 0 deletions test/model_parsing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,36 @@ end
@test A.structure[:components] == [[:cc, :C]]
end

@testset "Event handling in MTKModel" begin
@mtkmodel M begin
@variables begin
x(t)
y(t)
z(t)
end
@equations begin
x ~ -D(x)
D(y) ~ 0
D(z) ~ 0
end
@continuous_events begin
[x ~ 1.5] => [x ~ 5, y ~ 1]
end
@discrete_events begin
(t == 1.5) => [x ~ x + 5, z ~ 2]
end
end

@mtkbuild model = M()
u0 = [model.x => 10, model.y => 0, model.z => 0]

prob = ODEProblem(model, u0, (0, 5.0))
sol = solve(prob, Tsit5(), tstops = [1.5])

@test isequal(sol[model.y][end], 1.0)
@test isequal(sol[model.z][end], 2.0)
end

# Ensure that modules consisting MTKModels with component arrays and icons of
# `Expr` type and `unit` metadata can be precompiled.
module PrecompilationTest
Expand Down
Loading