Skip to content

Commit 7530435

Browse files
Merge pull request #2499 from ven-k/vkb/constant-in-mtkmodel
Parse constants in `@mtkmodel`
2 parents 8436903 + 7c377de commit 7530435

File tree

5 files changed

+89
-10
lines changed

5 files changed

+89
-10
lines changed

docs/src/basics/ContextualVariables.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ All modeling projects have some form of parameters. `@parameters` marks a variab
2020
as being the parameter of some system, which allows automatic detection algorithms
2121
to ignore such variables when attempting to find the unknowns of a system.
2222

23-
## Constants
23+
## [Constants](@id constants)
2424

2525
Constants, defined by e.g. `@constants myconst1` are like parameters that:
2626

docs/src/basics/MTKModel_Connector.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ equations.
2424
`@mtkmodel` definition contains begin blocks of
2525

2626
- `@components`: for listing sub-components of the system
27+
- `@constants`: for declaring constants
2728
- `@equations`: for the list of equations
2829
- `@extend`: for extending a base system and unpacking its unknowns
2930
- `@icon` : for embedding the model icon
@@ -52,6 +53,9 @@ end
5253
5354
@mtkmodel ModelC begin
5455
@icon "https://github.com/SciML/SciMLDocs/blob/main/docs/src/assets/logo.png"
56+
@constants begin
57+
c::Int = 1, [description = "Example constant."]
58+
end
5559
@structural_parameters begin
5660
f = sin
5761
end
@@ -107,6 +111,12 @@ end
107111
- This block is for non symbolic input arguments. 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.
108112
- Whenever default values are specified, unlike parameters/variables, they are reflected in the keyword argument list.
109113

114+
#### `@constants` begin block
115+
116+
- Declare constants in the model definition.
117+
- The values of these can't be changed by the user.
118+
- This works similar to symbolic constants described [here](@ref constants)
119+
110120
#### `@parameters` and `@variables` begin block
111121

112122
- Parameters and variables are declared with respective begin blocks.
@@ -220,7 +230,8 @@ end
220230

221231
`structure` stores metadata that describes composition of a model. It includes:
222232

223-
- `:components`: List of sub-components in the form of [[name, sub_component_name],...].
233+
- `:components`: The list of sub-components in the form of [[name, sub_component_name],...].
234+
- `:constants`: Dictionary of constants mapped to its metadata.
224235
- `:extend`: The list of extended unknowns, name given to the base system, and name of the base system.
225236
- `:structural_parameters`: Dictionary of structural parameters mapped to their metadata.
226237
- `:parameters`: Dictionary of symbolic parameters mapped to their metadata. For
@@ -239,6 +250,7 @@ Dict{Symbol, Any} with 9 entries:
239250
:components => Any[Union{Expr, Symbol}[:model_a, :ModelA]]
240251
:variables => Dict{Symbol, Dict{Symbol, Any}}(:v=>Dict(:default=>:v_var, :type=>Real), :v_array=>Dict(:type=>Real, :size=>(2, 3)))
241252
:icon => URI("https://github.com/SciML/SciMLDocs/blob/main/docs/src/assets/logo.png")
253+
:constants => Dict{Symbol, Dict}(:c=>Dict{Symbol, Any}(:value=>1, :type=>Int64, :description=>"Example constant."))
242254
:kwargs => Dict{Symbol, Dict}(:f=>Dict(:value=>:sin), :v=>Dict{Symbol, Union{Nothing, Symbol}}(:value=>:v_var, :type=>Real), :v_array=>Dict(:value=>nothing, :type=>Real), :p1=>Dict(:value=>nothing))
243255
:structural_parameters => Dict{Symbol, Dict}(:f=>Dict(:value=>:sin))
244256
:independent_variable => t

