Skip to content

Commit 07701c4

Browse files
authored
Merge pull request #3092 from SciML/revert-3055-vkb/array-length-as-input
Revert "feat: allow users to set array length via args in `@mtkmodel`"
2 parents 85d8d10 + 21dc079 commit 07701c4

File tree

3 files changed

+52
-175
lines changed

3 files changed

+52
-175
lines changed

docs/src/basics/MTKLanguage.md

+8-9
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]

src/systems/model_parsing.jl

+41-120
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!(

test/model_parsing.jl

+3-46
Original file line numberDiff line numberDiff line change
@@ -259,8 +259,7 @@ end
259259
@test all(collect(hasmetadata.(model.l, ModelingToolkit.VariableDescription)))
260260

261261
@test all(lastindex.([model.a2, model.b2, model.d2, model.e2, model.h2]) .== 2)
262-
@test size(model.l) == (2, 3)
263-
@test_broken MockModel.structure[:parameters][:l][:size] == (2, 3)
262+
@test size(model.l) == MockModel.structure[:parameters][:l][:size] == (2, 3)
264263

265264
model = complete(model)
266265
@test getdefault(model.cval) == 1
@@ -314,6 +313,7 @@ end
314313
@test_throws TypeError TypeModel(; name = :throws, par3 = true)
315314
@test_throws TypeError TypeModel(; name = :throws, par4 = true)
316315
# par7 should be an AbstractArray of BigFloat.
316+
@test_throws MethodError TypeModel(; name = :throws, par7 = rand(Int, 3, 3))
317317

318318
# Test that array types are correctly added.
319319
@named type_model2 = TypeModel(; par5 = rand(BigFloat, 3))
@@ -474,8 +474,7 @@ using ModelingToolkit: getdefault, scalarize
474474

475475
@named model_with_component_array = ModelWithComponentArray()
476476

477-
@test_broken eval(ModelWithComponentArray.structure[:parameters][:r][:unit]) ==
478-
eval(u"")
477+
@test eval(ModelWithComponentArray.structure[:parameters][:r][:unit]) == eval(u"")
479478
@test lastindex(parameters(model_with_component_array)) == 3
480479

481480
# Test the constant `k`. Manually k's value should be kept in sync here
@@ -877,45 +876,3 @@ end
877876
end),
878877
false)
879878
end
880-
881-
@testset "Array Length as an Input" begin
882-
@mtkmodel VaryingLengthArray begin
883-
@structural_parameters begin
884-
N
885-
M
886-
end
887-
@parameters begin
888-
p1[1:N]
889-
p2[1:N, 1:M]
890-
end
891-
@variables begin
892-
v1(t)[1:N]
893-
v2(t)[1:N, 1:M]
894-
end
895-
end
896-
897-
@named model = VaryingLengthArray(N = 2, M = 3)
898-
@test length(model.p1) == 2
899-
@test size(model.p2) == (2, 3)
900-
@test length(model.v1) == 2
901-
@test size(model.v2) == (2, 3)
902-
903-
@mtkmodel WithMetadata begin
904-
@structural_parameters begin
905-
N
906-
end
907-
@parameters begin
908-
p_only_default[1:N] = 101
909-
p_only_metadata[1:N], [description = "this only has metadata"]
910-
p_both_default_and_metadata[1:N] = 102,
911-
[description = "this has both default value and metadata"]
912-
end
913-
end
914-
915-
@named with_metadata = WithMetadata(N = 10)
916-
@test getdefault(with_metadata.p_only_default) == 101
917-
@test getdescription(with_metadata.p_only_metadata) == "this only has metadata"
918-
@test getdefault(with_metadata.p_both_default_and_metadata) == 102
919-
@test getdescription(with_metadata.p_both_default_and_metadata) ==
920-
"this has both default value and metadata"
921-
end

0 commit comments

Comments
 (0)