From ae9415341bc9d73a4c4b3efa3ca503f5bf3f2549 Mon Sep 17 00:00:00 2001 From: Venkateshprasad <32921645+ven-k@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:47:55 +0530 Subject: [PATCH 1/2] fix: improve handling of expression in metadata The variables in mtkmodel can have complicated expressions as `guess` and other metadata. Instead of evaluating them while defining the mtk-model, make those exprs part of the the definition. Along with this, the metadata (in structure dict) is kept in expr form. --- docs/src/basics/MTKModel_Connector.md | 5 +- src/systems/model_parsing.jl | 66 +++++++++++++++++++-------- test/model_parsing.jl | 6 +-- 3 files changed, 54 insertions(+), 23 deletions(-) diff --git a/docs/src/basics/MTKModel_Connector.md b/docs/src/basics/MTKModel_Connector.md index 5282b7db18..7b92e19ceb 100644 --- a/docs/src/basics/MTKModel_Connector.md +++ b/docs/src/basics/MTKModel_Connector.md @@ -408,12 +408,13 @@ The conditional parts are reflected in the `structure`. For `BranchOutsideTheBlo ```julia julia> BranchOutsideTheBlock.structure -Dict{Symbol, Any} with 6 entries: +Dict{Symbol, Any} with 7 entries: :components => Any[(:if, :flag, Vector{Union{Expr, Symbol}}[[:sys1, :C]], Any[])] :kwargs => Dict{Symbol, Dict}(:flag=>Dict{Symbol, Bool}(:value=>1)) :structural_parameters => Dict{Symbol, Dict}(:flag=>Dict{Symbol, Bool}(:value=>1)) :independent_variable => t - :parameters => Dict{Symbol, Dict{Symbol, Any}}(:a2 => Dict(:type => AbstractArray{Real}, :condition => (:if, :flag, Dict{Symbol, Any}(:kwargs => Dict{Any, Any}(:a1 => Dict{Symbol, Union{Nothing, DataType}}(:value => nothing, :type => Real)), :parameters => Any[Dict{Symbol, Dict{Symbol, Any}}(:a1 => Dict(:type => AbstractArray{Real}))]), Dict{Symbol, Any}(:variables => Any[Dict{Symbol, Dict{Symbol, Any}}()], :kwargs => Dict{Any, Any}(:a2 => Dict{Symbol, Union{Nothing, DataType}}(:value => nothing, :type => Real)), :parameters => Any[Dict{Symbol, Dict{Symbol, Any}}(:a2 => Dict(:type => AbstractArray{Real}))]))), :a1 => Dict(:type => AbstractArray{Real}, :condition => (:if, :flag, Dict{Symbol, Any}(:kwargs => Dict{Any, Any}(:a1 => Dict{Symbol, Union{Nothing, DataType}}(:value => nothing, :type => Real)), :parameters => Any[Dict{Symbol, Dict{Symbol, Any}}(:a1 => Dict(:type => AbstractArray{Real}))]), Dict{Symbol, Any}(:variables => Any[Dict{Symbol, Dict{Symbol, Any}}()], :kwargs => Dict{Any, Any}(:a2 => Dict{Symbol, Union{Nothing, DataType}}(:value => nothing, :type => Real)), :parameters => Any[Dict{Symbol, Dict{Symbol, Any}}(:a2 => Dict(:type => AbstractArray{Real}))])))) + :parameters => Dict{Symbol, Dict{Symbol, Any}}(:a2 => Dict(:type=>AbstractArray{Real}, :condition=>(:if, :flag, Dict{Symbol, Any}(:kwargs=>Dict{Any, Any}(:a1=>Dict{Symbol, Union{Nothing, DataType}}(:value=>nothing, :type=>Real)), :parameters=>Any[Dict{Symbol, Dict{Symbol, Any}}(:a1=>Dict(:type=>AbstractArray{Real}))]), Dict{Symbol, Any}(:variables=>Any[Dict{Symbol, Dict{Symbol, Any}}()], :kwargs=>Dict{Any, Any}(:a2=>Dict{Symbol, Union{Nothing, DataType}}(:value=>nothing, :type=>Real)), :parameters=>Any[Dict{Symbol, Dict{Symbol, Any}}(:a2=>Dict(:type=>AbstractArray{Real}))]))), :a1 => Dict(:type=>AbstractArray{Real}, :condition=>(:if, :flag, Dict{Symbol, Any}(:kwargs=>Dict{Any, Any}(:a1=>Dict{Symbol, Union{Nothing, DataType}}(:value=>nothing, :type=>Real)), :parameters=>Any[Dict{Symbol, Dict{Symbol, Any}}(:a1=>Dict(:type=>AbstractArray{Real}))]), Dict{Symbol, Any}(:variables=>Any[Dict{Symbol, Dict{Symbol, Any}}()], :kwargs=>Dict{Any, Any}(:a2=>Dict{Symbol, Union{Nothing, DataType}}(:value=>nothing, :type=>Real)), :parameters=>Any[Dict{Symbol, Dict{Symbol, Any}}(:a2=>Dict(:type=>AbstractArray{Real}))])))) + :defaults => Dict{Symbol, Any}(:a1=>10) :equations => Any[(:if, :flag, ["a1 ~ 0"], ["a2 ~ 0"])] ``` diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index ec8d5aa6f5..d6fac17616 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -187,7 +187,7 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; var = generate_var!(dict, a, varclass; indices, type) update_kwargs_and_metadata!(dict, kwargs, a, def, indices, type, var, varclass, where_types) - (var, def) + return var, def, Dict() end Expr(:(::), a, type) => begin type = getfield(mod, type) @@ -202,12 +202,12 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; var = generate_var!(dict, a, b, varclass, mod; indices, type) update_kwargs_and_metadata!(dict, kwargs, a, def, indices, type, var, varclass, where_types) - (var, def) + return var, def, Dict() end Expr(:(=), a, b) => begin Base.remove_linenums!(b) def, meta = parse_default(mod, b) - var, def = parse_variable_def!( + var, def, _ = parse_variable_def!( dict, mod, a, varclass, kwargs, where_types; def, type) if dict[varclass] isa Vector dict[varclass][1][getname(var)][:default] = def @@ -225,12 +225,13 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; end end end - var = set_var_metadata(var, meta) + var, metadata_with_exprs = set_var_metadata(var, meta) + return var, def, metadata_with_exprs end - (var, def) + return var, def, Dict() end Expr(:tuple, a, b) => begin - var, def = parse_variable_def!( + var, def, _ = parse_variable_def!( dict, mod, a, varclass, kwargs, where_types; type) meta = parse_metadata(mod, b) if meta !== nothing @@ -244,9 +245,10 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; end end end - var = set_var_metadata(var, meta) + var, metadata_with_exprs = set_var_metadata(var, meta) + return var, def, metadata_with_exprs end - (var, def) + return var, def, Dict() end Expr(:ref, a, b...) => begin indices = map(i -> UnitRange(i.args[2], i.args[end]), b) @@ -350,21 +352,33 @@ function parse_metadata(mod, a) end end +function _set_var_metadata!(metadata_with_exprs, a, m, v::Expr) + push!(metadata_with_exprs, m => v) + a +end +function _set_var_metadata!(metadata_with_exprs, a, m, v) + wrap(set_scalar_metadata(unwrap(a), m, v)) +end + function set_var_metadata(a, ms) + metadata_with_exprs = Dict{DataType, Expr}() for (m, v) in ms - a = wrap(set_scalar_metadata(unwrap(a), m, v)) + if m == VariableGuess && v isa Symbol + v = quote + $v + end + end + a = _set_var_metadata!(metadata_with_exprs, a, m, v) end - a + a, metadata_with_exprs end function get_var(mod::Module, b) if b isa Symbol - getproperty(mod, b) - elseif b isa Expr - Core.eval(mod, b) - else - b + isdefined(mod, b) && return getproperty(mod, b) + isdefined(@__MODULE__, b) && return getproperty(@__MODULE__, b) end + b end function parse_model!(exprs, comps, ext, eqs, icon, vs, ps, sps, c_evts, d_evts, @@ -595,10 +609,26 @@ function parse_variable_arg!(exprs, vs, dict, mod, arg, varclass, kwargs, where_ end function parse_variable_arg(dict, mod, arg, varclass, kwargs, where_types) - vv, def = parse_variable_def!(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) - return vv isa Num ? name : :($name...), - :($name = $name === nothing ? $setdefault($vv, $def) : $setdefault($vv, $name)) + + varexpr = quote + $name = if $name === nothing + $setdefault($vv, $def) + else + $setdefault($vv, $name) + 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 + + push!(varexpr.args, metadata_expr) + return vv isa Num ? name : :($name...), varexpr end function handle_conditional_vars!( diff --git a/test/model_parsing.jl b/test/model_parsing.jl index 578eb60c74..50cbd64270 100644 --- a/test/model_parsing.jl +++ b/test/model_parsing.jl @@ -376,8 +376,8 @@ end @testset "Metadata in variables" begin metadata = Dict(:description => "Variable to test metadata in the Model.structure", - :input => true, :bounds => (-1, 1), :connection_type => :Flow, - :tunable => false, :disturbance => true, :dist => Normal(1, 1)) + :input => true, :bounds => :((-1, 1)), :connection_type => :Flow, + :tunable => false, :disturbance => true, :dist => :(Normal(1, 1))) @connector MockMeta begin m(t), @@ -473,7 +473,7 @@ using ModelingToolkit: getdefault, scalarize @named model_with_component_array = ModelWithComponentArray() - @test ModelWithComponentArray.structure[:parameters][:r][:unit] == 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 35013c846cedb6a0ab3fb214731484d69bb6b8f2 Mon Sep 17 00:00:00 2001 From: Venkateshprasad <32921645+ven-k@users.noreply.github.com> Date: Thu, 25 Apr 2024 16:57:38 +0530 Subject: [PATCH 2/2] test: various forms of guess as expression --- mo.diff | 582 ++++++++++++++++++++++++++++++++++++++++++ test/model_parsing.jl | 30 ++- 2 files changed, 609 insertions(+), 3 deletions(-) create mode 100644 mo.diff diff --git a/mo.diff b/mo.diff new file mode 100644 index 0000000000..abb2a3b037 --- /dev/null +++ b/mo.diff @@ -0,0 +1,582 @@ +27,28d26 +< Base.parentmodule(m::Model) = parentmodule(m.f) +< +40,46c38,40 +< dict = Dict{Symbol, Any}( +< :constants => Dict{Symbol, Dict}(), +< :defaults => Dict{Symbol, Any}(), +< :kwargs => Dict{Symbol, Dict}(), +< :structural_parameters => Dict{Symbol, Dict}() +< ) +< comps = Union{Symbol, Expr}[] +--- +> dict = Dict{Symbol, Any}() +> dict[:kwargs] = Dict{Symbol, Any}() +> comps = Symbol[] +51,52d44 +< c_evts = [] +< d_evts = [] +54d45 +< where_types = Expr[] +60d50 +< push!(exprs.args, :(defaults = Dict{Num, Union{Number, Symbol, Function}}())) +66c56 +< sps, c_evts, d_evts, dict, mod, arg, kwargs, where_types) +--- +> sps, dict, mod, arg, kwargs) +73,74c63 +< mod, ps, vs, where_types, +< parse_top_level_branch(condition, x.args)...) +--- +> mod, ps, vs, parse_top_level_branch(condition, x.args)...) +78,79c67 +< mod, ps, vs, where_types, +< parse_top_level_branch(condition, x.args, y)...) +--- +> mod, ps, vs, parse_top_level_branch(condition, x.args, y)...) +86,87c74 +< parse_variable_arg!( +< exprs.args, vs, dict, mod, arg, :variables, kwargs, where_types) +--- +> parse_variable_arg!(exprs.args, vs, dict, mod, arg, :variables, kwargs) +95c82 +< iv = dict[:independent_variable] = get_t(mod, :t) +--- +> iv = dict[:independent_variable] = variable(:t) +106,108d92 +< @inline pop_structure_dict!.( +< Ref(dict), [:constants, :defaults, :kwargs, :structural_parameters]) +< +110c94 +< name, systems, gui_metadata = $gui_metadata, defaults)) +--- +> name, systems, gui_metadata = $gui_metadata)) +121,139c105 +< !isempty(c_evts) && push!(exprs.args, +< :($Setfield.@set!(var"#___sys___".continuous_events=$SymbolicContinuousCallback.([ +< $(c_evts...) +< ])))) +< +< !isempty(d_evts) && push!(exprs.args, +< :($Setfield.@set!(var"#___sys___".discrete_events=$SymbolicDiscreteCallback.([ +< $(d_evts...) +< ])))) +< +< f = if length(where_types) == 0 +< :($(Symbol(:__, name, :__))(; name, $(kwargs...)) = $exprs) +< else +< f_with_where = Expr(:where) +< push!(f_with_where.args, +< :($(Symbol(:__, name, :__))(; name, $(kwargs...))), where_types...) +< :($f_with_where = $exprs) +< end +< +--- +> f = :($(Symbol(:__, name, :__))(; name, $(kwargs...)) = $exprs) +143,169c109,110 +< pop_structure_dict!(dict, key) = length(dict[key]) == 0 && pop!(dict, key) +< +< function update_kwargs_and_metadata!(dict, kwargs, a, def, indices, type, var, +< varclass, where_types) +< if indices isa Nothing +< push!(kwargs, Expr(:kw, Expr(:(::), a, Union{Nothing, type}), nothing)) +< dict[:kwargs][getname(var)] = Dict(:value => def, :type => type) +< else +< vartype = gensym(:T) +< push!(kwargs, +< Expr(:kw, +< Expr(:(::), a, +< Expr(:curly, :Union, :Nothing, Expr(:curly, :AbstractArray, vartype))), +< nothing)) +< push!(where_types, :($vartype <: $type)) +< dict[:kwargs][getname(var)] = Dict(:value => def, :type => AbstractArray{type}) +< end +< if dict[varclass] isa Vector +< dict[varclass][1][getname(var)][:type] = AbstractArray{type} +< else +< dict[varclass][getname(var)][:type] = type +< end +< end +< +< function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; +< def = nothing, indices::Union{Vector{UnitRange{Int}}, Nothing} = nothing, +< type::Type = Real) +--- +> function parse_variable_def!(dict, mod, arg, varclass, kwargs; +> def = nothing, indices::Union{Vector{UnitRange{Int}}, Nothing} = nothing) +182c123,125 +< (:dist, VariableDistribution)] +--- +> (:dist, VariableDistribution), +> (:binary, VariableBinary), +> (:integer, VariableInteger)] +187,199c130,133 +< var = generate_var!(dict, a, varclass; indices, type) +< update_kwargs_and_metadata!(dict, kwargs, a, def, indices, type, var, +< varclass, where_types) +< return var, def, Dict() +< end +< Expr(:(::), a, type) => begin +< type = getfield(mod, type) +< parse_variable_def!(dict, mod, a, varclass, kwargs, where_types; def, type) +< 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) +--- +> push!(kwargs, Expr(:kw, a, nothing)) +> var = generate_var!(dict, a, varclass; indices) +> dict[:kwargs][getname(var)] = def +> (var, def, Dict()) +202,205c136,139 +< var = generate_var!(dict, a, b, varclass, mod; indices, type) +< update_kwargs_and_metadata!(dict, kwargs, a, def, indices, type, var, +< varclass, where_types) +< return var, def, Dict() +--- +> push!(kwargs, Expr(:kw, a, nothing)) +> var = generate_var!(dict, a, b, varclass; indices) +> dict[:kwargs][getname(var)] = def +> (var, def, Dict()) +211,217c145,146 +< var, def, _ = parse_variable_def!( +< dict, mod, a, varclass, kwargs, where_types; def, type) +< if dict[varclass] isa Vector +< dict[varclass][1][getname(var)][:default] = def +< else +< dict[varclass][getname(var)][:default] = def +< end +--- +> var, def, _ = parse_variable_def!(dict, mod, a, varclass, kwargs; def) +> dict[varclass][getname(var)][:default] = def +222d150 +< key == VariableConnectType && (mt = nameof(mt)) +236,237c164,165 +< var, def, _ = parse_variable_def!( +< dict, mod, a, varclass, kwargs, where_types; type) +--- +> @info 166 a b +> var, def, _ = parse_variable_def!(dict, mod, a, varclass, kwargs) +257,258c185,186 +< parse_variable_def!(dict, mod, a, varclass, kwargs, where_types; +< def, indices, type) +--- +> parse_variable_def!(dict, mod, a, varclass, kwargs; +> def, indices) +265,268c193,194 +< indices::Union{Vector{UnitRange{Int}}, Nothing} = nothing, +< type = Real) +< var = indices === nothing ? Symbolics.variable(a; T = type) : +< first(@variables $a[indices...]::type) +--- +> indices::Union{Vector{UnitRange{Int}}, Nothing} = nothing) +> var = indices === nothing ? Symbolics.variable(a) : first(@variables $a[indices...]) +276,277c202 +< indices::Union{Vector{UnitRange{Int}}, Nothing} = nothing, +< type = Real) +--- +> indices::Union{Vector{UnitRange{Int}}, Nothing} = nothing) +284c209 +< generate_var(a, varclass; indices, type) +--- +> generate_var(a, varclass; indices) +287,290c212,214 +< 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, :variables) +--- +> function generate_var!(dict, a, b, varclass; +> indices::Union{Vector{UnitRange{Int}}, Nothing} = nothing) +> iv = generate_var(b, :variables) +301c225 +< Symbolics.variable(a, T = SymbolicUtils.FnType{Tuple{Any}, type})(iv) +--- +> Symbolics.variable(a, T = SymbolicUtils.FnType{Tuple{Any}, Real})(iv) +304c228 +< first(@variables $a(iv)[indices...]::type) +--- +> first(@variables $a(iv)[indices...]) +312,325d235 +< # Use the `t` defined in the `mod`. When it is unavailable, generate a new `t` with a warning. +< function get_t(mod, t) +< try +< get_var(mod, t) +< catch e +< if e isa UndefVarError +< @warn("Could not find a predefined `t` in `$mod`; generating a new one within this model.\nConsider defining it or importing `t` (or `t_nounits`, `t_unitful` as `t`) from ModelingToolkit.") +< variable(:t) +< else +< throw(e) +< end +< end +< end +< +365a276 +> @info typeof(m) typeof(v) m v +380,381c291,292 +< function parse_model!(exprs, comps, ext, eqs, icon, vs, ps, sps, c_evts, d_evts, +< dict, mod, arg, kwargs, where_types) +--- +> function parse_model!(exprs, comps, ext, eqs, icon, vs, ps, sps, +> dict, mod, arg, kwargs) +389c300 +< parse_variables!(exprs, vs, dict, mod, body, :variables, kwargs, where_types) +--- +> parse_variables!(exprs, vs, dict, mod, body, :variables, kwargs) +391c302 +< parse_variables!(exprs, ps, dict, mod, body, :parameters, kwargs, where_types) +--- +> parse_variables!(exprs, ps, dict, mod, body, :parameters, kwargs) +396,401d306 +< 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) +405,406d309 +< elseif mname == Symbol("@defaults") +< parse_system_defaults!(exprs, arg, dict) +412,476d314 +< function parse_constants!(exprs, dict, body, mod) +< Base.remove_linenums!(body) +< for arg in body.args +< MLStyle.@match arg begin +< Expr(:(=), Expr(:(::), a, type), Expr(:tuple, b, metadata)) || Expr(:(=), Expr(:(::), a, type), b) => begin +< type = getfield(mod, type) +< b = _type_check!(get_var(mod, b), a, type, :constants) +< push!(exprs, +< :($(Symbolics._parse_vars( +< :constants, type, [:($a = $b), metadata], toconstant)))) +< dict[:constants][a] = Dict(:value => b, :type => type) +< if @isdefined metadata +< for data in metadata.args +< dict[:constants][a][data.args[1]] = data.args[2] +< end +< end +< end +< Expr(:(=), a, Expr(:tuple, b, metadata)) => begin +< push!(exprs, +< :($(Symbolics._parse_vars( +< :constants, Real, [:($a = $b), metadata], toconstant)))) +< dict[:constants][a] = Dict{Symbol, Any}(:value => get_var(mod, b)) +< for data in metadata.args +< dict[:constants][a][data.args[1]] = data.args[2] +< end +< end +< Expr(:(=), a, b) => begin +< push!(exprs, +< :($(Symbolics._parse_vars( +< :constants, Real, [:($a = $b)], toconstant)))) +< dict[:constants][a] = Dict(:value => get_var(mod, b)) +< end +< _ => error("""Malformed constant definition `$arg`. Please use the following syntax: +< ``` +< @constants begin +< var = value, [description = "This is an example constant."] +< end +< ``` +< """) +< end +< end +< end +< +< push_additional_defaults!(dict, a, b::Number) = dict[:defaults][a] = b +< push_additional_defaults!(dict, a, b::QuoteNode) = dict[:defaults][a] = b.value +< function push_additional_defaults!(dict, a, b::Expr) +< dict[:defaults][a] = readable_code(b) +< end +< +< function parse_system_defaults!(exprs, defaults_body, dict) +< for default_arg in defaults_body.args[end].args +< # for arg in default_arg.args +< MLStyle.@match default_arg begin +< # For cases like `p => 1` and `p => f()`. In both cases the definitions of +< # `a`, here `p` and when `b` is a function, here `f` are available while +< # defining the model +< Expr(:call, :(=>), a, b) => begin +< push!(exprs, :(defaults[$a] = $b)) +< push_additional_defaults!(dict, a, b) +< end +< _ => error("Invalid `defaults` entry $default_arg $(typeof(a)) $(typeof(b))") +< end +< end +< end +< +481,488d318 +< Expr(:(=), Expr(:(::), a, type), b) => begin +< type = getfield(mod, type) +< b = _type_check!(get_var(mod, b), a, type, :structural_parameters) +< push!(sps, a) +< push!(kwargs, Expr(:kw, Expr(:(::), a, type), b)) +< dict[:structural_parameters][a] = dict[:kwargs][a] = Dict( +< :value => b, :type => type) +< end +492c322 +< dict[:structural_parameters][a] = dict[:kwargs][a] = Dict(:value => b) +--- +> dict[:kwargs][a] = b +497c327 +< dict[:structural_parameters][a] = dict[:kwargs][a] = Dict(:value => nothing) +--- +> dict[:kwargs][a] = nothing +521c351 +< dict[:kwargs][x] = Dict(:value => nothing) +--- +> dict[:kwargs][x] = nothing +525c355 +< dict[:kwargs][x] = Dict(:value => nothing) +--- +> dict[:kwargs][x] = nothing +531c361 +< dict[:kwargs][x] = Dict(:value => nothing) +--- +> dict[:kwargs][x] = nothing +601,602c431,432 +< function parse_variable_arg!(exprs, vs, dict, mod, arg, varclass, kwargs, where_types) +< name, ex = parse_variable_arg(dict, mod, arg, varclass, kwargs, where_types) +--- +> function parse_variable_arg!(exprs, vs, dict, mod, arg, varclass, kwargs) +> name, ex = parse_variable_arg(dict, mod, arg, varclass, kwargs) +607,608c437,438 +< 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) +--- +> function parse_variable_arg(dict, mod, arg, varclass, kwargs) +> vv, def, metadata_with_exprs = parse_variable_def!(dict, mod, arg, varclass, kwargs) +628,629c458 +< function handle_conditional_vars!( +< arg, conditional_branch, mod, varclass, kwargs, where_types) +--- +> function handle_conditional_vars!(arg, conditional_branch, mod, varclass, kwargs) +634,635c463 +< name, ex = parse_variable_arg( +< conditional_dict, mod, _arg, varclass, kwargs, where_types) +--- +> name, ex = parse_variable_arg(conditional_dict, mod, _arg, varclass, kwargs) +693c521 +< function parse_variables!(exprs, vs, dict, mod, body, varclass, kwargs, where_types) +--- +> function parse_variables!(exprs, vs, dict, mod, body, varclass, kwargs) +705,706c533 +< kwargs, +< where_types) +--- +> kwargs) +716,717c543 +< kwargs, +< where_types) +--- +> kwargs) +722c548 +< kwargs, where_types) +--- +> kwargs) +731,732c557 +< _ => parse_variable_arg!( +< exprs, vs, dict, mod, arg, varclass, kwargs, where_types) +--- +> _ => parse_variable_arg!(exprs, vs, dict, mod, arg, varclass, kwargs) +737c562 +< function handle_y_vars(y, dict, mod, varclass, kwargs, where_types) +--- +> function handle_y_vars(y, dict, mod, varclass, kwargs) +744,747c569,570 +< kwargs, +< where_types) +< _y_expr, _conditional_dict = handle_y_vars( +< y.args[end], dict, mod, varclass, kwargs, where_types) +--- +> kwargs) +> _y_expr, _conditional_dict = handle_y_vars(y.args[end], dict, mod, varclass, kwargs) +752c575 +< handle_conditional_vars!(y, conditional_y_expr, mod, varclass, kwargs, where_types) +--- +> handle_conditional_vars!(y, conditional_y_expr, mod, varclass, kwargs) +813,830d635 +< 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 +< +856c661 +< function component_args!(a, b, varexpr, kwargs; index_name = nothing) +--- +> function component_args!(a, b, expr, varexpr, kwargs) +865,876c670,674 +< varname, _varname = _rename(a, x) +< b.args[i] = Expr(:kw, x, _varname) +< push!(varexpr.args, :((if $varname !== nothing +< $_varname = $varname +< elseif @isdefined $x +< # Allow users to define a var in `structural_parameters` and set +< # that as positional arg of subcomponents; it is useful for cases +< # where it needs to be passed to multiple subcomponents. +< $_varname = $x +< end))) +< push!(kwargs, Expr(:kw, varname, nothing)) +< # dict[:kwargs][varname] = nothing +--- +> _v = _rename(a, x) +> b.args[i] = Expr(:kw, x, _v) +> push!(varexpr.args, :((@isdefined $x) && ($_v = $x))) +> push!(kwargs, Expr(:kw, _v, nothing)) +> # dict[:kwargs][_v] = nothing +879c677 +< component_args!(a, arg, varexpr, kwargs) +--- +> component_args!(a, arg, expr, varexpr, kwargs) +882,891c680,684 +< varname, _varname = _rename(a, x) +< b.args[i] = Expr(:kw, x, _varname) +< if isnothing(index_name) +< push!(varexpr.args, :($_varname = $varname === nothing ? $y : $varname)) +< else +< push!(varexpr.args, +< :($_varname = $varname === nothing ? $y : $varname[$index_name])) +< end +< push!(kwargs, Expr(:kw, varname, nothing)) +< # dict[:kwargs][varname] = nothing +--- +> _v = _rename(a, x) +> b.args[i] = Expr(:kw, x, _v) +> push!(varexpr.args, :($_v = $_v === nothing ? $y : $_v)) +> push!(kwargs, Expr(:kw, _v, nothing)) +> # dict[:kwargs][_v] = nothing +898,901c691,692 +< model_name(name, range) = Symbol.(name, :_, collect(range)) +< +< function _parse_components!(body, kwargs) +< local expr +--- +> function _parse_components!(exprs, body, kwargs) +> expr = Expr(:block) +903c694,695 +< comps = Vector{Union{Union{Expr, Symbol}, Expr}}[] +--- +> # push!(exprs, varexpr) +> comps = Vector{Union{Symbol, Expr}}[] +906,908c698,699 +< Base.remove_linenums!(body) +< arg = body.args[end] +< +--- +> for arg in body.args +> arg isa LineNumberNode && continue +910,927d700 +< Expr(:(=), a, Expr(:comprehension, Expr(:generator, b, Expr(:(=), c, d)))) => begin +< array_varexpr = Expr(:block) +< +< push!(comp_names, :($a...)) +< push!(comps, [a, b.args[1], d]) +< b = deepcopy(b) +< +< component_args!(a, b, array_varexpr, kwargs; index_name = c) +< +< expr = _named_idxs(a, d, :($c -> $b); extra_args = array_varexpr) +< end +< Expr(:(=), a, Expr(:comprehension, Expr(:generator, b, Expr(:filter, e, Expr(:(=), c, d))))) => begin +< error("List comprehensions with conditional statements aren't supported.") +< end +< Expr(:(=), a, Expr(:comprehension, Expr(:generator, b, Expr(:(=), c, d), e...))) => begin +< # Note that `e` is of the form `Tuple{Expr(:(=), c, d)}` +< error("More than one index isn't supported while building component array") +< end +932,943d704 +< Expr(:(=), a, Expr(:for, Expr(:(=), c, d), b)) => begin +< Base.remove_linenums!(b) +< array_varexpr = Expr(:block) +< push!(array_varexpr.args, b.args[1:(end - 1)]...) +< push!(comp_names, :($a...)) +< push!(comps, [a, b.args[end].args[1], d]) +< b = deepcopy(b) +< +< component_args!(a, b.args[end], array_varexpr, kwargs; index_name = c) +< +< expr = _named_idxs(a, d, :($c -> $(b.args[end])); extra_args = array_varexpr) +< end +948c709 +< component_args!(a, b, varexpr, kwargs) +--- +> component_args!(a, b, expr, varexpr, kwargs) +951c712 +< expr = :(@named $arg) +--- +> push!(expr.args, arg) +959c720 +< +--- +> end +966c727,729 +< push!(blk.args, expr_vec) +--- +> push!(blk.args, :(@named begin +> $(expr_vec.args...) +> end)) +973c736 +< comp_names, comps, expr_vec, varexpr = _parse_components!(x, kwargs) +--- +> comp_names, comps, expr_vec, varexpr = _parse_components!(ifexpr, x, kwargs) +989c752 +< comp_names, comps, expr_vec, varexpr = _parse_components!(y, kwargs) +--- +> comp_names, comps, expr_vec, varexpr = _parse_components!(exprs, y, kwargs) +1014,1016c777,779 +< # Either the arg is top level component declaration or an invalid cause - both are handled by `_parse_components` +< _ => begin +< comp_names, comps, expr_vec, varexpr = _parse_components!(:(begin +--- +> Expr(:(=), a, b) => begin +> comp_names, comps, expr_vec, varexpr = _parse_components!(exprs, +> :(begin +1022c785,787 +< push!(exprs, varexpr, expr_vec) +--- +> push!(exprs, varexpr, :(@named begin +> $(expr_vec.args...) +> end)) +1023a789 +> _ => error("Couldn't parse the component body $compbody") +1030d795 +< (compname, Symbol(:_, compname)) +1091c856 +< ps, vs, where_types, component_blk, equations_blk, parameter_blk, variable_blk) +--- +> ps, vs, component_blk, equations_blk, parameter_blk, variable_blk) +1096c861 +< end), :parameters, kwargs, where_types) +--- +> end), :parameters, kwargs) +1102c867 +< end), :variables, kwargs, where_types) +--- +> end), :variables, kwargs) +1114,1126d878 +< end +< +< function _type_check!(val, a, type, class) +< if val isa type +< return val +< else +< try +< return convert(type, val) +< catch e +< throw(TypeError(Symbol("`@mtkmodel`"), +< "`$class`, while assigning to `$a`", type, typeof(val))) +< end +< end diff --git a/test/model_parsing.jl b/test/model_parsing.jl index 50cbd64270..2583083968 100644 --- a/test/model_parsing.jl +++ b/test/model_parsing.jl @@ -1,8 +1,7 @@ using ModelingToolkit, Test using ModelingToolkit: get_connector_type, get_defaults, get_gui_metadata, - get_systems, get_ps, getdefault, getname, scalarize, symtype, - VariableDescription, - RegularConnector + get_systems, get_ps, getdefault, getname, readable_code, + scalarize, symtype, VariableDescription, RegularConnector using URIs: URI using Distributions using DynamicQuantities, OrdinaryDiffEq @@ -769,3 +768,28 @@ end @testset "Parent module of Models" begin @test parentmodule(MyMockModule.Ground) == MyMockModule end + +@testset "Guesses with expression" begin + @mtkmodel GuessModel begin + @variables begin + k(t) + l(t) = 10, [guess = k, unit = u"A"] + i(t), [guess = k, unit = u"A"] + j(t), [guess = k + l / i] + end + end + + @named guess_model = GuessModel() + + j_guess = getguess(guess_model.j) + @test typeof(j_guess) == Num + @test readable_code(j_guess) == "l(t) / i(t) + k(t)" + + i_guess = getguess(guess_model.i) + @test typeof(i_guess) == Num + @test readable_code(i_guess) == "k(t)" + + l_guess = getguess(guess_model.l) + @test typeof(l_guess) == Num + @test readable_code(l_guess) == "k(t)" +end