src/systems/model_parsing.jl

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ end
3636
function _model_macro(mod, name, expr, isconnector)
3737
exprs = Expr(:block)
3838
dict = Dict{Symbol, Any}(
39+
:constants => Dict{Symbol, Dict}(),
3940
:kwargs => Dict{Symbol, Dict}(),
4041
:structural_parameters => Dict{Symbol, Dict}()
4142
)
@@ -347,6 +348,8 @@ function parse_model!(exprs, comps, ext, eqs, icon, vs, ps, sps,
347348
parse_structural_parameters!(exprs, sps, dict, mod, body, kwargs)
348349
elseif mname == Symbol("@equations")
349350
parse_equations!(exprs, eqs, dict, body)
351+
elseif mname == Symbol("@constants")
352+
parse_constants!(exprs, dict, body, mod)
350353
elseif mname == Symbol("@icon")
351354
isassigned(icon) && error("This model has more than one icon.")
352355
parse_icon!(body, dict, icon, mod)
@@ -355,13 +358,53 @@ function parse_model!(exprs, comps, ext, eqs, icon, vs, ps, sps,
355358
end
356359
end
357360

361+
function parse_constants!(exprs, dict, body, mod)
362+
Base.remove_linenums!(body)
363+
for arg in body.args
364+
MLStyle.@match arg begin
365+
Expr(:(=), Expr(:(::), a, type), Expr(:tuple, b, metadata)) || Expr(:(=), Expr(:(::), a, type), b) => begin
366+
type = getfield(mod, type)
367+
b = _type_check!(get_var(mod, b), a, type, :constants)
368+
constant = first(@constants $a::type = b)
369+
push!(exprs, :($a = $constant))
370+
dict[:constants][a] = Dict(:value => b, :type => type)
371+
if @isdefined metadata
372+
for data in metadata.args
373+
dict[:constants][a][data.args[1]] = data.args[2]
374+
end
375+
end
376+
end
377+
Expr(:(=), a, Expr(:tuple, b, metadata)) => begin
378+
constant = first(@constants $a = b)
379+
push!(exprs, :($a = $constant))
380+
dict[:constants][a] = Dict{Symbol, Any}(:value => get_var(mod, b))
381+
for data in metadata.args
382+
dict[:constants][a][data.args[1]] = data.args[2]
383+
end
384+
end
385+
Expr(:(=), a, b) => begin
386+
constant = first(@constants $a = b)
387+
push!(exprs, :($a = $constant))
388+
dict[:constants][a] = Dict(:value => get_var(mod, b))
389+
end
390+
_ => error("""Malformed constant definition `$arg`. Please use the following syntax:
391+
```
392+
@constants begin
393+
var = value, [description = "This is an example constant."]
394+
end
395+
```
396+
""")
397+
end
398+
end
399+
end
400+
358401
function parse_structural_parameters!(exprs, sps, dict, mod, body, kwargs)
359402
Base.remove_linenums!(body)
360403
for arg in body.args
361404
MLStyle.@match arg begin
362405
Expr(:(=), Expr(:(::), a, type), b) => begin
363-
type = Core.eval(mod, type)
364-
b = _type_check!(Core.eval(mod, b), a, type, :structural_parameters)
406+
type = getfield(mod, type)
407+
b = _type_check!(get_var(mod, b), a, type, :structural_parameters)
365408
push!(sps, a)
366409
push!(kwargs, Expr(:kw, Expr(:(::), a, type), b))
367410
dict[:structural_parameters][a] = dict[:kwargs][a] = Dict(
@@ -922,16 +965,15 @@ function parse_conditional_model_statements(comps, dict, eqs, exprs, kwargs, mod
922965
end))
923966
end
924967

925-
function _type_check!(val, a, type, varclass)
968+
function _type_check!(val, a, type, class)
926969
if val isa type
927970
return val
928971
else
929972
try
930973
return convert(type, val)
931-
catch
932-
(e)
974+
catch e
933975
throw(TypeError(Symbol("`@mtkmodel`"),
934-
"`$varclass`, while assigning to `$a`", type, typeof(val)))
976+
"`$class`, while assigning to `$a`", type, typeof(val)))
935977
end
936978
end
937979
end

test/model_parsing.jl

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,23 @@ resistor = getproperty(rc, :resistor; namespace = false)
180180

181181
@test length(equations(rc)) == 1
182182

183+
@testset "Constants" begin
184+
@mtkmodel PiModel begin
185+
@constants begin
186+
_p::Irrational = π, [description = "Value of Pi."]
187+
end
188+
@parameters begin
189+
p = _p, [description = "Assign constant `_p` value."]
190+
end
191+
end
192+
193+
@named pi_model = PiModel()
194+
195+
@test typeof(ModelingToolkit.getdefault(pi_model.p)) <:
196+
SymbolicUtils.BasicSymbolic{Irrational}
197+
@test getdefault(getdefault(pi_model.p)) == π
198+
end
199+
183200
@testset "Parameters and Structural parameters in various modes" begin
184201
@mtkmodel MockModel begin
185202
@parameters begin
@@ -400,14 +417,19 @@ end
400417
module PrecompilationTest
401418
push!(LOAD_PATH, joinpath(@__DIR__, "precompile_test"))
402419
using Unitful, Test, ModelParsingPrecompile, ModelingToolkit
420+
using ModelingToolkit: getdefault, scalarize
403421
@testset "Precompile packages with MTKModels" begin
404422
using ModelParsingPrecompile: ModelWithComponentArray
405423

406424
@named model_with_component_array = ModelWithComponentArray()
407425

408-
@test ModelWithComponentArray.structure[:parameters][:R][:unit] == u""
426+
@test ModelWithComponentArray.structure[:parameters][:r][:unit] == u""
409427
@test lastindex(parameters(model_with_component_array)) == 3
410428

429+
# Test the constant `k`. Manually k's value should be kept in sync here
430+
# and the ModelParsingPrecompile.
431+
@test all(getdefault.(getdefault.(scalarize(model_with_component_array.r))) .== 1)
432+
411433
pop!(LOAD_PATH)
412434
end
413435
end

test/precompile_test/ModelParsingPrecompile.jl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ module ModelParsingPrecompile
33
using ModelingToolkit, Unitful
44

55
@mtkmodel ModelWithComponentArray begin
6+
@constants begin
7+
k = 1, [description = "Default val of R"]
8+
end
69
@parameters begin
7-
R(t)[1:3] = 1, [description = "Parameter array", unit = u""]
10+
r(t)[1:3] = k, [description = "Parameter array", unit = u""]
811
end
912
end
1013

0 commit comments

Comments
 (0)