From ae22c7127fef1210f2d4c17210d45d60a5d58933 Mon Sep 17 00:00:00 2001 From: Venkateshprasad <32921645+ven-k@users.noreply.github.com> Date: Tue, 8 Oct 2024 14:06:52 +0000 Subject: [PATCH 01/11] feat: add helpers to update symbolic array metadata --- src/systems/model_parsing.jl | 64 +++++++++++++++++++++++++++++++++--- test/model_parsing.jl | 6 ++-- 2 files changed, 64 insertions(+), 6 deletions(-) diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index 7b6705bbd5..8a0f5165e4 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -163,7 +163,7 @@ function update_kwargs_and_metadata!(dict, kwargs, a, def, indices, type, var, push!(kwargs, Expr(:kw, :($a::Union{Nothing, Missing, $NoValue, $type}), NO_VALUE)) end - dict[:kwargs][getname(var)] = Dict(:value => def, :type => type) + dict[:kwargs][a] = Dict(:value => def, :type => type) else vartype = gensym(:T) push!(kwargs, @@ -177,15 +177,71 @@ function update_kwargs_and_metadata!(dict, kwargs, a, def, indices, type, var, else push!(where_types, :($vartype <: $type)) end - dict[:kwargs][getname(var)] = Dict(:value => def, :type => AbstractArray{type}) + dict[:kwargs][a] = Dict(:value => def, :type => AbstractArray{type}) end if dict[varclass] isa Vector - dict[varclass][1][getname(var)][:type] = AbstractArray{type} + dict[varclass][1][a][:type] = AbstractArray{type} else - dict[varclass][getname(var)][:type] = type + dict[varclass][a][:type] = type end end +function update_readable_metadata!(varclass_dict, meta::Dict, varname) + metatypes = [(:connection_type, VariableConnectType), + (:description, VariableDescription), + (:unit, VariableUnit), + (:bounds, VariableBounds), + (:noise, VariableNoiseType), + (:input, VariableInput), + (:output, VariableOutput), + (:irreducible, VariableIrreducible), + (:state_priority, VariableStatePriority), + (:misc, VariableMisc), + (:disturbance, VariableDisturbance), + (:tunable, VariableTunable), + (:dist, VariableDistribution)] + + var_dict = get!(varclass_dict, varname) do + Dict{Symbol, Any}() + end + + for (type, key) in metatypes + if (mt = get(meta, key, nothing)) !== nothing + key == VariableConnectType && (mt = nameof(mt)) + var_dict[type] = mt + end + end +end + +function push_array_kwargs_and_metadata!( + dict, indices, meta, type, varclass, varname, varval) + dict[varclass] = get!(dict, varclass) do + Dict{Symbol, Dict{Symbol, Any}}() + end + varclass_dict = dict[varclass] isa Vector ? Ref(dict[varclass][1]) : Ref(dict[varclass]) + + merge!(varclass_dict[], + Dict(varname => Dict( + :size => tuple([index_arg.args[end] for index_arg in indices]...), + :value => varval, + :type => type + ))) + + # Useful keys for kwargs entry are: value, type and size. + dict[:kwargs][varname] = varclass_dict[][varname] + + meta !== nothing && update_readable_metadata!(varclass_dict[], meta, varname) +end + +function unit_handled_variable_value(meta, varname) + varval = if meta isa Nothing || get(meta, VariableUnit, nothing) isa Nothing + varname + else + :($convert_units($(meta[VariableUnit]), $varname)) + end + return varval +end + function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; def = nothing, indices::Union{Vector{UnitRange{Int}}, Nothing} = nothing, type::Type = Real, meta = Dict{DataType, Expr}()) diff --git a/test/model_parsing.jl b/test/model_parsing.jl index f80dc77dc4..727954a00d 100644 --- a/test/model_parsing.jl +++ b/test/model_parsing.jl @@ -259,7 +259,8 @@ end @test all(collect(hasmetadata.(model.l, ModelingToolkit.VariableDescription))) @test all(lastindex.([model.a2, model.b2, model.d2, model.e2, model.h2]) .== 2) - @test size(model.l) == MockModel.structure[:parameters][:l][:size] == (2, 3) + @test size(model.l) == (2, 3) + @test MockModel.structure[:parameters][:l][:size] == (2, 3) model = complete(model) @test getdefault(model.cval) == 1 @@ -474,7 +475,8 @@ using ModelingToolkit: getdefault, scalarize @named model_with_component_array = ModelWithComponentArray() - @test eval(ModelWithComponentArray.structure[:parameters][:r][:unit]) == eval(u"Ω") + @test eval(ModelWithComponentArray.structure[:parameters][:r][:unit]) == + eval(u"Ω") @test lastindex(parameters(model_with_component_array)) == 3 # Test the constant `k`. Manually k's value should be kept in sync here From b0ab722aa5e9412a615b3e73d78bd2048a9755a2 Mon Sep 17 00:00:00 2001 From: Venkateshprasad <32921645+ven-k@users.noreply.github.com> Date: Tue, 8 Oct 2024 14:09:58 +0000 Subject: [PATCH 02/11] feat: support arbitrary length arrays with metadata and default Update metadata dict and kwargs for symbolic arrays --- src/systems/model_parsing.jl | 207 +++++++++++++++++++++-------------- 1 file changed, 127 insertions(+), 80 deletions(-) diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index 8a0f5165e4..04b133ba87 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -245,20 +245,6 @@ end function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; def = nothing, indices::Union{Vector{UnitRange{Int}}, Nothing} = nothing, type::Type = Real, meta = Dict{DataType, Expr}()) - metatypes = [(:connection_type, VariableConnectType), - (:description, VariableDescription), - (:unit, VariableUnit), - (:bounds, VariableBounds), - (:noise, VariableNoiseType), - (:input, VariableInput), - (:output, VariableOutput), - (:irreducible, VariableIrreducible), - (:state_priority, VariableStatePriority), - (:misc, VariableMisc), - (:disturbance, VariableDisturbance), - (:tunable, VariableTunable), - (:dist, VariableDistribution)] - arg isa LineNumberNode && return MLStyle.@match arg begin a::Symbol => begin @@ -284,27 +270,86 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; varclass, where_types, meta) return var, def, Dict() end + Expr(:tuple, Expr(:(::), Expr(:ref, a, indices...), type), meta_val) || + Expr(:tuple, Expr(:ref, a, indices...), meta_val) => begin + (@isdefined type) || (type = Real) + varname = Meta.isexpr(a, :call) ? a.args[1] : a + push!(kwargs, Expr(:kw, varname, nothing)) + meta = parse_metadata(mod, meta_val) + varval = (@isdefined default_val) ? default_val : + unit_handled_variable_value(meta, varname) + if varclass == :parameters + var = :($varname = $first(@parameters ($a[$(indices...)]::$type = $varval), + $meta_val)) + else + var = :($varname = $first(@variables ($a[$(indices)]::$type = $varval), + $meta_val)) + end + push_array_kwargs_and_metadata!( + dict, indices, meta, type, varclass, varname, varval) + (:($varname...), var), nothing, Dict() + end + Expr(:(=), Expr(:(::), Expr(:ref, a, indices...), type), def_n_meta) || + Expr(:(=), Expr(:ref, a, indices...), def_n_meta) => begin + (@isdefined type) || (type = Real) + varname = Meta.isexpr(a, :call) ? a.args[1] : a + if Meta.isexpr(def_n_meta, :tuple) + meta = parse_metadata(mod, def_n_meta) + varval = unit_handled_variable_value(meta, varname) + val, def_n_meta = (def_n_meta.args[1], def_n_meta.args[2:end]) + push!(kwargs, Expr(:kw, varname, nothing)) + if varclass == :parameters + var = :($varname = $varname === nothing ? $val : $varname; + $varname = $first(@parameters ($a[$(indices...)]::$type = $varval), + $(def_n_meta...))) + else + var = :($varname = $varname === nothing ? $val : $varname; + $varname = $first(@variables $a[$(indices...)]::$type = ( + $varval), + $(def_n_meta...))) + end + else + push!(kwargs, Expr(:kw, varname, nothing)) + if varclass == :parameters + var = :($varname = $varname === nothing ? $def_n_meta : $varname; + $varname = $first(@parameters $a[$(indices...)]::$type = $varname)) + else + var = :($varname = $varname === nothing ? $def_n_meta : $varname; + $varname = $first(@variables $a[$(indices...)]::$type = $varname)) + end + varval, meta = def_n_meta, nothing + end + push_array_kwargs_and_metadata!( + dict, indices, meta, type, varclass, varname, varval) + (:($varname...), var), nothing, Dict() + end + Expr(:(::), Expr(:ref, a, indices...), type) || + Expr(:ref, a, indices...) => begin + (@isdefined type) || (type = Real) + varname = a isa Expr && a.head == :call ? a.args[1] : a + push!(kwargs, Expr(:kw, varname, nothing)) + if varclass == :parameters + var = :($varname = $first(@parameters $a[$(indices...)]::$type = $varname)) + elseif varclass == :variables + var = :($varname = $first(@variables $a[$(indices...)]::$type = $varname)) + else + throw("Symbolic array with arbitrary length is not handled for $varclass. + Please open an issue with an example.") + end + push_array_kwargs_and_metadata!( + dict, indices, nothing, type, varclass, varname, nothing) + (:($varname...), var), nothing, Dict() + end Expr(:(=), a, b) => begin Base.remove_linenums!(b) def, meta = parse_default(mod, b) var, def, _ = parse_variable_def!( dict, mod, a, varclass, kwargs, where_types; def, type, meta) - if dict[varclass] isa Vector - dict[varclass][1][getname(var)][:default] = def - else - dict[varclass][getname(var)][:default] = def - end + varclass_dict = dict[varclass] isa Vector ? Ref(dict[varclass][1]) : + Ref(dict[varclass]) + varclass_dict[][getname(var)][:default] = def if meta !== nothing - for (type, key) in metatypes - if (mt = get(meta, key, nothing)) !== nothing - key == VariableConnectType && (mt = nameof(mt)) - if dict[varclass] isa Vector - dict[varclass][1][getname(var)][type] = mt - else - dict[varclass][getname(var)][type] = mt - end - end - end + update_readable_metadata!(varclass_dict[], meta, getname(var)) var, metadata_with_exprs = set_var_metadata(var, meta) return var, def, metadata_with_exprs end @@ -314,27 +359,15 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; meta = parse_metadata(mod, b) var, def, _ = parse_variable_def!( dict, mod, a, varclass, kwargs, where_types; type, meta) + varclass_dict = dict[varclass] isa Vector ? Ref(dict[varclass][1]) : + Ref(dict[varclass]) if meta !== nothing - for (type, key) in metatypes - if (mt = get(meta, key, nothing)) !== nothing - key == VariableConnectType && (mt = nameof(mt)) - if dict[varclass] isa Vector - dict[varclass][1][getname(var)][type] = mt - else - dict[varclass][getname(var)][type] = mt - end - end - end + update_readable_metadata!(varclass_dict[], meta, getname(var)) var, metadata_with_exprs = set_var_metadata(var, meta) return var, def, metadata_with_exprs end return var, def, Dict() end - Expr(:ref, a, b...) => begin - indices = map(i -> UnitRange(i.args[2], i.args[end]), b) - parse_variable_def!(dict, mod, a, varclass, kwargs, where_types; - def, indices, type, meta) - end _ => error("$arg cannot be parsed") end end @@ -442,14 +475,23 @@ function parse_default(mod, a) end end -function parse_metadata(mod, a) +function parse_metadata(mod, a::Expr) MLStyle.@match a begin - Expr(:vect, eles...) => Dict(parse_metadata(mod, e) for e in eles) + Expr(:vect, b...) => Dict(parse_metadata(mod, m) for m in b) + Expr(:tuple, a, b...) => parse_metadata(mod, b) Expr(:(=), a, b) => Symbolics.option_to_metadata_type(Val(a)) => get_var(mod, b) _ => error("Cannot parse metadata $a") end end +function parse_metadata(mod, metadata::AbstractArray) + ret = Dict() + for m in metadata + merge!(ret, parse_metadata(mod, m)) + end + ret +end + function _set_var_metadata!(metadata_with_exprs, a, m, v::Expr) push!(metadata_with_exprs, m => v) a @@ -707,6 +749,7 @@ function parse_variable_arg!(exprs, vs, dict, mod, arg, varclass, kwargs, where_ end function convert_units(varunits::DynamicQuantities.Quantity, value) + value isa Nothing && return nothing DynamicQuantities.ustrip(DynamicQuantities.uconvert( DynamicQuantities.SymbolicUnits.as_quantity(varunits), value)) end @@ -718,6 +761,7 @@ function convert_units( end function convert_units(varunits::Unitful.FreeUnits, value) + value isa Nothing && return nothing Unitful.ustrip(varunits, value) end @@ -736,47 +780,50 @@ end function parse_variable_arg(dict, mod, arg, varclass, kwargs, where_types) vv, def, metadata_with_exprs = parse_variable_def!( dict, mod, arg, varclass, kwargs, where_types) - name = getname(vv) - - varexpr = if haskey(metadata_with_exprs, VariableUnit) - unit = metadata_with_exprs[VariableUnit] - quote - $name = if $name === $NO_VALUE - $setdefault($vv, $def) - else - try - $setdefault($vv, $convert_units($unit, $name)) - catch e - if isa(e, $(DynamicQuantities.DimensionError)) || - isa(e, $(Unitful.DimensionError)) - error("Unable to convert units for \'" * string(:($$vv)) * "\'") - elseif isa(e, MethodError) - error("No or invalid units provided for \'" * string(:($$vv)) * - "\'") - else - rethrow(e) + if !(vv isa Tuple) + name = getname(vv) + varexpr = if haskey(metadata_with_exprs, VariableUnit) + unit = metadata_with_exprs[VariableUnit] + quote + $name = if $name === $NO_VALUE + $setdefault($vv, $def) + else + try + $setdefault($vv, $convert_units($unit, $name)) + catch e + if isa(e, $(DynamicQuantities.DimensionError)) || + isa(e, $(Unitful.DimensionError)) + error("Unable to convert units for \'" * string(:($$vv)) * "\'") + elseif isa(e, MethodError) + error("No or invalid units provided for \'" * string(:($$vv)) * + "\'") + else + rethrow(e) + end end end end - end - else - quote - $name = if $name === $NO_VALUE - $setdefault($vv, $def) - else - $setdefault($vv, $name) + else + quote + $name = if $name === $NO_VALUE + $setdefault($vv, $def) + else + $setdefault($vv, $name) + end end end - end - metadata_expr = Expr(:block) - for (k, v) in metadata_with_exprs - push!(metadata_expr.args, - :($name = $wrap($set_scalar_metadata($unwrap($name), $k, $v)))) - end + metadata_expr = Expr(:block) + for (k, v) in metadata_with_exprs + push!(metadata_expr.args, + :($name = $wrap($set_scalar_metadata($unwrap($name), $k, $v)))) + end - push!(varexpr.args, metadata_expr) - return vv isa Num ? name : :($name...), varexpr + push!(varexpr.args, metadata_expr) + return vv isa Num ? name : :($name...), varexpr + else + return vv + end end function handle_conditional_vars!( From a403880cc92493fd3bd980fc1c72bba6e50eb1a5 Mon Sep 17 00:00:00 2001 From: Venkateshprasad <32921645+ven-k@users.noreply.github.com> Date: Mon, 23 Sep 2024 20:32:56 +0000 Subject: [PATCH 03/11] docs: use symbolic array with arbitray length in ModelC example --- docs/src/basics/MTKLanguage.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/src/basics/MTKLanguage.md b/docs/src/basics/MTKLanguage.md index a2fb7d0870..a656b24096 100644 --- a/docs/src/basics/MTKLanguage.md +++ b/docs/src/basics/MTKLanguage.md @@ -63,13 +63,14 @@ end @structural_parameters begin f = sin N = 2 + M = 3 end begin v_var = 1.0 end @variables begin v(t) = v_var - v_array(t)[1:2, 1:3] + v_array(t)[1:N, 1:M] v_for_defaults(t) end @extend ModelB(; p1) @@ -324,10 +325,10 @@ For example, the structure of `ModelC` is: julia> ModelC.structure Dict{Symbol, Any} with 10 entries: :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)]] - :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)) + :variables => Dict{Symbol, Dict{Symbol, Any}}(:v=>Dict(:default=>:v_var, :type=>Real), :v_for_defaults=>Dict(:type=>Real)) :icon => URI("https://github.com/SciML/SciMLDocs/blob/main/docs/src/assets/logo.png") - :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)) - :structural_parameters => Dict{Symbol, Dict}(:f=>Dict(:value=>:sin), :N=>Dict(:value=>2)) + :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)), + :structural_parameters => Dict{Symbol, Dict}(:f => Dict(:value => :sin), :N => Dict(:value => 2), :M => Dict(:value => 3)) :independent_variable => t :constants => Dict{Symbol, Dict}(:c=>Dict{Symbol, Any}(:value=>1, :type=>Int64, :description=>"Example constant.")) :extend => Any[[:p2, :p1], Symbol("#mtkmodel__anonymous__ModelB"), :ModelB] From 9f4e7b8f92d2efa50c2c359d41dac5e95ce8c860 Mon Sep 17 00:00:00 2001 From: Venkateshprasad <32921645+ven-k@users.noreply.github.com> Date: Tue, 8 Oct 2024 14:12:20 +0000 Subject: [PATCH 04/11] feat: parity in symbolic-array definition with vanilla `@variables` --- src/systems/model_parsing.jl | 2 ++ test/model_parsing.jl | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index 04b133ba87..3e011b044a 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -270,6 +270,8 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; varclass, where_types, meta) return var, def, Dict() end + Expr(:tuple, Expr(:(=), Expr(:ref, a, indices...), default_val), meta_val) || + Expr(:tuple, Expr(:(=), Expr(:(::), Expr(:ref, a, indices...), type), default_val), meta_val) || Expr(:tuple, Expr(:(::), Expr(:ref, a, indices...), type), meta_val) || Expr(:tuple, Expr(:ref, a, indices...), meta_val) => begin (@isdefined type) || (type = Real) diff --git a/test/model_parsing.jl b/test/model_parsing.jl index 727954a00d..c108b88625 100644 --- a/test/model_parsing.jl +++ b/test/model_parsing.jl @@ -279,6 +279,21 @@ end @test MockModel.structure[:defaults] == Dict(:n => 1.0, :n2 => "g()") end +@testset "Arrays using vanilla-@variable syntax" begin + @mtkmodel TupleInArrayDef begin + @parameters begin + (l(t)[1:2, 1:3] = 1), [description = "l is more than 1D"] + (l2(t)[1:3] = 2), [description = "l2 is 1D"] + (l3(t)[1:3]::Int = 3), [description = "l3 is 1D and has a type"] + end + end + + @named arr = TupleInArrayDef() + @test getdefault(arr.l) == 1 + @test getdefault(arr.l2) == 2 + @test getdefault(arr.l3) == 3 +end + @testset "Type annotation" begin @mtkmodel TypeModel begin @structural_parameters begin From d7d82bf1ce82626de29be9c2bf1aae1349c184eb Mon Sep 17 00:00:00 2001 From: Venkateshprasad <32921645+ven-k@users.noreply.github.com> Date: Tue, 8 Oct 2024 14:46:08 +0000 Subject: [PATCH 05/11] feat: check independent-var usage in symbolic vars - Ensure `@variables` are variables of an independent var. - In both `@variables` and `@parameters` case, ensure a single indpendent var is used --- src/systems/model_parsing.jl | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index 3e011b044a..94469308e4 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -281,9 +281,13 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; varval = (@isdefined default_val) ? default_val : unit_handled_variable_value(meta, varname) if varclass == :parameters + Meta.isexpr(a, :call) && assert_unique_independent_var(dict, a.args[end]) var = :($varname = $first(@parameters ($a[$(indices...)]::$type = $varval), $meta_val)) else + Meta.isexpr(a, :call) || + throw("$a is not a variable of the independent variable") + assert_unique_independent_var(dict, a.args[end]) var = :($varname = $first(@variables ($a[$(indices)]::$type = $varval), $meta_val)) end @@ -301,10 +305,15 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; val, def_n_meta = (def_n_meta.args[1], def_n_meta.args[2:end]) push!(kwargs, Expr(:kw, varname, nothing)) if varclass == :parameters + Meta.isexpr(a, :call) && + assert_unique_independent_var(dict, a.args[end]) var = :($varname = $varname === nothing ? $val : $varname; $varname = $first(@parameters ($a[$(indices...)]::$type = $varval), $(def_n_meta...))) else + Meta.isexpr(a, :call) || + throw("$a is not a variable of the independent variable") + assert_unique_independent_var(dict, a.args[end]) var = :($varname = $varname === nothing ? $val : $varname; $varname = $first(@variables $a[$(indices...)]::$type = ( $varval), @@ -313,9 +322,14 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; else push!(kwargs, Expr(:kw, varname, nothing)) if varclass == :parameters + Meta.isexpr(a, :call) && + assert_unique_independent_var(dict, a.args[end]) var = :($varname = $varname === nothing ? $def_n_meta : $varname; $varname = $first(@parameters $a[$(indices...)]::$type = $varname)) else + Meta.isexpr(a, :call) || + throw("$a is not a variable of the independent variable") + assert_unique_independent_var(dict, a.args[end]) var = :($varname = $varname === nothing ? $def_n_meta : $varname; $varname = $first(@variables $a[$(indices...)]::$type = $varname)) end @@ -331,8 +345,12 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; varname = a isa Expr && a.head == :call ? a.args[1] : a push!(kwargs, Expr(:kw, varname, nothing)) if varclass == :parameters + Meta.isexpr(a, :call) && assert_unique_independent_var(dict, a.args[end]) var = :($varname = $first(@parameters $a[$(indices...)]::$type = $varname)) elseif varclass == :variables + Meta.isexpr(a, :call) || + throw("$a is not a variable of the independent variable") + assert_unique_independent_var(dict, a.args[end]) var = :($varname = $first(@variables $a[$(indices...)]::$type = $varname)) else throw("Symbolic array with arbitrary length is not handled for $varclass. @@ -411,14 +429,22 @@ function generate_var!(dict, a, varclass; generate_var(a, varclass; indices, type) end +function assert_unique_independent_var(dict, iv::Num) + assert_unique_independent_var(dict, nameof(iv)) +end +function assert_unique_independent_var(dict, iv) + prev_iv = get!(dict, :independent_variable) do + iv + end + prev_iv isa Num && (prev_iv = nameof(prev_iv)) + @assert isequal(iv, prev_iv) "Multiple independent variables are used in the model $(typeof(iv)) $(typeof(prev_iv))" +end + function generate_var!(dict, a, b, varclass, mod; indices::Union{Vector{UnitRange{Int}}, Nothing} = nothing, type = Real) iv = b == :t ? get_t(mod, b) : generate_var(b, :independent_variables) - prev_iv = get!(dict, :independent_variable) do - iv - end - @assert isequal(iv, prev_iv) "Multiple independent variables are used in the model" + assert_unique_independent_var(dict, iv) check_name_uniqueness(dict, a, varclass) vd = get!(dict, varclass) do Dict{Symbol, Dict{Symbol, Any}}() From 84bf015a4541037ea141c8dc861c529b1e057a80 Mon Sep 17 00:00:00 2001 From: Venkateshprasad <32921645+ven-k@users.noreply.github.com> Date: Tue, 8 Oct 2024 15:08:53 +0000 Subject: [PATCH 06/11] docs: add a dedicated section to showcase symbolic-array definition With many ways to define, this feature demands a dedicated section. --- docs/src/basics/MTKLanguage.md | 42 +++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/docs/src/basics/MTKLanguage.md b/docs/src/basics/MTKLanguage.md index a656b24096..685c549429 100644 --- a/docs/src/basics/MTKLanguage.md +++ b/docs/src/basics/MTKLanguage.md @@ -302,7 +302,7 @@ end For more examples of usage, checkout [ModelingToolkitStandardLibrary.jl](https://github.com/SciML/ModelingToolkitStandardLibrary.jl/) -## More on `Model.structure` +## [More on `Model.structure`](@id model_structure) `structure` stores metadata that describes composition of a model. It includes: @@ -336,6 +336,46 @@ Dict{Symbol, Any} with 10 entries: :equations => Any["model_a.k ~ f(v)"] ``` +### Different ways to define symbolics arrays: + +`@mtkmodel` supports symbolics arrays in both `@parameters` and `@variables`. +Using a structural parameters, symbolic arrays of arbitrary lengths can be defined. +Refer the following example for different ways to define symbolic arrays. + +```@example mtkmodel-example +@mtkmodel ModelWithArrays begin + @structural_parameters begin + N = 2 + M = 3 + end + @parameters begin + p1[1:4] + p2[1:N] + p3[1:N, 1:M] = 10, + [description = "A multi-dimensional array of arbitrary length with description"] + (p4[1:N, 1:M] = 10), + [description = "An alternate syntax for p3 to match the syntax of vanilla parameters macro"] + end + @variables begin + v1(t)[1:2] = 10, [description = "An array of variable `v1`"] + v2(t)[1:3] = [1, 2, 3] + end +end +``` + +The size of symbolic array can be accessed via `:size` key, along with other metadata (refer [More on `Model.structure`](@ref model_structure)) +of the symbolic variable. + +```julia +julia> ModelWithArrays.structure +Dict{Symbol, Any} with 5 entries: + :variables => Dict{Symbol, Dict{Symbol, Any}}(:v2 => Dict(:value => :([1, 2, 3]), :type => Real, :size => (3,)), :v1 => Dict(:value => :v1, :type => Real, :description => "An array of variable `v1`", :size => (2,))) + :kwargs => Dict{Symbol, Dict}(:p2 => Dict{Symbol, Any}(:value => nothing, :type => Real, :size => (:N,)), :v1 => Dict{Symbol, Any}(:value => :v1, :type => Real, :description => "An array of variable `v1`", :size => (2,)), :N => Dict(:value => 2), :M => Dict(:value => 3), :p4 => Dict{Symbol, Any}(:value => 10, :type => Real, :description => "An alternate syntax for p3 to match the syntax of vanilla parameters macro", :size => (:N, :M)), :v2 => Dict{Symbol, Any}(:value => :([1, 2, 3]), :type => Real, :size => (3,)), :p1 => Dict{Symbol, Any}(:value => nothing, :type => Real, :size => (4,)), :p3 => Dict{Symbol, Any}(:value => :p3, :type => Real, :description => "A multi-dimensional array of arbitrary length with description", :size => (:N, :M))) + :structural_parameters => Dict{Symbol, Dict}(:N => Dict(:value => 2), :M => Dict(:value => 3)) + :independent_variable => :t + :parameters => Dict{Symbol, Dict{Symbol, Any}}(:p2 => Dict(:value => nothing, :type => Real, :size => (:N,)), :p4 => Dict(:value => 10, :type => Real, :description => "An alternate syntax for p3 to match the syntax of vanilla parameters macro", :size => (:N, :M)), :p1 => Dict(:value => nothing, :type => Real, :size => (4,)), :p3 => Dict(:value => :p3, :type => Real, :description => "A multi-dimensional array of arbitrary length with description", :size => (:N, :M)))), false) +``` + ### Using conditional statements #### Conditional elements of the system From 7b9a234c7a850424f44b784e80b59744554588cf Mon Sep 17 00:00:00 2001 From: Venkateshprasad <32921645+ven-k@users.noreply.github.com> Date: Tue, 8 Oct 2024 18:15:49 +0000 Subject: [PATCH 07/11] feat: update the vartype of kwargs rel. to symbolic-array While here, removes passing `indices` around to generate_var, update_kwargs_and_metadata! and parse_variable_def! as these no longer handles symbolic-arrays. --- src/systems/model_parsing.jl | 84 ++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 48 deletions(-) diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index 94469308e4..7e8eed9900 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -151,34 +151,18 @@ pop_structure_dict!(dict, key) = length(dict[key]) == 0 && pop!(dict, key) struct NoValue end const NO_VALUE = NoValue() -function update_kwargs_and_metadata!(dict, kwargs, a, def, indices, type, var, +function update_kwargs_and_metadata!(dict, kwargs, a, def, type, varclass, where_types, meta) - if indices isa Nothing - if !isnothing(meta) && haskey(meta, VariableUnit) - uvar = gensym() - push!(where_types, uvar) - push!(kwargs, - Expr(:kw, :($a::Union{Nothing, Missing, $NoValue, $uvar}), NO_VALUE)) - else - push!(kwargs, - Expr(:kw, :($a::Union{Nothing, Missing, $NoValue, $type}), NO_VALUE)) - end - dict[:kwargs][a] = Dict(:value => def, :type => type) + if !isnothing(meta) && haskey(meta, VariableUnit) + uvar = gensym() + push!(where_types, uvar) + push!(kwargs, + Expr(:kw, :($a::Union{Nothing, Missing, $NoValue, $uvar}), NO_VALUE)) else - vartype = gensym(:T) push!(kwargs, - Expr(:kw, - Expr(:(::), a, - Expr(:curly, :Union, :Nothing, :Missing, NoValue, - Expr(:curly, :AbstractArray, vartype))), - NO_VALUE)) - if !isnothing(meta) && haskey(meta, VariableUnit) - push!(where_types, vartype) - else - push!(where_types, :($vartype <: $type)) - end - dict[:kwargs][a] = Dict(:value => def, :type => AbstractArray{type}) + Expr(:kw, :($a::Union{Nothing, Missing, $NoValue, $type}), NO_VALUE)) end + dict[:kwargs][a] = Dict(:value => def, :type => type) if dict[varclass] isa Vector dict[varclass][1][a][:type] = AbstractArray{type} else @@ -213,8 +197,8 @@ function update_readable_metadata!(varclass_dict, meta::Dict, varname) end end -function push_array_kwargs_and_metadata!( - dict, indices, meta, type, varclass, varname, varval) +function update_array_kwargs_and_metadata!( + dict, indices, kwargs, meta, type, varclass, varname, varval, where_types) dict[varclass] = get!(dict, varclass) do Dict{Symbol, Dict{Symbol, Any}}() end @@ -227,6 +211,18 @@ function push_array_kwargs_and_metadata!( :type => type ))) + vartype = gensym(:T) + push!(kwargs, + Expr(:kw, + Expr(:(::), varname, + Expr(:curly, :Union, :Nothing, Expr(:curly, :AbstractArray, vartype))), + nothing)) + if !isnothing(meta) && haskey(meta, VariableUnit) + push!(where_types, vartype) + else + push!(where_types, :($vartype <: $type)) + end + # Useful keys for kwargs entry are: value, type and size. dict[:kwargs][varname] = varclass_dict[][varname] @@ -243,13 +239,12 @@ function unit_handled_variable_value(meta, varname) end function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; - def = nothing, indices::Union{Vector{UnitRange{Int}}, Nothing} = nothing, - type::Type = Real, meta = Dict{DataType, Expr}()) + def = nothing, type::Type = Real, meta = Dict{DataType, Expr}()) arg isa LineNumberNode && return MLStyle.@match arg begin a::Symbol => begin - var = generate_var!(dict, a, varclass; indices, type) - update_kwargs_and_metadata!(dict, kwargs, a, def, indices, type, var, + var = generate_var!(dict, a, varclass; type) + update_kwargs_and_metadata!(dict, kwargs, a, def, type, varclass, where_types, meta) return var, def, Dict() end @@ -265,8 +260,8 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; dict, mod, a, varclass, kwargs, where_types; def, type, meta) end Expr(:call, a, b) => begin - var = generate_var!(dict, a, b, varclass, mod; indices, type) - update_kwargs_and_metadata!(dict, kwargs, a, def, indices, type, var, + var = generate_var!(dict, a, b, varclass, mod; type) + update_kwargs_and_metadata!(dict, kwargs, a, def, type, varclass, where_types, meta) return var, def, Dict() end @@ -276,7 +271,6 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; Expr(:tuple, Expr(:ref, a, indices...), meta_val) => begin (@isdefined type) || (type = Real) varname = Meta.isexpr(a, :call) ? a.args[1] : a - push!(kwargs, Expr(:kw, varname, nothing)) meta = parse_metadata(mod, meta_val) varval = (@isdefined default_val) ? default_val : unit_handled_variable_value(meta, varname) @@ -291,8 +285,8 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; var = :($varname = $first(@variables ($a[$(indices)]::$type = $varval), $meta_val)) end - push_array_kwargs_and_metadata!( - dict, indices, meta, type, varclass, varname, varval) + update_array_kwargs_and_metadata!( + dict, indices, kwargs, meta, type, varclass, varname, varval, where_types) (:($varname...), var), nothing, Dict() end Expr(:(=), Expr(:(::), Expr(:ref, a, indices...), type), def_n_meta) || @@ -303,7 +297,6 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; meta = parse_metadata(mod, def_n_meta) varval = unit_handled_variable_value(meta, varname) val, def_n_meta = (def_n_meta.args[1], def_n_meta.args[2:end]) - push!(kwargs, Expr(:kw, varname, nothing)) if varclass == :parameters Meta.isexpr(a, :call) && assert_unique_independent_var(dict, a.args[end]) @@ -320,7 +313,6 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; $(def_n_meta...))) end else - push!(kwargs, Expr(:kw, varname, nothing)) if varclass == :parameters Meta.isexpr(a, :call) && assert_unique_independent_var(dict, a.args[end]) @@ -335,15 +327,14 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; end varval, meta = def_n_meta, nothing end - push_array_kwargs_and_metadata!( - dict, indices, meta, type, varclass, varname, varval) + update_array_kwargs_and_metadata!( + dict, indices, kwargs, meta, type, varclass, varname, varval, where_types) (:($varname...), var), nothing, Dict() end Expr(:(::), Expr(:ref, a, indices...), type) || Expr(:ref, a, indices...) => begin (@isdefined type) || (type = Real) varname = a isa Expr && a.head == :call ? a.args[1] : a - push!(kwargs, Expr(:kw, varname, nothing)) if varclass == :parameters Meta.isexpr(a, :call) && assert_unique_independent_var(dict, a.args[end]) var = :($varname = $first(@parameters $a[$(indices...)]::$type = $varname)) @@ -356,8 +347,8 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; throw("Symbolic array with arbitrary length is not handled for $varclass. Please open an issue with an example.") end - push_array_kwargs_and_metadata!( - dict, indices, nothing, type, varclass, varname, nothing) + update_array_kwargs_and_metadata!( + dict, indices, kwargs, nothing, type, varclass, varname, nothing, where_types) (:($varname...), var), nothing, Dict() end Expr(:(=), a, b) => begin @@ -392,11 +383,8 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; end end -function generate_var(a, varclass; - indices::Union{Vector{UnitRange{Int}}, Nothing} = nothing, - type = Real) - var = indices === nothing ? Symbolics.variable(a; T = type) : - first(@variables $a[indices...]::type) +function generate_var(a, varclass; type = Real) + var = Symbolics.variable(a; T = type) if varclass == :parameters var = toparam(var) elseif varclass == :independent_variables @@ -426,7 +414,7 @@ function generate_var!(dict, a, varclass; vd isa Vector && (vd = first(vd)) vd[a] = Dict{Symbol, Any}() indices !== nothing && (vd[a][:size] = Tuple(lastindex.(indices))) - generate_var(a, varclass; indices, type) + generate_var(a, varclass; type) end function assert_unique_independent_var(dict, iv::Num) From 1290985bfdce26b2c8010e45314a1cf110fde273 Mon Sep 17 00:00:00 2001 From: Venkateshprasad <32921645+ven-k@users.noreply.github.com> Date: Fri, 11 Oct 2024 09:04:51 +0000 Subject: [PATCH 08/11] refactor: set default value of the symbolic-array-kwargs to NO_VALUE --- src/systems/model_parsing.jl | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index 7e8eed9900..bf70d06b79 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -215,8 +215,9 @@ function update_array_kwargs_and_metadata!( push!(kwargs, Expr(:kw, Expr(:(::), varname, - Expr(:curly, :Union, :Nothing, Expr(:curly, :AbstractArray, vartype))), - nothing)) + Expr(:curly, :Union, :Nothing, :Missing, NoValue, + Expr(:curly, :AbstractArray, vartype))), + NO_VALUE)) if !isnothing(meta) && haskey(meta, VariableUnit) push!(where_types, vartype) else @@ -300,14 +301,14 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; if varclass == :parameters Meta.isexpr(a, :call) && assert_unique_independent_var(dict, a.args[end]) - var = :($varname = $varname === nothing ? $val : $varname; + var = :($varname = $varname === $NO_VALUE ? $val : $varname; $varname = $first(@parameters ($a[$(indices...)]::$type = $varval), $(def_n_meta...))) else Meta.isexpr(a, :call) || throw("$a is not a variable of the independent variable") assert_unique_independent_var(dict, a.args[end]) - var = :($varname = $varname === nothing ? $val : $varname; + var = :($varname = $varname === $NO_VALUE ? $val : $varname; $varname = $first(@variables $a[$(indices...)]::$type = ( $varval), $(def_n_meta...))) @@ -316,13 +317,13 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; if varclass == :parameters Meta.isexpr(a, :call) && assert_unique_independent_var(dict, a.args[end]) - var = :($varname = $varname === nothing ? $def_n_meta : $varname; + var = :($varname = $varname === $NO_VALUE ? $def_n_meta : $varname; $varname = $first(@parameters $a[$(indices...)]::$type = $varname)) else Meta.isexpr(a, :call) || throw("$a is not a variable of the independent variable") assert_unique_independent_var(dict, a.args[end]) - var = :($varname = $varname === nothing ? $def_n_meta : $varname; + var = :($varname = $varname === $NO_VALUE ? $def_n_meta : $varname; $varname = $first(@variables $a[$(indices...)]::$type = $varname)) end varval, meta = def_n_meta, nothing @@ -765,11 +766,12 @@ function parse_variable_arg!(exprs, vs, dict, mod, arg, varclass, kwargs, where_ end function convert_units(varunits::DynamicQuantities.Quantity, value) - value isa Nothing && return nothing DynamicQuantities.ustrip(DynamicQuantities.uconvert( DynamicQuantities.SymbolicUnits.as_quantity(varunits), value)) end +convert_units(::DynamicQuantities.Quantity, value::NoValue) = NO_VALUE + function convert_units( varunits::DynamicQuantities.Quantity, value::AbstractArray{T}) where {T} DynamicQuantities.ustrip.(DynamicQuantities.uconvert.( @@ -777,21 +779,18 @@ function convert_units( end function convert_units(varunits::Unitful.FreeUnits, value) - value isa Nothing && return nothing Unitful.ustrip(varunits, value) end +convert_units(::Unitful.FreeUnits, value::NoValue) = NO_VALUE + function convert_units(varunits::Unitful.FreeUnits, value::AbstractArray{T}) where {T} Unitful.ustrip.(varunits, value) end -function convert_units(varunits::Unitful.FreeUnits, value::Num) - value -end +convert_units(::Unitful.FreeUnits, value::Num) = value -function convert_units(varunits::DynamicQuantities.Quantity, value::Num) - value -end +convert_units(::DynamicQuantities.Quantity, value::Num) = value function parse_variable_arg(dict, mod, arg, varclass, kwargs, where_types) vv, def, metadata_with_exprs = parse_variable_def!( From 09de19020c99b92d51ba577d13b4bae6b3202d1c Mon Sep 17 00:00:00 2001 From: Venkateshprasad <32921645+ven-k@users.noreply.github.com> Date: Fri, 11 Oct 2024 14:44:05 +0000 Subject: [PATCH 09/11] test: for vanilla-`@variable` syntax with arbitrary length --- test/model_parsing.jl | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/test/model_parsing.jl b/test/model_parsing.jl index c108b88625..3e2bd2045c 100644 --- a/test/model_parsing.jl +++ b/test/model_parsing.jl @@ -281,17 +281,34 @@ end @testset "Arrays using vanilla-@variable syntax" begin @mtkmodel TupleInArrayDef begin + @structural_parameters begin + N + M + end @parameters begin (l(t)[1:2, 1:3] = 1), [description = "l is more than 1D"] - (l2(t)[1:3] = 2), [description = "l2 is 1D"] - (l3(t)[1:3]::Int = 3), [description = "l3 is 1D and has a type"] + (l2(t)[1:N, 1:M] = 2), + [description = "l is more than 1D, with arbitrary length"] + (l3(t)[1:3] = 3), [description = "l2 is 1D"] + (l4(t)[1:N] = 4), [description = "l2 is 1D, with arbitrary length"] + (l5(t)[1:3]::Int = 5), [description = "l3 is 1D and has a type"] + (l6(t)[1:N]::Int = 6), + [description = "l3 is 1D and has a type, with arbitrary length"] end end - @named arr = TupleInArrayDef() + N, M = 4, 5 + @named arr = TupleInArrayDef(; N, M) @test getdefault(arr.l) == 1 @test getdefault(arr.l2) == 2 @test getdefault(arr.l3) == 3 + @test getdefault(arr.l4) == 4 + @test getdefault(arr.l5) == 5 + @test getdefault(arr.l6) == 6 + + @test size(arr.l2) == (N, M) + @test size(arr.l4) == (N,) + @test size(arr.l6) == (N,) end @testset "Type annotation" begin From 0d4c97d6a015fa76c1e80bda64a21bd1ab6a9974 Mon Sep 17 00:00:00 2001 From: Venkateshprasad <32921645+ven-k@users.noreply.github.com> Date: Fri, 11 Oct 2024 16:10:46 +0000 Subject: [PATCH 10/11] fix: remove an unused parsing block This is handled in two steps and this block is never used --- src/systems/model_parsing.jl | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index bf70d06b79..51b7c6eb69 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -254,12 +254,6 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; parse_variable_def!( dict, mod, a, varclass, kwargs, where_types; def, type, meta) end - Expr(:(::), Expr(:call, a, b), type) => begin - type = getfield(mod, type) - def = _type_check!(def, a, type, varclass) - parse_variable_def!( - dict, mod, a, varclass, kwargs, where_types; def, type, meta) - end Expr(:call, a, b) => begin var = generate_var!(dict, a, b, varclass, mod; type) update_kwargs_and_metadata!(dict, kwargs, a, def, type, From 4c0b1edee4f4cae0b05eec9f206e491f372c6955 Mon Sep 17 00:00:00 2001 From: Venkateshprasad <32921645+ven-k@users.noreply.github.com> Date: Fri, 11 Oct 2024 16:12:24 +0000 Subject: [PATCH 11/11] docs: document the syntax parsed by a particular block --- src/systems/model_parsing.jl | 62 ++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index 51b7c6eb69..1298d72506 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -239,27 +239,65 @@ function unit_handled_variable_value(meta, varname) return varval end +# This function parses various variable/parameter definitions. +# +# The comments indicate the syntax matched by a block; either when parsed directly +# when it is called recursively for parsing a part of an expression. +# These variable definitions are part of test suite in `test/model_parsing.jl` function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; def = nothing, type::Type = Real, meta = Dict{DataType, Expr}()) arg isa LineNumberNode && return MLStyle.@match arg begin + # Parses: `a` + # Recursively called by: `c(t) = cval + jval` + # Recursively called by: `d = 2` + # Recursively called by: `e, [description = "e"]` + # Recursively called by: `f = 3, [description = "f"]` + # Recursively called by: `k = kval, [description = "k"]` + # Recursively called by: `par0::Bool = true` a::Symbol => begin var = generate_var!(dict, a, varclass; type) update_kwargs_and_metadata!(dict, kwargs, a, def, type, varclass, where_types, meta) return var, def, Dict() end + # Parses: `par5[1:3]::BigFloat` + # Parses: `par6(t)[1:3]::BigFloat` + # Recursively called by: `par2(t)::Int` + # Recursively called by: `par3(t)::BigFloat = 1.0` Expr(:(::), a, type) => begin type = getfield(mod, type) parse_variable_def!( dict, mod, a, varclass, kwargs, where_types; def, type, meta) end + # Recursively called by: `i(t) = 4, [description = "i(t)"]` + # Recursively called by: `h(t), [description = "h(t)"]` + # Recursively called by: `j(t) = jval, [description = "j(t)"]` + # Recursively called by: `par2(t)::Int` + # Recursively called by: `par3(t)::BigFloat = 1.0` Expr(:call, a, b) => begin var = generate_var!(dict, a, b, varclass, mod; type) update_kwargs_and_metadata!(dict, kwargs, a, def, type, varclass, where_types, meta) return var, def, Dict() end + # Condition 1 parses: + # `(l(t)[1:2, 1:3] = 1), [description = "l is more than 1D"]` + # `(l2(t)[1:N, 1:M] = 2), [description = "l is more than 1D, with arbitrary length"]` + # `(l3(t)[1:3] = 3), [description = "l2 is 1D"]` + # `(l4(t)[1:N] = 4), [description = "l2 is 1D, with arbitrary length"]` + # + # Condition 2 parses: + # `(l5(t)[1:3]::Int = 5), [description = "l3 is 1D and has a type"]` + # `(l6(t)[1:N]::Int = 6), [description = "l3 is 1D and has a type, with arbitrary length"]` + # + # Condition 3 parses: + # `e2[1:2]::Int, [description = "e2"]` + # `h2(t)[1:2]::Int, [description = "h2(t)"]` + # + # Condition 4 parses: + # `e2[1:2], [description = "e2"]` + # `h2(t)[1:2], [description = "h2(t)"]` Expr(:tuple, Expr(:(=), Expr(:ref, a, indices...), default_val), meta_val) || Expr(:tuple, Expr(:(=), Expr(:(::), Expr(:ref, a, indices...), type), default_val), meta_val) || Expr(:tuple, Expr(:(::), Expr(:ref, a, indices...), type), meta_val) || @@ -284,6 +322,12 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; dict, indices, kwargs, meta, type, varclass, varname, varval, where_types) (:($varname...), var), nothing, Dict() end + # Condition 1 parses: + # `par7(t)[1:3, 1:3]::BigFloat = 1.0, [description = "with description"]` + # + # Condition 2 parses: + # `d2[1:2] = 2` + # `l(t)[1:2, 1:3] = 2, [description = "l is more than 1D"]` Expr(:(=), Expr(:(::), Expr(:ref, a, indices...), type), def_n_meta) || Expr(:(=), Expr(:ref, a, indices...), def_n_meta) => begin (@isdefined type) || (type = Real) @@ -326,6 +370,13 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; dict, indices, kwargs, meta, type, varclass, varname, varval, where_types) (:($varname...), var), nothing, Dict() end + # Condition 1 is recursively called by: + # `par5[1:3]::BigFloat` + # `par6(t)[1:3]::BigFloat` + # + # Condition 2 parses: + # `b2(t)[1:2]` + # `a2[1:2]` Expr(:(::), Expr(:ref, a, indices...), type) || Expr(:ref, a, indices...) => begin (@isdefined type) || (type = Real) @@ -346,6 +397,14 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; dict, indices, kwargs, nothing, type, varclass, varname, nothing, where_types) (:($varname...), var), nothing, Dict() end + # Parses: `c(t) = cval + jval` + # Parses: `d = 2` + # Parses: `f = 3, [description = "f"]` + # Parses: `i(t) = 4, [description = "i(t)"]` + # Parses: `j(t) = jval, [description = "j(t)"]` + # Parses: `k = kval, [description = "k"]` + # Parses: `par0::Bool = true` + # Parses: `par3(t)::BigFloat = 1.0` Expr(:(=), a, b) => begin Base.remove_linenums!(b) def, meta = parse_default(mod, b) @@ -361,6 +420,9 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; end return var, def, Dict() end + # Parses: `e, [description = "e"]` + # Parses: `h(t), [description = "h(t)"]` + # Parses: `par2(t)::Int` Expr(:tuple, a, b) => begin meta = parse_metadata(mod, b) var, def, _ = parse_variable_def!(