Skip to content

Commit 8c3e053

Browse files
committed
Merge branch 'master' into myb/dd
2 parents e9667fe + 07701c4 commit 8c3e053

File tree

4 files changed

+78
-193
lines changed

4 files changed

+78
-193
lines changed

docs/src/basics/MTKLanguage.md

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,13 @@ end
6363
@structural_parameters begin
6464
f = sin
6565
N = 2
66-
M = 3
6766
end
6867
begin
6968
v_var = 1.0
7069
end
7170
@variables begin
7271
v(t) = v_var
73-
v_array(t)[1:N, 1:M]
72+
v_array(t)[1:2, 1:3]
7473
v_for_defaults(t)
7574
end
7675
@extend ModelB(; p1)
@@ -311,10 +310,10 @@ end
311310
- `:defaults`: Dictionary of variables and default values specified in the `@defaults`.
312311
- `:extend`: The list of extended unknowns, name given to the base system, and name of the base system.
313312
- `:structural_parameters`: Dictionary of structural parameters mapped to their metadata.
314-
- `:parameters`: Dictionary of symbolic parameters mapped to their metadata. Metadata of
315-
the parameter arrays is, for now, omitted.
316-
- `:variables`: Dictionary of symbolic variables mapped to their metadata. Metadata of
317-
the variable arrays is, for now, omitted.
313+
- `:parameters`: Dictionary of symbolic parameters mapped to their metadata. For
314+
parameter arrays, length is added to the metadata as `:size`.
315+
- `:variables`: Dictionary of symbolic variables mapped to their metadata. For
316+
variable arrays, length is added to the metadata as `:size`.
318317
- `:kwargs`: Dictionary of keyword arguments mapped to their metadata.
319318
- `:independent_variable`: Independent variable, which is added while generating the Model.
320319
- `:equations`: List of equations (represented as strings).
@@ -325,10 +324,10 @@ For example, the structure of `ModelC` is:
325324
julia> ModelC.structure
326325
Dict{Symbol, Any} with 10 entries:
327326
:components => Any[Union{Expr, Symbol}[:model_a, :ModelA], Union{Expr, Symbol}[:model_array_a, :ModelA, :(1:N)], Union{Expr, Symbol}[:model_array_b, :ModelA, :(1:N)]]
328-
:variables => Dict{Symbol, Dict{Symbol, Any}}(:v=>Dict(:default=>:v_var, :type=>Real), :v_for_defaults=>Dict(:type=>Real))
327+
:variables => Dict{Symbol, Dict{Symbol, Any}}(:v=>Dict(:default=>:v_var, :type=>Real), :v_array=>Dict(:type=>Real, :size=>(2, 3)), :v_for_defaults=>Dict(:type=>Real))
329328
:icon => URI("https://github.com/SciML/SciMLDocs/blob/main/docs/src/assets/logo.png")
330-
:kwargs => Dict{Symbol, Dict}(:f => Dict(:value => :sin), :N => Dict(:value => 2), :M => Dict(:value => 3), :v => Dict{Symbol, Any}(:value => :v_var, :type => Real), :v_for_defaults => Dict{Symbol, Union{Nothing, DataType}}(:value => nothing, :type => Real), :p1 => Dict(:value => nothing)),
331-
:structural_parameters => Dict{Symbol, Dict}(:f => Dict(:value => :sin), :N => Dict(:value => 2), :M => Dict(:value => 3))
329+
:kwargs => Dict{Symbol, Dict}(:f=>Dict(:value=>:sin), :N=>Dict(:value=>2), :v=>Dict{Symbol, Any}(:value=>:v_var, :type=>Real), :v_array=>Dict{Symbol, Union{Nothing, UnionAll}}(:value=>nothing, :type=>AbstractArray{Real}), :v_for_defaults=>Dict{Symbol, Union{Nothing, DataType}}(:value=>nothing, :type=>Real), :p1=>Dict(:value=>nothing))
330+
:structural_parameters => Dict{Symbol, Dict}(:f=>Dict(:value=>:sin), :N=>Dict(:value=>2))
332331
:independent_variable => t
333332
:constants => Dict{Symbol, Dict}(:c=>Dict{Symbol, Any}(:value=>1, :type=>Int64, :description=>"Example constant."))
334333
:extend => Any[[:p2, :p1], Symbol("#mtkmodel__anonymous__ModelB"), :ModelB]

docs/src/tutorials/programmatically_generating.md

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,44 @@
11
# [Programmatically Generating and Scripting ODESystems](@id programmatically)
22

3-
In the following tutorial we will discuss how to programmatically generate `ODESystem`s.
4-
This is for cases where one is writing functions that generating `ODESystem`s, for example
5-
if implementing a reader which parses some file format to generate an `ODESystem` (for example,
6-
SBML), or for writing functions that transform an `ODESystem` (for example, if you write a
7-
function that log-transforms a variable in an `ODESystem`).
3+
In the following tutorial, we will discuss how to programmatically generate `ODESystem`s.
4+
This is useful for functions that generate `ODESystem`s, for example
5+
when you implement a reader that parses some file format, such as SBML, to generate an `ODESystem`.
6+
It is also useful for functions that transform an `ODESystem`, for example
7+
when you write a function that log-transforms a variable in an `ODESystem`.
88

99
## The Representation of a ModelingToolkit System
1010

1111
ModelingToolkit is built on [Symbolics.jl](https://symbolics.juliasymbolics.org/dev/),
1212
a symbolic Computer Algebra System (CAS) developed in Julia. As such, all CAS functionality
13-
is available on ModelingToolkit systems, such as symbolic differentiation, Groebner basis
13+
is also available to be used on ModelingToolkit systems, such as symbolic differentiation, Groebner basis
1414
calculations, and whatever else you can think of. Under the hood, all ModelingToolkit
1515
variables and expressions are Symbolics.jl variables and expressions. Thus when scripting
1616
a ModelingToolkit system, one simply needs to generate Symbolics.jl variables and equations
1717
as demonstrated in the Symbolics.jl documentation. This looks like:
1818

1919
```@example scripting
20-
using Symbolics
21-
using ModelingToolkit: t_nounits as t, D_nounits as D
22-
23-
@variables x(t) y(t) # Define variables
20+
using ModelingToolkit # reexports Symbolics
21+
@variables t x(t) y(t) # Define variables
22+
D = Differential(t)
2423
eqs = [D(x) ~ y
2524
D(y) ~ x] # Define an array of equations
2625
```
2726

27+
However, ModelingToolkit has many higher-level features which will make scripting ModelingToolkit systems more convenient.
28+
For example, as shown in the next section, defining your own independent variables and differentials is rarely needed.
29+
2830
## The Non-DSL (non-`@mtkmodel`) Way of Defining an ODESystem
2931

30-
Using `@mtkmodel` is the preferred way of defining ODEs with MTK. However, let us
31-
look at how we can define the same system without `@mtkmodel`. This is useful for
32-
defining PDESystem etc.
32+
Using `@mtkmodel`, like in the [getting started tutorial](@ref getting_started),
33+
is the preferred way of defining ODEs with MTK.
34+
However generating the contents of a `@mtkmodel` programmatically can be tedious.
35+
Let us look at how we can define the same system without `@mtkmodel`.
3336

3437
```@example scripting
3538
using ModelingToolkit
3639
using ModelingToolkit: t_nounits as t, D_nounits as D
37-
38-
@variables x(t) # independent and dependent variables
39-
@parameters τ # parameters
40+
@variables x(t) = 0.0 # independent and dependent variables
41+
@parameters τ = 3.0 # parameters
4042
@constants h = 1 # constants
4143
eqs = [D(x) ~ (h - x) / τ] # create an array of equations
4244
@@ -45,10 +47,16 @@ eqs = [D(x) ~ (h - x) / τ] # create an array of equations
4547
4648
# Perform the standard transformations and mark the model complete
4749
# Note: Complete models cannot be subsystems of other models!
48-
fol_model = structural_simplify(model)
50+
fol = structural_simplify(model)
51+
prob = ODEProblem(fol, [], (0.0, 10.0), [])
52+
using DifferentialEquations: solve
53+
sol = solve(prob)
54+
55+
using Plots
56+
plot(sol)
4957
```
5058

51-
As you can see, generating an ODESystem is as simple as creating the array of equations
59+
As you can see, generating an ODESystem is as simple as creating an array of equations
5260
and passing it to the `ODESystem` constructor.
5361

5462
## Understanding the Difference Between the Julia Variable and the Symbolic Variable

src/systems/model_parsing.jl

Lines changed: 41 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -180,16 +180,6 @@ function update_kwargs_and_metadata!(dict, kwargs, a, def, indices, type, var,
180180
end
181181
end
182182

183-
function unit_handled_variable_value(mod, y, varname)
184-
meta = parse_metadata(mod, y)
185-
varval = if meta isa Nothing || get(meta, VariableUnit, nothing) isa Nothing
186-
varname
187-
else
188-
:($convert_units($(meta[VariableUnit]), $varname))
189-
end
190-
return varval
191-
end
192-
193183
function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types;
194184
def = nothing, indices::Union{Vector{UnitRange{Int}}, Nothing} = nothing,
195185
type::Type = Real, meta = Dict{DataType, Expr}())
@@ -232,66 +222,6 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types;
232222
varclass, where_types, meta)
233223
return var, def, Dict()
234224
end
235-
Expr(:tuple, Expr(:(::), Expr(:ref, a, b...), type), y) || Expr(:tuple, Expr(:ref, a, b...), y) => begin
236-
(@isdefined type) || (type = Real)
237-
varname = Meta.isexpr(a, :call) ? a.args[1] : a
238-
push!(kwargs, Expr(:kw, varname, nothing))
239-
varval = unit_handled_variable_value(mod, y, varname)
240-
if varclass == :parameters
241-
var = :($varname = $first(@parameters $a[$(b...)]::$type = ($varval, $y)))
242-
else
243-
var = :($varname = $first(@variables $a[$(b...)]::$type = ($varval, $y)))
244-
end
245-
#TODO: update `dict` aka `Model.structure` with the metadata
246-
(:($varname...), var), nothing, Dict()
247-
end
248-
Expr(:(=), Expr(:(::), Expr(:ref, a, b...), type), y) || Expr(:(=), Expr(:ref, a, b...), y) => begin
249-
(@isdefined type) || (type = Real)
250-
varname = Meta.isexpr(a, :call) ? a.args[1] : a
251-
if Meta.isexpr(y, :tuple)
252-
varval = unit_handled_variable_value(mod, y, varname)
253-
val, y = (y.args[1], y.args[2:end])
254-
push!(kwargs, Expr(:kw, varname, nothing))
255-
if varclass == :parameters
256-
var = :($varname = $varname === nothing ? $val : $varname;
257-
$varname = $first(@parameters $a[$(b...)]::$type = (
258-
$varval, $(y...))))
259-
else
260-
var = :($varname = $varname === nothing ? $val : $varname;
261-
$varname = $first(@variables $a[$(b...)]::$type = (
262-
$varval, $(y...))))
263-
end
264-
else
265-
push!(kwargs, Expr(:kw, varname, nothing))
266-
if varclass == :parameters
267-
var = :($varname = $varname === nothing ? $y : $varname;
268-
$varname = $first(@parameters $a[$(b...)]::$type = $varname))
269-
else
270-
var = :($varname = $varname === nothing ? $y : $varname;
271-
$varname = $first(@variables $a[$(b...)]::$type = $varname))
272-
end
273-
end
274-
#TODO: update `dict`` aka `Model.structure` with the metadata
275-
(:($varname...), var), nothing, Dict()
276-
end
277-
Expr(:(::), Expr(:ref, a, b...), type) || Expr(:ref, a, b...) => begin
278-
(@isdefined type) || (type = Real)
279-
varname = a isa Expr && a.head == :call ? a.args[1] : a
280-
push!(kwargs, Expr(:kw, varname, nothing))
281-
if varclass == :parameters
282-
var = :($varname = $first(@parameters $a[$(b...)]::$type = $varname))
283-
elseif varclass == :variables
284-
var = :($varname = $first(@variables $a[$(b...)]::$type = $varname))
285-
else
286-
throw("Symbolic array with arbitrary length is not handled for $varclass.
287-
Please open an issue with an example.")
288-
end
289-
dict[varclass] = get!(dict, varclass) do
290-
Dict{Symbol, Dict{Symbol, Any}}()
291-
end
292-
# dict[:kwargs][varname] = dict[varclass][varname] = Dict(:size => b)
293-
(:($varname...), var), nothing, Dict()
294-
end
295225
Expr(:(=), a, b) => begin
296226
Base.remove_linenums!(b)
297227
def, meta = parse_default(mod, b)
@@ -338,6 +268,11 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types;
338268
end
339269
return var, def, Dict()
340270
end
271+
Expr(:ref, a, b...) => begin
272+
indices = map(i -> UnitRange(i.args[2], i.args[end]), b)
273+
parse_variable_def!(dict, mod, a, varclass, kwargs, where_types;
274+
def, indices, type, meta)
275+
end
341276
_ => error("$arg cannot be parsed")
342277
end
343278
end
@@ -445,23 +380,14 @@ function parse_default(mod, a)
445380
end
446381
end
447382

448-
function parse_metadata(mod, a::Expr)
383+
function parse_metadata(mod, a)
449384
MLStyle.@match a begin
450-
Expr(:vect, b...) => Dict(parse_metadata(mod, m) for m in b)
451-
Expr(:tuple, a, b...) => parse_metadata(mod, b)
385+
Expr(:vect, eles...) => Dict(parse_metadata(mod, e) for e in eles)
452386
Expr(:(=), a, b) => Symbolics.option_to_metadata_type(Val(a)) => get_var(mod, b)
453387
_ => error("Cannot parse metadata $a")
454388
end
455389
end
456390

457-
function parse_metadata(mod, metadata::AbstractArray)
458-
ret = Dict()
459-
for m in metadata
460-
merge!(ret, parse_metadata(mod, m))
461-
end
462-
ret
463-
end
464-
465391
function _set_var_metadata!(metadata_with_exprs, a, m, v::Expr)
466392
push!(metadata_with_exprs, m => v)
467393
a
@@ -719,7 +645,6 @@ function parse_variable_arg!(exprs, vs, dict, mod, arg, varclass, kwargs, where_
719645
end
720646

721647
function convert_units(varunits::DynamicQuantities.Quantity, value)
722-
value isa Nothing && return nothing
723648
DynamicQuantities.ustrip(DynamicQuantities.uconvert(
724649
DynamicQuantities.SymbolicUnits.as_quantity(varunits), value))
725650
end
@@ -731,7 +656,6 @@ function convert_units(
731656
end
732657

733658
function convert_units(varunits::Unitful.FreeUnits, value)
734-
value isa Nothing && return nothing
735659
Unitful.ustrip(varunits, value)
736660
end
737661

@@ -750,50 +674,47 @@ end
750674
function parse_variable_arg(dict, mod, arg, varclass, kwargs, where_types)
751675
vv, def, metadata_with_exprs = parse_variable_def!(
752676
dict, mod, arg, varclass, kwargs, where_types)
753-
if !(vv isa Tuple)
754-
name = getname(vv)
755-
varexpr = if haskey(metadata_with_exprs, VariableUnit)
756-
unit = metadata_with_exprs[VariableUnit]
757-
quote
758-
$name = if $name === nothing
759-
$setdefault($vv, $def)
760-
else
761-
try
762-
$setdefault($vv, $convert_units($unit, $name))
763-
catch e
764-
if isa(e, $(DynamicQuantities.DimensionError)) ||
765-
isa(e, $(Unitful.DimensionError))
766-
error("Unable to convert units for \'" * string(:($$vv)) * "\'")
767-
elseif isa(e, MethodError)
768-
error("No or invalid units provided for \'" * string(:($$vv)) *
769-
"\'")
770-
else
771-
rethrow(e)
772-
end
677+
name = getname(vv)
678+
679+
varexpr = if haskey(metadata_with_exprs, VariableUnit)
680+
unit = metadata_with_exprs[VariableUnit]
681+
quote
682+
$name = if $name === nothing
683+
$setdefault($vv, $def)
684+
else
685+
try
686+
$setdefault($vv, $convert_units($unit, $name))
687+
catch e
688+
if isa(e, $(DynamicQuantities.DimensionError)) ||
689+
isa(e, $(Unitful.DimensionError))
690+
error("Unable to convert units for \'" * string(:($$vv)) * "\'")
691+
elseif isa(e, MethodError)
692+
error("No or invalid units provided for \'" * string(:($$vv)) *
693+
"\'")
694+
else
695+
rethrow(e)
773696
end
774697
end
775698
end
776-
else
777-
quote
778-
$name = if $name === nothing
779-
$setdefault($vv, $def)
780-
else
781-
$setdefault($vv, $name)
782-
end
783-
end
784699
end
785-
786-
metadata_expr = Expr(:block)
787-
for (k, v) in metadata_with_exprs
788-
push!(metadata_expr.args,
789-
:($name = $wrap($set_scalar_metadata($unwrap($name), $k, $v))))
700+
else
701+
quote
702+
$name = if $name === nothing
703+
$setdefault($vv, $def)
704+
else
705+
$setdefault($vv, $name)
706+
end
790707
end
708+
end
791709

792-
push!(varexpr.args, metadata_expr)
793-
return vv isa Num ? name : :($name...), varexpr
794-
else
795-
return vv
710+
metadata_expr = Expr(:block)
711+
for (k, v) in metadata_with_exprs
712+
push!(metadata_expr.args,
713+
:($name = $wrap($set_scalar_metadata($unwrap($name), $k, $v))))
796714
end
715+
716+
push!(varexpr.args, metadata_expr)
717+
return vv isa Num ? name : :($name...), varexpr
797718
end
798719

799720
function handle_conditional_vars!(

0 commit comments

Comments
 (0)