From 21f49ecd8705b3c8dc21689df235fab43ab99d32 Mon Sep 17 00:00:00 2001 From: Venkateshprasad <32921645+ven-k@users.noreply.github.com> Date: Thu, 8 Jun 2023 21:56:59 +0530 Subject: [PATCH 01/12] init: whenever there is an undef var assigned to @variables or @parameters, a kwargs list is created with those With this, we can pass the them as kwargs while calling the Model --- src/systems/model_parsing.jl | 39 ++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index c3b570ce40..7a8124e232 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -32,7 +32,7 @@ function connector_macro(mod, name, body) parse_icon!(icon, dict, dict, arg.args[end]) continue end - push!(vs, Num(parse_variable_def!(dict, mod, arg, :variables))) + push!(vs, Num(parse_variable_def!(dict, mod, arg, :variables, kwargs))) end iv = get(dict, :independent_variable, nothing) if iv === nothing @@ -49,18 +49,19 @@ function connector_macro(mod, name, body) end end -function parse_variable_def!(dict, mod, arg, varclass) +function parse_variable_def!(dict, mod, arg, varclass, kwargs) MLStyle.@match arg begin ::Symbol => generate_var!(dict, arg, varclass) Expr(:call, a, b) => generate_var!(dict, a, b, varclass) Expr(:(=), a, b) => begin - var = parse_variable_def!(dict, mod, a, varclass) - def = parse_default(mod, b) + var = parse_variable_def!(dict, mod, a, varclass, kwargs) + def = parse_default(mod, b, kwargs) dict[varclass][getname(var)][:default] = def - setdefault(var, def) + var = setdefault(var, def) + var end Expr(:tuple, a, b) => begin - var = parse_variable_def!(dict, mod, a, varclass) + var = parse_variable_def!(dict, mod, a, varclass, kwargs) meta = parse_metadata(mod, b) if (ct = get(meta, VariableConnectType, nothing)) !== nothing dict[varclass][getname(var)][:connection_type] = nameof(ct) @@ -102,10 +103,16 @@ function generate_var!(dict, a, b, varclass) end var end -function parse_default(mod, a) + +function set_kwargs!(kwargs, a) + push!(kwargs, a) + return a +end + +function parse_default(mod, a, kwargs) a = Base.remove_linenums!(deepcopy(a)) MLStyle.@match a begin - Expr(:block, a) => get_var(mod, a) + Expr(:block, a) => set_kwargs!(kwargs, a) ::Symbol => get_var(mod, a) ::Number => a _ => error("Cannot parse default $a") @@ -141,10 +148,11 @@ function model_macro(mod, name, expr) ps = Symbol[] eqs = Expr[] icon = Ref{Union{String, URI}}() + kwargs = [] for arg in expr.args arg isa LineNumberNode && continue arg.head == :macrocall || error("$arg is not valid syntax. Expected a macro call.") - parse_model!(exprs.args, comps, ext, eqs, vs, ps, icon, dict, mod, arg) + parse_model!(exprs.args, comps, ext, eqs, vs, ps, icon, dict, mod, arg, kwargs) end iv = get(dict, :independent_variable, nothing) if iv === nothing @@ -159,10 +167,11 @@ function model_macro(mod, name, expr) else push!(exprs.args, :($extend($sys, $(ext[])))) end - :($name = $Model((; name) -> $exprs, $dict)) + @info "Exprs: $exprs" + :($name = $Model((; name, kwargs...) -> $exprs, $dict)) end -function parse_model!(exprs, comps, ext, eqs, vs, ps, icon, dict, mod, arg) +function parse_model!(exprs, comps, ext, eqs, vs, ps, icon, dict, mod, arg, kwargs) mname = arg.args[1] body = arg.args[end] if mname == Symbol("@components") @@ -170,9 +179,9 @@ function parse_model!(exprs, comps, ext, eqs, vs, ps, icon, dict, mod, arg) elseif mname == Symbol("@extend") parse_extend!(exprs, ext, dict, body) elseif mname == Symbol("@variables") - parse_variables!(exprs, vs, dict, mod, body, :variables) + parse_variables!(exprs, vs, dict, mod, body, :variables, kwargs) elseif mname == Symbol("@parameters") - parse_variables!(exprs, ps, dict, mod, body, :parameters) + parse_variables!(exprs, ps, dict, mod, body, :parameters, kwargs) elseif mname == Symbol("@equations") parse_equations!(exprs, eqs, dict, body) elseif mname == Symbol("@icon") @@ -231,12 +240,12 @@ function parse_extend!(exprs, ext, dict, body) end end -function parse_variables!(exprs, vs, dict, mod, body, varclass) +function parse_variables!(exprs, vs, dict, mod, body, varclass, kwargs) expr = Expr(:block) push!(exprs, expr) for arg in body.args arg isa LineNumberNode && continue - vv = parse_variable_def!(dict, mod, arg, varclass) + vv = parse_variable_def!(dict, mod, arg, varclass, kwargs) v = Num(vv) name = getname(v) push!(vs, name) From 6ab41e7274a2b8d324fd52bece6c42657c875af4 Mon Sep 17 00:00:00 2001 From: Venkateshprasad <32921645+ven-k@users.noreply.github.com> Date: Fri, 9 Jun 2023 22:23:42 +0530 Subject: [PATCH 02/12] feat: macro now sets the defaults of parameters and variables correctly to the passed kw - RN, codition when all passed vars and pars have a kw default works. - The `parse_variables_with_kw!` and `for_keyword_queue` will be modified suitably to ensure the existing methods will be used for rest of conditions --- src/systems/model_parsing.jl | 56 +++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index 7a8124e232..8f1e50dfce 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -72,6 +72,37 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs) end end +function for_keyword_queue() + # These args contain potential keywords + # Handle it along with vars without defaults +end + +# Takes in args and populates kw and var definition exprs. +# This should be modified to handle the other cases (i.e they should use existing +# methods) +function parse_variables_with_kw!(exprs, dict, mod, body, varclass, kwargs) + expr = if varclass == :parameters + :(ps = @parameters begin + end) + elseif varclass == :variables + :(vs = @variables begin + end) + end + + for arg in body.args + arg isa LineNumberNode && continue + MLStyle.@match arg begin + + Expr(:(=), a, b) => begin + def = Base.remove_linenums!(b).args[end] + push!(expr.args[end].args[end].args, :($a = $def)) + push!(kwargs, def) + end + end + end + push!(exprs, expr) +end + function generate_var(a, varclass) var = Symbolics.variable(a) if varclass == :parameters @@ -104,15 +135,10 @@ function generate_var!(dict, a, b, varclass) var end -function set_kwargs!(kwargs, a) - push!(kwargs, a) - return a -end - function parse_default(mod, a, kwargs) a = Base.remove_linenums!(deepcopy(a)) MLStyle.@match a begin - Expr(:block, a) => set_kwargs!(kwargs, a) + Expr(:block, a) => get_var(mod, a) ::Symbol => get_var(mod, a) ::Number => a _ => error("Cannot parse default $a") @@ -132,7 +158,7 @@ function set_var_metadata(a, ms) a end function get_var(mod::Module, b) - b isa Symbol ? getproperty(mod, b) : b + b isa Symbol ? getproperty(mod, b) : for_keyword_queue() end macro model(name::Symbol, expr) @@ -144,15 +170,13 @@ function model_macro(mod, name, expr) dict = Dict{Symbol, Any}() comps = Symbol[] ext = Ref{Any}(nothing) - vs = Symbol[] - ps = Symbol[] eqs = Expr[] icon = Ref{Union{String, URI}}() kwargs = [] for arg in expr.args arg isa LineNumberNode && continue arg.head == :macrocall || error("$arg is not valid syntax. Expected a macro call.") - parse_model!(exprs.args, comps, ext, eqs, vs, ps, icon, dict, mod, arg, kwargs) + parse_model!(exprs.args, comps, ext, eqs, icon, dict, mod, arg, kwargs) end iv = get(dict, :independent_variable, nothing) if iv === nothing @@ -160,18 +184,18 @@ function model_macro(mod, name, expr) end gui_metadata = isassigned(icon) > 0 ? GUIMetadata(GlobalRef(mod, name), icon[]) : nothing - sys = :($ODESystem($Equation[$(eqs...)], $iv, [$(vs...)], [$(ps...)]; + sys = :($ODESystem($Equation[$(eqs...)], $iv, vs, ps; systems = [$(comps...)], name, gui_metadata = $gui_metadata)) if ext[] === nothing push!(exprs.args, sys) else push!(exprs.args, :($extend($sys, $(ext[])))) end - @info "Exprs: $exprs" - :($name = $Model((; name, kwargs...) -> $exprs, $dict)) + + :($name = $Model((; name, $(kwargs...)) -> $exprs, $dict)) end -function parse_model!(exprs, comps, ext, eqs, vs, ps, icon, dict, mod, arg, kwargs) +function parse_model!(exprs, comps, ext, eqs, icon, dict, mod, arg, kwargs) mname = arg.args[1] body = arg.args[end] if mname == Symbol("@components") @@ -179,9 +203,9 @@ function parse_model!(exprs, comps, ext, eqs, vs, ps, icon, dict, mod, arg, kwar elseif mname == Symbol("@extend") parse_extend!(exprs, ext, dict, body) elseif mname == Symbol("@variables") - parse_variables!(exprs, vs, dict, mod, body, :variables, kwargs) + parse_variables_with_kw!(exprs, dict, mod, body, :variables, kwargs) elseif mname == Symbol("@parameters") - parse_variables!(exprs, ps, dict, mod, body, :parameters, kwargs) + parse_variables_with_kw!(exprs, dict, mod, body, :parameters, kwargs) elseif mname == Symbol("@equations") parse_equations!(exprs, eqs, dict, body) elseif mname == Symbol("@icon") From b11a49726505d5bdd7194138ff384fe0e9800edb Mon Sep 17 00:00:00 2001 From: Venkateshprasad <32921645+ven-k@users.noreply.github.com> Date: Wed, 14 Jun 2023 11:54:51 +0530 Subject: [PATCH 03/12] feat: handle hierarchal kwargs of the Model `@named model_a = ModelA( model_b.component=1)` just works --- src/ModelingToolkit.jl | 2 ++ src/systems/abstractsystem.jl | 28 +++++++++++++++ src/systems/model_parsing.jl | 65 ++++++++++++++++++++++++++++++----- 3 files changed, 87 insertions(+), 8 deletions(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 93fdf5e5e3..1a721a2616 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -49,6 +49,8 @@ import SymbolicUtils.Code: toexpr import SymbolicUtils.Rewriters: Chain, Postwalk, Prewalk, Fixpoint import JuliaFormatter +using MLStyle + using Reexport using Symbolics: degree @reexport using Symbolics diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 768fd66c41..2215c25cd7 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -935,6 +935,32 @@ function split_assign(expr) name, call = expr.args end +varname_fix!(s) = return + +function varname_fix!(expr::Expr) + for arg in expr.args + MLStyle.@match arg begin + ::Symbol => continue + Expr(:kw, a) => varname_sanitization!(arg) + Expr(:parameters, a...) => begin + for _arg in arg.args + varname_sanitization!(_arg) + end + end + _ => @debug "skipping variable sanitization of $arg" + end + end +end + +varname_sanitization!(a) = return + +function varname_sanitization!(expr::Expr) + var_splits = split(string(expr.args[1]), ".") + if length(var_splits) > 1 + expr.args[1] = Symbol(join(var_splits, "__")) + end +end + function _named(name, call, runtime = false) has_kw = false call isa Expr || throw(Meta.ParseError("The rhs must be an Expr. Got $call.")) @@ -948,6 +974,8 @@ function _named(name, call, runtime = false) end end + varname_fix!(call) + if !has_kw param = Expr(:parameters) if length(call.args) == 1 diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index 8f1e50dfce..eaf3adc8c9 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -8,8 +8,6 @@ struct Model{F, S} end (m::Model)(args...; kw...) = m.f(args...; kw...) -using MLStyle - function connector_macro(mod, name, body) if !Meta.isexpr(body, :block) err = """ @@ -82,10 +80,10 @@ end # methods) function parse_variables_with_kw!(exprs, dict, mod, body, varclass, kwargs) expr = if varclass == :parameters - :(ps = @parameters begin + :(pss = @parameters begin end) elseif varclass == :variables - :(vs = @variables begin + :(vss = @variables begin end) end @@ -97,9 +95,12 @@ function parse_variables_with_kw!(exprs, dict, mod, body, varclass, kwargs) def = Base.remove_linenums!(b).args[end] push!(expr.args[end].args[end].args, :($a = $def)) push!(kwargs, def) + @info "\nIn $varclass $kwargs for arg: $arg" end + _ => "got $arg" end end + dict[:kwargs] = kwargs push!(exprs, expr) end @@ -173,6 +174,8 @@ function model_macro(mod, name, expr) eqs = Expr[] icon = Ref{Union{String, URI}}() kwargs = [] + vs, vss = [], [] + ps, pss = [], [] for arg in expr.args arg isa LineNumberNode && continue arg.head == :macrocall || error("$arg is not valid syntax. Expected a macro call.") @@ -184,14 +187,14 @@ function model_macro(mod, name, expr) end gui_metadata = isassigned(icon) > 0 ? GUIMetadata(GlobalRef(mod, name), icon[]) : nothing - sys = :($ODESystem($Equation[$(eqs...)], $iv, vs, ps; + sys = :($ODESystem($Equation[$(eqs...)], $iv, [], [$(ps...); pss...]; systems = [$(comps...)], name, gui_metadata = $gui_metadata)) if ext[] === nothing push!(exprs.args, sys) else push!(exprs.args, :($extend($sys, $(ext[])))) end - +@info "\nexprs $exprs final kwargs: $kwargs" :($name = $Model((; name, $(kwargs...)) -> $exprs, $dict)) end @@ -199,7 +202,7 @@ function parse_model!(exprs, comps, ext, eqs, icon, dict, mod, arg, kwargs) mname = arg.args[1] body = arg.args[end] if mname == Symbol("@components") - parse_components!(exprs, comps, dict, body) + parse_components!(exprs, comps, dict, body, kwargs) elseif mname == Symbol("@extend") parse_extend!(exprs, ext, dict, body) elseif mname == Symbol("@variables") @@ -215,18 +218,64 @@ function parse_model!(exprs, comps, ext, eqs, icon, dict, mod, arg, kwargs) end end -function parse_components!(exprs, cs, dict, body) +function var_rename(compname, varname::Expr, arglist) + @info typeof(varname) + compname = Symbol(compname, :__, varname.args[1]) + push!(arglist, Expr(:(=), compname, varname.args[2])) + @info "$(typeof(varname)) | the arglist @220 is $arglist" + return Expr(:kw, varname, compname) +end + +function var_rename(compname, varname, arglist) + compname = Symbol(compname, :__, varname) + push!(arglist, :($compname)) + @info "$(typeof(varname)) | the arglist @229 is $arglist" + return Expr(:kw, varname, compname) +end + +function component_args!(compname, comparg, arglist, varnamed) + for arg in comparg.args + arg isa LineNumberNode && continue + MLStyle.@match arg begin + Expr(:parameters, a, b) => begin + component_args!(compname, arg, arglist, varnamed) + end + Expr(:parameters, Expr) => begin + # push!(varnamed , var_rename(compname, a, arglist)) + push!(varnamed , var_rename.(Ref(compname), arg.args, Ref(arglist))) + end + Expr(:parameters, a) => begin + # push!(varnamed , var_rename(compname, a, arglist)) + for a_arg in a.args + push!(varnamed , var_rename(compname, a_arg, arglist)) + end + end + Expr(:kw, a, b) => begin + push!(varnamed , var_rename(compname, a, arglist)) + end + ::Symbol => continue + _ => @info "got $arg" + end + end +end + +function parse_components!(exprs, cs, dict, body, kwargs) expr = Expr(:block) push!(exprs, expr) comps = Vector{String}[] + varnamed = [] for arg in body.args arg isa LineNumberNode && continue MLStyle.@match arg begin Expr(:(=), a, b) => begin push!(cs, a) + component_args!(a, b, kwargs, varnamed) push!(comps, [String(a), String(b.args[1])]) arg = deepcopy(arg) b = deepcopy(arg.args[2]) + + b.args[2] = varnamed[1][1] + push!(b.args, Expr(:kw, :name, Meta.quot(a))) arg.args[2] = b push!(expr.args, arg) From 97e330deeecd6891407ad9fd5976942ec5432633 Mon Sep 17 00:00:00 2001 From: Venkateshprasad <32921645+ven-k@users.noreply.github.com> Date: Thu, 15 Jun 2023 16:59:54 +0530 Subject: [PATCH 04/12] feat: parse the components to appropriately add its args to model arglist --- src/systems/model_parsing.jl | 98 +++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 47 deletions(-) diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index eaf3adc8c9..9e6b092026 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -178,8 +178,13 @@ function model_macro(mod, name, expr) ps, pss = [], [] for arg in expr.args arg isa LineNumberNode && continue - arg.head == :macrocall || error("$arg is not valid syntax. Expected a macro call.") - parse_model!(exprs.args, comps, ext, eqs, icon, dict, mod, arg, kwargs) + if arg.head == :macrocall + parse_model!(exprs.args, comps, ext, eqs, icon, vs, varexpr, ps, parexpr, dict, mod, arg, kwargs) + elseif arg.head == :block + push!(exprs.args, arg) + else + error("$arg is not valid syntax. Expected a macro call.") + end end iv = get(dict, :independent_variable, nothing) if iv === nothing @@ -194,11 +199,10 @@ function model_macro(mod, name, expr) else push!(exprs.args, :($extend($sys, $(ext[])))) end -@info "\nexprs $exprs final kwargs: $kwargs" :($name = $Model((; name, $(kwargs...)) -> $exprs, $dict)) end -function parse_model!(exprs, comps, ext, eqs, icon, dict, mod, arg, kwargs) +function parse_model!(exprs, comps, ext, eqs, icon, vs, varexpr, ps, parexpr, dict, mod, arg, kwargs) mname = arg.args[1] body = arg.args[end] if mname == Symbol("@components") @@ -218,47 +222,7 @@ function parse_model!(exprs, comps, ext, eqs, icon, dict, mod, arg, kwargs) end end -function var_rename(compname, varname::Expr, arglist) - @info typeof(varname) - compname = Symbol(compname, :__, varname.args[1]) - push!(arglist, Expr(:(=), compname, varname.args[2])) - @info "$(typeof(varname)) | the arglist @220 is $arglist" - return Expr(:kw, varname, compname) -end - -function var_rename(compname, varname, arglist) - compname = Symbol(compname, :__, varname) - push!(arglist, :($compname)) - @info "$(typeof(varname)) | the arglist @229 is $arglist" - return Expr(:kw, varname, compname) -end - -function component_args!(compname, comparg, arglist, varnamed) - for arg in comparg.args - arg isa LineNumberNode && continue - MLStyle.@match arg begin - Expr(:parameters, a, b) => begin - component_args!(compname, arg, arglist, varnamed) - end - Expr(:parameters, Expr) => begin - # push!(varnamed , var_rename(compname, a, arglist)) - push!(varnamed , var_rename.(Ref(compname), arg.args, Ref(arglist))) - end - Expr(:parameters, a) => begin - # push!(varnamed , var_rename(compname, a, arglist)) - for a_arg in a.args - push!(varnamed , var_rename(compname, a_arg, arglist)) - end - end - Expr(:kw, a, b) => begin - push!(varnamed , var_rename(compname, a, arglist)) - end - ::Symbol => continue - _ => @info "got $arg" - end - end -end - +# components function parse_components!(exprs, cs, dict, body, kwargs) expr = Expr(:block) push!(exprs, expr) @@ -269,16 +233,16 @@ function parse_components!(exprs, cs, dict, body, kwargs) MLStyle.@match arg begin Expr(:(=), a, b) => begin push!(cs, a) - component_args!(a, b, kwargs, varnamed) push!(comps, [String(a), String(b.args[1])]) arg = deepcopy(arg) b = deepcopy(arg.args[2]) - b.args[2] = varnamed[1][1] + component_args!(a, b, expr, kwargs) push!(b.args, Expr(:kw, :name, Meta.quot(a))) arg.args[2] = b push!(expr.args, arg) + @info "\n\nExpr $expr, b: $b\n\n" end _ => error("`@components` only takes assignment expressions. Got $arg") end @@ -286,6 +250,46 @@ function parse_components!(exprs, cs, dict, body, kwargs) dict[:components] = comps end +function var_rename(compname, varname) + compname = Symbol(compname, :__, varname) +end + +function component_args!(a, b, expr, kwargs) + for i in 1:lastindex(b.args) + arg = b.args[i] + MLStyle.@match arg begin + ::Symbol => begin + if b.head == :parameters + _v = varname2(a, arg) + push!(kwargs, _v) + b.args[i] = Expr(:kw, arg, _v) + end + continue + end + Expr(:parameters, x...) => begin + component_args!(a, arg, expr, kwargs) + end + Expr(:kw, x) => begin + _v = varname2(a, x) + b.args[i] = Expr(:kw, x, _v) + push!(kwargs, _v) + end + Expr(:kw, x, y::Number) => begin + _v = varname2(a, x) + b.args[i] = Expr(:kw, x, _v) + push!(kwargs, Expr(:kw, _v, y)) + end + Expr(:kw, x, y) => begin + _v = varname2(a, x) + push!(expr.args, :($y = $_v)) + push!(kwargs, Expr(:kw, _v, y)) + end + _ => "Got this: $arg" + end + end +end + +# function parse_extend!(exprs, ext, dict, body) expr = Expr(:block) push!(exprs, expr) From 4e3cb7a7a632b02cd8da8eef963b7da6fbc1d2e4 Mon Sep 17 00:00:00 2001 From: Venkateshprasad <32921645+ven-k@users.noreply.github.com> Date: Thu, 15 Jun 2023 19:21:49 +0530 Subject: [PATCH 05/12] refactor: use existing parsers for vars/pars whenever they have defined vals --- src/systems/model_parsing.jl | 107 ++++++++++++++++++----------------- 1 file changed, 54 insertions(+), 53 deletions(-) diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index 9e6b092026..d1177bae16 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -24,6 +24,7 @@ function connector_macro(mod, name, body) vs = Num[] icon = Ref{Union{String, URI}}() dict = Dict{Symbol, Any}() + kwargs = [] for arg in body.args arg isa LineNumberNode && continue if arg.head == :macrocall && arg.args[1] == Symbol("@icon") @@ -70,38 +71,32 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs) end end -function for_keyword_queue() - # These args contain potential keywords - # Handle it along with vars without defaults -end - -# Takes in args and populates kw and var definition exprs. -# This should be modified to handle the other cases (i.e they should use existing -# methods) -function parse_variables_with_kw!(exprs, dict, mod, body, varclass, kwargs) - expr = if varclass == :parameters - :(pss = @parameters begin - end) - elseif varclass == :variables - :(vss = @variables begin - end) - end - +function parse_variables_with_kw!(exprs, var, dict, mod, body, varexpr, varclass, kwargs) for arg in body.args arg isa LineNumberNode && continue MLStyle.@match arg begin - + Expr(:(=), a, b::Number) => parse_variables!(exprs, var, dict, mod, arg, varclass, kwargs) + Expr(:(=), a, b::Symbol) => begin + isdefined(mod, b) ? + parse_variables!(exprs, var, dict, mod, arg, varclass, kwargs) : + push!(kwargs, b) + end Expr(:(=), a, b) => begin def = Base.remove_linenums!(b).args[end] - push!(expr.args[end].args[end].args, :($a = $def)) - push!(kwargs, def) - @info "\nIn $varclass $kwargs for arg: $arg" + MLStyle.@match def begin + Expr(:tuple, x::Symbol, y) || x::Symbol => begin + push!(varexpr.args[end].args[end].args, :($a = $def)) + push!(kwargs, x) + end + Expr(:tuple, x::Number, y) => parse_variables!(exprs, var, dict, mod, arg, varclass, kwargs) + ::Number => parse_variables!(exprs, var, dict, mod, arg, varclass, kwargs) + _ => @info "Got $def" + end end _ => "got $arg" end end dict[:kwargs] = kwargs - push!(exprs, expr) end function generate_var(a, varclass) @@ -111,6 +106,7 @@ function generate_var(a, varclass) end var end + function generate_var!(dict, a, varclass) var = generate_var(a, varclass) vd = get!(dict, varclass) do @@ -119,6 +115,7 @@ function generate_var!(dict, a, varclass) vd[a] = Dict{Symbol, Any}() var end + function generate_var!(dict, a, b, varclass) iv = generate_var(b, :variables) prev_iv = get!(dict, :independent_variable) do @@ -145,6 +142,7 @@ function parse_default(mod, a, kwargs) _ => error("Cannot parse default $a") end end + function parse_metadata(mod, a) MLStyle.@match a begin Expr(:vect, eles...) => Dict(parse_metadata(mod, e) for e in eles) @@ -152,14 +150,16 @@ function parse_metadata(mod, a) _ => error("Cannot parse metadata $a") end end + function set_var_metadata(a, ms) for (m, v) in ms a = setmetadata(a, m, v) end a end + function get_var(mod::Module, b) - b isa Symbol ? getproperty(mod, b) : for_keyword_queue() + b isa Symbol ? getproperty(mod, b) : b end macro model(name::Symbol, expr) @@ -173,9 +173,13 @@ function model_macro(mod, name, expr) ext = Ref{Any}(nothing) eqs = Expr[] icon = Ref{Union{String, URI}}() + vs = [] + ps = [] + parexpr = :(pss = @parameters begin + end) + varexpr = :(vss = @variables begin + end) kwargs = [] - vs, vss = [], [] - ps, pss = [], [] for arg in expr.args arg isa LineNumberNode && continue if arg.head == :macrocall @@ -190,9 +194,14 @@ function model_macro(mod, name, expr) if iv === nothing iv = dict[:independent_variable] = variable(:t) end + + push!(exprs.args, varexpr) + push!(exprs.args, parexpr) + gui_metadata = isassigned(icon) > 0 ? GUIMetadata(GlobalRef(mod, name), icon[]) : - nothing - sys = :($ODESystem($Equation[$(eqs...)], $iv, [], [$(ps...); pss...]; + nothing + + sys = :($ODESystem($Equation[$(eqs...)], $iv, [$(vs...), vss...], [$(ps...), pss...]; systems = [$(comps...)], name, gui_metadata = $gui_metadata)) if ext[] === nothing push!(exprs.args, sys) @@ -210,9 +219,9 @@ function parse_model!(exprs, comps, ext, eqs, icon, vs, varexpr, ps, parexpr, di elseif mname == Symbol("@extend") parse_extend!(exprs, ext, dict, body) elseif mname == Symbol("@variables") - parse_variables_with_kw!(exprs, dict, mod, body, :variables, kwargs) + parse_variables_with_kw!(exprs, vs, dict, mod, body, varexpr, :variables, kwargs) elseif mname == Symbol("@parameters") - parse_variables_with_kw!(exprs, dict, mod, body, :parameters, kwargs) + parse_variables_with_kw!(exprs, ps, dict, mod, body, parexpr, :parameters, kwargs) elseif mname == Symbol("@equations") parse_equations!(exprs, eqs, dict, body) elseif mname == Symbol("@icon") @@ -222,12 +231,10 @@ function parse_model!(exprs, comps, ext, eqs, icon, vs, varexpr, ps, parexpr, di end end -# components function parse_components!(exprs, cs, dict, body, kwargs) expr = Expr(:block) push!(exprs, expr) comps = Vector{String}[] - varnamed = [] for arg in body.args arg isa LineNumberNode && continue MLStyle.@match arg begin @@ -242,7 +249,6 @@ function parse_components!(exprs, cs, dict, body, kwargs) push!(b.args, Expr(:kw, :name, Meta.quot(a))) arg.args[2] = b push!(expr.args, arg) - @info "\n\nExpr $expr, b: $b\n\n" end _ => error("`@components` only takes assignment expressions. Got $arg") end @@ -250,37 +256,35 @@ function parse_components!(exprs, cs, dict, body, kwargs) dict[:components] = comps end -function var_rename(compname, varname) +function _rename(compname, varname) compname = Symbol(compname, :__, varname) end function component_args!(a, b, expr, kwargs) - for i in 1:lastindex(b.args) + for i in 2:lastindex(b.args) arg = b.args[i] + arg isa LineNumberNode && continue MLStyle.@match arg begin ::Symbol => begin - if b.head == :parameters - _v = varname2(a, arg) - push!(kwargs, _v) - b.args[i] = Expr(:kw, arg, _v) - end - continue + _v = _rename(a, arg) + push!(kwargs, _v) + b.args[i] = Expr(:kw, arg, _v) end Expr(:parameters, x...) => begin component_args!(a, arg, expr, kwargs) end Expr(:kw, x) => begin - _v = varname2(a, x) + _v = _rename(a, x) b.args[i] = Expr(:kw, x, _v) push!(kwargs, _v) end Expr(:kw, x, y::Number) => begin - _v = varname2(a, x) + _v = _rename(a, x) b.args[i] = Expr(:kw, x, _v) push!(kwargs, Expr(:kw, _v, y)) end Expr(:kw, x, y) => begin - _v = varname2(a, x) + _v = _rename(a, x) push!(expr.args, :($y = $_v)) push!(kwargs, Expr(:kw, _v, y)) end @@ -289,7 +293,6 @@ function component_args!(a, b, expr, kwargs) end end -# function parse_extend!(exprs, ext, dict, body) expr = Expr(:block) push!(exprs, expr) @@ -317,17 +320,15 @@ function parse_extend!(exprs, ext, dict, body) end end -function parse_variables!(exprs, vs, dict, mod, body, varclass, kwargs) +function parse_variables!(exprs, vs, dict, mod, arg, varclass, kwargs) expr = Expr(:block) push!(exprs, expr) - for arg in body.args - arg isa LineNumberNode && continue - vv = parse_variable_def!(dict, mod, arg, varclass, kwargs) - v = Num(vv) - name = getname(v) - push!(vs, name) - push!(expr.args, :($name = $v)) - end + arg isa LineNumberNode && return + vv = parse_variable_def!(dict, mod, arg, varclass, kwargs) + v = Num(vv) + name = getname(v) + push!(vs, name) + push!(expr.args, :($name = $v)) end function parse_equations!(exprs, eqs, dict, body) From 879383a20dca9ac5bc7a40bc8e6cc36b2bd1f814 Mon Sep 17 00:00:00 2001 From: Venkateshprasad <32921645+ven-k@users.noreply.github.com> Date: Tue, 20 Jun 2023 15:13:23 +0530 Subject: [PATCH 06/12] feat: explicitly specify the model kwargs and args --- src/systems/model_parsing.jl | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index d1177bae16..3e83d3f5d1 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -79,17 +79,17 @@ function parse_variables_with_kw!(exprs, var, dict, mod, body, varexpr, varclass Expr(:(=), a, b::Symbol) => begin isdefined(mod, b) ? parse_variables!(exprs, var, dict, mod, arg, varclass, kwargs) : - push!(kwargs, b) + push!(varexpr.args[end].args[end].args, arg) end Expr(:(=), a, b) => begin - def = Base.remove_linenums!(b).args[end] + def = Base.remove_linenums!(b) MLStyle.@match def begin Expr(:tuple, x::Symbol, y) || x::Symbol => begin - push!(varexpr.args[end].args[end].args, :($a = $def)) - push!(kwargs, x) + push!(varexpr.args[end].args[end].args, :($a = $(def.args[end]))) end - Expr(:tuple, x::Number, y) => parse_variables!(exprs, var, dict, mod, arg, varclass, kwargs) + Expr(:tuple, x::Number, y) => (@info "111"; parse_variables!(exprs, var, dict, mod, arg, varclass, kwargs)) ::Number => parse_variables!(exprs, var, dict, mod, arg, varclass, kwargs) + ::Expr => push!(varexpr.args[end].args[end].args, :($a = $(def.args[end]))) _ => @info "Got $def" end end @@ -166,7 +166,21 @@ macro model(name::Symbol, expr) esc(model_macro(__module__, name, expr)) end -function model_macro(mod, name, expr) +@inline is_kwarg(::Symbol) = false +@inline is_kwarg(e::Expr) = (e.head == :parameters) + +macro model(fcall::Expr, expr) + fcall.head == :call || "Couldn't comprehend the model $arg" + + arglist, kwargs = if lastindex(fcall.args) > 1 && is_kwarg(fcall.args[2]) + (lastindex(fcall.args) > 2 ? (@info 1; Set(fcall.args[3:end])) : (@info 2; Set())), Set(fcall.args[2].args) + else + Set(), Set(fcall.args[2:end]) + end + esc(model_macro(__module__, fcall.args[1], expr; arglist, kwargs)) +end + +function model_macro(mod, name, expr; arglist = Set([]), kwargs = Set([])) exprs = Expr(:block) dict = Dict{Symbol, Any}() comps = Symbol[] @@ -179,7 +193,7 @@ function model_macro(mod, name, expr) end) varexpr = :(vss = @variables begin end) - kwargs = [] + for arg in expr.args arg isa LineNumberNode && continue if arg.head == :macrocall @@ -208,7 +222,8 @@ function model_macro(mod, name, expr) else push!(exprs.args, :($extend($sys, $(ext[])))) end - :($name = $Model((; name, $(kwargs...)) -> $exprs, $dict)) + + :($name = $Model(($(arglist...); name, $(kwargs...)) -> $exprs, $dict)) end function parse_model!(exprs, comps, ext, eqs, icon, vs, varexpr, ps, parexpr, dict, mod, arg, kwargs) @@ -261,7 +276,8 @@ function _rename(compname, varname) end function component_args!(a, b, expr, kwargs) - for i in 2:lastindex(b.args) + start = b.head == :parameters ? 1 : 2 + for i in start:lastindex(b.args) arg = b.args[i] arg isa LineNumberNode && continue MLStyle.@match arg begin From 43034feb48d3374b6a4827a79307fd580af789b1 Mon Sep 17 00:00:00 2001 From: Venkateshprasad <32921645+ven-k@users.noreply.github.com> Date: Wed, 21 Jun 2023 00:39:16 +0530 Subject: [PATCH 07/12] fix: set metadata correctly whenever var/par has a default value --- src/systems/model_parsing.jl | 85 ++++++++++++++++++++++-------------- 1 file changed, 53 insertions(+), 32 deletions(-) diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index 3e83d3f5d1..6ac3173a0e 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -24,14 +24,13 @@ function connector_macro(mod, name, body) vs = Num[] icon = Ref{Union{String, URI}}() dict = Dict{Symbol, Any}() - kwargs = [] for arg in body.args arg isa LineNumberNode && continue if arg.head == :macrocall && arg.args[1] == Symbol("@icon") parse_icon!(icon, dict, dict, arg.args[end]) continue end - push!(vs, Num(parse_variable_def!(dict, mod, arg, :variables, kwargs))) + push!(vs, Num(parse_variable_def!(dict, mod, arg, :variables))) end iv = get(dict, :independent_variable, nothing) if iv === nothing @@ -48,19 +47,25 @@ function connector_macro(mod, name, body) end end -function parse_variable_def!(dict, mod, arg, varclass, kwargs) +function parse_variable_def!(dict, mod, arg, varclass) MLStyle.@match arg begin ::Symbol => generate_var!(dict, arg, varclass) Expr(:call, a, b) => generate_var!(dict, a, b, varclass) Expr(:(=), a, b) => begin - var = parse_variable_def!(dict, mod, a, varclass, kwargs) - def = parse_default(mod, b, kwargs) + var = parse_variable_def!(dict, mod, a, varclass) + def, meta = parse_default(mod, b) dict[varclass][getname(var)][:default] = def var = setdefault(var, def) + if !isnothing(meta) + if (ct = get(meta, VariableConnectType, nothing)) !== nothing + dict[varclass][getname(var)][:connection_type] = nameof(ct) + end + var = set_var_metadata(var, meta) + end var end Expr(:tuple, a, b) => begin - var = parse_variable_def!(dict, mod, a, varclass, kwargs) + var = parse_variable_def!(dict, mod, a, varclass) meta = parse_metadata(mod, b) if (ct = get(meta, VariableConnectType, nothing)) !== nothing dict[varclass][getname(var)][:connection_type] = nameof(ct) @@ -75,25 +80,33 @@ function parse_variables_with_kw!(exprs, var, dict, mod, body, varexpr, varclass for arg in body.args arg isa LineNumberNode && continue MLStyle.@match arg begin - Expr(:(=), a, b::Number) => parse_variables!(exprs, var, dict, mod, arg, varclass, kwargs) + ::Symbol || Expr(:tuple, a, b) || Expr(:call, a, b) || Expr(:(=), a, b::Number) => begin + parse_variables!(exprs, var, dict, mod, arg, varclass) + end Expr(:(=), a, b::Symbol) => begin isdefined(mod, b) ? - parse_variables!(exprs, var, dict, mod, arg, varclass, kwargs) : - push!(varexpr.args[end].args[end].args, arg) + parse_variables!(exprs, var, dict, mod, arg, varclass) : + push!(varexpr.args[end].args[end].args, arg) end Expr(:(=), a, b) => begin def = Base.remove_linenums!(b) MLStyle.@match def begin - Expr(:tuple, x::Symbol, y) || x::Symbol => begin - push!(varexpr.args[end].args[end].args, :($a = $(def.args[end]))) + Expr(:tuple, x::Symbol, y) => begin + isdefined(mod, x) ? + parse_variables!(exprs, var, dict, mod, arg, varclass) : + push!(varexpr.args[end].args[end].args, arg) end - Expr(:tuple, x::Number, y) => (@info "111"; parse_variables!(exprs, var, dict, mod, arg, varclass, kwargs)) - ::Number => parse_variables!(exprs, var, dict, mod, arg, varclass, kwargs) - ::Expr => push!(varexpr.args[end].args[end].args, :($a = $(def.args[end]))) - _ => @info "Got $def" + ::Symbol => push!(varexpr.args[end].args[end].args, + :($a = $(def.args[end]))) + ::Number || Expr(:tuple, x::Number, y) => begin + parse_variables!(exprs, var, dict, mod, arg, varclass) + end + ::Expr => push!(varexpr.args[end].args[end].args, + :($a = $(def.args[end]))) + _ => error("Got $def") end end - _ => "got $arg" + _ => error("Could not parse this $varclass definition $arg") end end dict[:kwargs] = kwargs @@ -133,12 +146,17 @@ function generate_var!(dict, a, b, varclass) var end -function parse_default(mod, a, kwargs) +function parse_default(mod, a) a = Base.remove_linenums!(deepcopy(a)) MLStyle.@match a begin - Expr(:block, a) => get_var(mod, a) - ::Symbol => get_var(mod, a) - ::Number => a + Expr(:block, x) => parse_default(mod, x) + Expr(:tuple, x, y) => begin + def, _ = parse_default(mod, x) + meta = parse_metadata(mod, y) + (def, meta) + end + ::Symbol => (get_var(mod, a), nothing) + ::Number => (a, nothing) _ => error("Cannot parse default $a") end end @@ -173,7 +191,8 @@ macro model(fcall::Expr, expr) fcall.head == :call || "Couldn't comprehend the model $arg" arglist, kwargs = if lastindex(fcall.args) > 1 && is_kwarg(fcall.args[2]) - (lastindex(fcall.args) > 2 ? (@info 1; Set(fcall.args[3:end])) : (@info 2; Set())), Set(fcall.args[2].args) + (lastindex(fcall.args) > 2 ? Set(fcall.args[3:end]) : Set()), + Set(fcall.args[2].args) else Set(), Set(fcall.args[2:end]) end @@ -189,15 +208,14 @@ function model_macro(mod, name, expr; arglist = Set([]), kwargs = Set([])) icon = Ref{Union{String, URI}}() vs = [] ps = [] - parexpr = :(pss = @parameters begin - end) - varexpr = :(vss = @variables begin - end) + parexpr = :(pss = @parameters begin end) + varexpr = :(vss = @variables begin end) for arg in expr.args arg isa LineNumberNode && continue if arg.head == :macrocall - parse_model!(exprs.args, comps, ext, eqs, icon, vs, varexpr, ps, parexpr, dict, mod, arg, kwargs) + parse_model!(exprs.args, comps, ext, eqs, icon, vs, varexpr, ps, + parexpr, dict, mod, arg, kwargs) elseif arg.head == :block push!(exprs.args, arg) else @@ -213,7 +231,7 @@ function model_macro(mod, name, expr; arglist = Set([]), kwargs = Set([])) push!(exprs.args, parexpr) gui_metadata = isassigned(icon) > 0 ? GUIMetadata(GlobalRef(mod, name), icon[]) : - nothing + nothing sys = :($ODESystem($Equation[$(eqs...)], $iv, [$(vs...), vss...], [$(ps...), pss...]; systems = [$(comps...)], name, gui_metadata = $gui_metadata)) @@ -226,7 +244,8 @@ function model_macro(mod, name, expr; arglist = Set([]), kwargs = Set([])) :($name = $Model(($(arglist...); name, $(kwargs...)) -> $exprs, $dict)) end -function parse_model!(exprs, comps, ext, eqs, icon, vs, varexpr, ps, parexpr, dict, mod, arg, kwargs) +function parse_model!(exprs, comps, ext, eqs, icon, vs, varexpr, ps, parexpr, dict, + mod, arg, kwargs) mname = arg.args[1] body = arg.args[end] if mname == Symbol("@components") @@ -276,7 +295,9 @@ function _rename(compname, varname) end function component_args!(a, b, expr, kwargs) - start = b.head == :parameters ? 1 : 2 + # Whenever `b` is a function call, skip the first arg aka the function name. + # Whenver it is a kwargs list, include it. + start = b.head == :call ? 2 : 1 for i in start:lastindex(b.args) arg = b.args[i] arg isa LineNumberNode && continue @@ -304,7 +325,7 @@ function component_args!(a, b, expr, kwargs) push!(expr.args, :($y = $_v)) push!(kwargs, Expr(:kw, _v, y)) end - _ => "Got this: $arg" + _ => error("Could not parse $arg of component $a") end end end @@ -336,11 +357,11 @@ function parse_extend!(exprs, ext, dict, body) end end -function parse_variables!(exprs, vs, dict, mod, arg, varclass, kwargs) +function parse_variables!(exprs, vs, dict, mod, arg, varclass) expr = Expr(:block) push!(exprs, expr) arg isa LineNumberNode && return - vv = parse_variable_def!(dict, mod, arg, varclass, kwargs) + vv = parse_variable_def!(dict, mod, arg, varclass) v = Num(vv) name = getname(v) push!(vs, name) From 057baf9953aa57c06f67c3145f5dcd216a7ca209 Mon Sep 17 00:00:00 2001 From: Venkateshprasad <32921645+ven-k@users.noreply.github.com> Date: Thu, 22 Jun 2023 02:40:42 +0530 Subject: [PATCH 08/12] test: add tests to verify heirarchal kwargs and metadata and default values of different parameter definitions --- test/model_parsing.jl | 55 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/test/model_parsing.jl b/test/model_parsing.jl index ae268a77e0..ce078923c7 100644 --- a/test/model_parsing.jl +++ b/test/model_parsing.jl @@ -1,5 +1,5 @@ using ModelingToolkit, Test -using ModelingToolkit: get_gui_metadata +using ModelingToolkit: get_gui_metadata, VariableDescription, getdefault using URIs: URI ENV["MTK_ICONS_DIR"] = "$(@__DIR__)/icons" @@ -10,12 +10,12 @@ end @connector RealOutput begin u(t), [output = true] end -@model Constant begin +@model Constant(; k = 1) begin @components begin output = RealOutput() end @parameters begin - k, [description = "Constant output value of block"] + k = k, [description = "Constant output value of block"] end @equations begin output.u ~ k @@ -61,10 +61,10 @@ end end resistor_log = "$(@__DIR__)/logo/resistor.svg" -@model Resistor begin +@model Resistor(; R = 1) begin @extend v, i = oneport = OnePort() @parameters begin - R = 1 + R = R end @icon begin """ @@ -87,10 +87,10 @@ l15 0" stroke="black" stroke-width="1" stroke-linejoin="bevel" fill="none"> Date: Thu, 22 Jun 2023 10:09:01 +0530 Subject: [PATCH 09/12] refactor: setdefault value while executing Model.f whenever def is a kwarg --- src/systems/model_parsing.jl | 92 ++++++++++++------------------------ 1 file changed, 30 insertions(+), 62 deletions(-) diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index 6ac3173a0e..9e22b0bef6 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -48,70 +48,38 @@ function connector_macro(mod, name, body) end function parse_variable_def!(dict, mod, arg, varclass) + arg isa LineNumberNode && return MLStyle.@match arg begin - ::Symbol => generate_var!(dict, arg, varclass) - Expr(:call, a, b) => generate_var!(dict, a, b, varclass) + ::Symbol => (generate_var!(dict, arg, varclass), nothing) + Expr(:call, a, b) => (generate_var!(dict, a, b, varclass), nothing) Expr(:(=), a, b) => begin - var = parse_variable_def!(dict, mod, a, varclass) + var, _ = parse_variable_def!(dict, mod, a, varclass) def, meta = parse_default(mod, b) dict[varclass][getname(var)][:default] = def - var = setdefault(var, def) + if typeof(def) != Symbol + var = setdefault(var, def) + def = nothing + end if !isnothing(meta) if (ct = get(meta, VariableConnectType, nothing)) !== nothing dict[varclass][getname(var)][:connection_type] = nameof(ct) end var = set_var_metadata(var, meta) end - var + (var, def) end Expr(:tuple, a, b) => begin - var = parse_variable_def!(dict, mod, a, varclass) + var, _ = parse_variable_def!(dict, mod, a, varclass) meta = parse_metadata(mod, b) if (ct = get(meta, VariableConnectType, nothing)) !== nothing dict[varclass][getname(var)][:connection_type] = nameof(ct) end - set_var_metadata(var, meta) + (set_var_metadata(var, meta), nothing) end _ => error("$arg cannot be parsed") end end -function parse_variables_with_kw!(exprs, var, dict, mod, body, varexpr, varclass, kwargs) - for arg in body.args - arg isa LineNumberNode && continue - MLStyle.@match arg begin - ::Symbol || Expr(:tuple, a, b) || Expr(:call, a, b) || Expr(:(=), a, b::Number) => begin - parse_variables!(exprs, var, dict, mod, arg, varclass) - end - Expr(:(=), a, b::Symbol) => begin - isdefined(mod, b) ? - parse_variables!(exprs, var, dict, mod, arg, varclass) : - push!(varexpr.args[end].args[end].args, arg) - end - Expr(:(=), a, b) => begin - def = Base.remove_linenums!(b) - MLStyle.@match def begin - Expr(:tuple, x::Symbol, y) => begin - isdefined(mod, x) ? - parse_variables!(exprs, var, dict, mod, arg, varclass) : - push!(varexpr.args[end].args[end].args, arg) - end - ::Symbol => push!(varexpr.args[end].args[end].args, - :($a = $(def.args[end]))) - ::Number || Expr(:tuple, x::Number, y) => begin - parse_variables!(exprs, var, dict, mod, arg, varclass) - end - ::Expr => push!(varexpr.args[end].args[end].args, - :($a = $(def.args[end]))) - _ => error("Got $def") - end - end - _ => error("Could not parse this $varclass definition $arg") - end - end - dict[:kwargs] = kwargs -end - function generate_var(a, varclass) var = Symbolics.variable(a) if varclass == :parameters @@ -155,8 +123,7 @@ function parse_default(mod, a) meta = parse_metadata(mod, y) (def, meta) end - ::Symbol => (get_var(mod, a), nothing) - ::Number => (a, nothing) + ::Symbol || ::Number => (a, nothing) _ => error("Cannot parse default $a") end end @@ -208,14 +175,12 @@ function model_macro(mod, name, expr; arglist = Set([]), kwargs = Set([])) icon = Ref{Union{String, URI}}() vs = [] ps = [] - parexpr = :(pss = @parameters begin end) - varexpr = :(vss = @variables begin end) for arg in expr.args arg isa LineNumberNode && continue if arg.head == :macrocall - parse_model!(exprs.args, comps, ext, eqs, icon, vs, varexpr, ps, - parexpr, dict, mod, arg, kwargs) + parse_model!(exprs.args, comps, ext, eqs, icon, vs, ps, + dict, mod, arg, kwargs) elseif arg.head == :block push!(exprs.args, arg) else @@ -227,13 +192,10 @@ function model_macro(mod, name, expr; arglist = Set([]), kwargs = Set([])) iv = dict[:independent_variable] = variable(:t) end - push!(exprs.args, varexpr) - push!(exprs.args, parexpr) - gui_metadata = isassigned(icon) > 0 ? GUIMetadata(GlobalRef(mod, name), icon[]) : nothing - sys = :($ODESystem($Equation[$(eqs...)], $iv, [$(vs...), vss...], [$(ps...), pss...]; + sys = :($ODESystem($Equation[$(eqs...)], $iv, [$(vs...)], [$(ps...)]; systems = [$(comps...)], name, gui_metadata = $gui_metadata)) if ext[] === nothing push!(exprs.args, sys) @@ -244,7 +206,7 @@ function model_macro(mod, name, expr; arglist = Set([]), kwargs = Set([])) :($name = $Model(($(arglist...); name, $(kwargs...)) -> $exprs, $dict)) end -function parse_model!(exprs, comps, ext, eqs, icon, vs, varexpr, ps, parexpr, dict, +function parse_model!(exprs, comps, ext, eqs, icon, vs, ps, dict, mod, arg, kwargs) mname = arg.args[1] body = arg.args[end] @@ -253,9 +215,9 @@ function parse_model!(exprs, comps, ext, eqs, icon, vs, varexpr, ps, parexpr, di elseif mname == Symbol("@extend") parse_extend!(exprs, ext, dict, body) elseif mname == Symbol("@variables") - parse_variables_with_kw!(exprs, vs, dict, mod, body, varexpr, :variables, kwargs) + parse_variables!(exprs, vs, dict, mod, body, :variables) elseif mname == Symbol("@parameters") - parse_variables_with_kw!(exprs, ps, dict, mod, body, parexpr, :parameters, kwargs) + parse_variables!(exprs, ps, dict, mod, body, :parameters) elseif mname == Symbol("@equations") parse_equations!(exprs, eqs, dict, body) elseif mname == Symbol("@icon") @@ -357,15 +319,21 @@ function parse_extend!(exprs, ext, dict, body) end end -function parse_variables!(exprs, vs, dict, mod, arg, varclass) - expr = Expr(:block) - push!(exprs, expr) - arg isa LineNumberNode && return - vv = parse_variable_def!(dict, mod, arg, varclass) +function parse_variable_arg!(expr, vs, dict, mod, arg, varclass) + vv, def = parse_variable_def!(dict, mod, arg, varclass) v = Num(vv) name = getname(v) push!(vs, name) - push!(expr.args, :($name = $v)) + def === nothing ? push!(expr.args, :($name = $v)) : push!(expr.args, :($name = $setdefault($v, $def))) +end + +function parse_variables!(exprs, vs, dict, mod, body, varclass) + expr = Expr(:block) + push!(exprs, expr) + for arg in body.args + arg isa LineNumberNode && continue + parse_variable_arg!(expr, vs, dict, mod, arg, varclass) + end end function parse_equations!(exprs, eqs, dict, body) From a7f77f306d2fa80f563ef579a8168579dd6f01e8 Mon Sep 17 00:00:00 2001 From: Venkateshprasad <32921645+ven-k@users.noreply.github.com> Date: Thu, 22 Jun 2023 10:30:38 +0530 Subject: [PATCH 10/12] feat: connectors now accept args/kwargs + default values can be set for @variables in connector - This allows to pass default values of variables via kwargs --- src/systems/model_parsing.jl | 61 +++++++++++++++++++----------------- test/model_parsing.jl | 7 +++-- 2 files changed, 37 insertions(+), 31 deletions(-) diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index 9e22b0bef6..8ee58057e0 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -1,14 +1,33 @@ -macro connector(name::Symbol, body) - esc(connector_macro(__module__, name, body)) -end - struct Model{F, S} f::F structure::S end (m::Model)(args...; kw...) = m.f(args...; kw...) -function connector_macro(mod, name, body) +for f in (:connector, :model) + @eval begin + macro $f(name::Symbol, body) + esc($(Symbol(f, :_macro))(__module__, name, body)) + end + + macro $f(fcall::Expr, body) + fcall.head == :call || "Couldn't comprehend the $f $arg" + + arglist, kwargs = if lastindex(fcall.args) > 1 && is_kwarg(fcall.args[2]) + (lastindex(fcall.args) > 2 ? Set(fcall.args[3:end]) : Set()), + Set(fcall.args[2].args) + else + Set(), Set(fcall.args[2:end]) + end + esc($(Symbol(f, :_macro))(__module__, fcall.args[1], body; arglist, kwargs)) + end + end +end + +@inline is_kwarg(::Symbol) = false +@inline is_kwarg(e::Expr) = (e.head == :parameters) + +function connector_macro(mod, name, body; arglist = Set([]), kwargs = Set([])) if !Meta.isexpr(body, :block) err = """ connector body must be a block! It should be in the form of @@ -21,16 +40,17 @@ function connector_macro(mod, name, body) """ error(err) end - vs = Num[] + vs = [] icon = Ref{Union{String, URI}}() dict = Dict{Symbol, Any}() + expr = Expr(:block) for arg in body.args arg isa LineNumberNode && continue if arg.head == :macrocall && arg.args[1] == Symbol("@icon") parse_icon!(icon, dict, dict, arg.args[end]) continue end - push!(vs, Num(parse_variable_def!(dict, mod, arg, :variables))) + parse_variable_arg!(expr, vs, dict, mod, arg, :variables) end iv = get(dict, :independent_variable, nothing) if iv === nothing @@ -39,8 +59,9 @@ function connector_macro(mod, name, body) gui_metadata = isassigned(icon) ? GUIMetadata(GlobalRef(mod, name), icon[]) : nothing quote - $name = $Model((; name) -> begin - var"#___sys___" = $ODESystem($(Equation[]), $iv, $vs, $([]); + $name = $Model(($(arglist...); name, $(kwargs...)) -> begin + $expr + var"#___sys___" = $ODESystem($(Equation[]), $iv, [$(vs...)], $([]); name, gui_metadata = $gui_metadata) $Setfield.@set!(var"#___sys___".connector_type=$connector_type(var"#___sys___")) end, $dict) @@ -147,25 +168,6 @@ function get_var(mod::Module, b) b isa Symbol ? getproperty(mod, b) : b end -macro model(name::Symbol, expr) - esc(model_macro(__module__, name, expr)) -end - -@inline is_kwarg(::Symbol) = false -@inline is_kwarg(e::Expr) = (e.head == :parameters) - -macro model(fcall::Expr, expr) - fcall.head == :call || "Couldn't comprehend the model $arg" - - arglist, kwargs = if lastindex(fcall.args) > 1 && is_kwarg(fcall.args[2]) - (lastindex(fcall.args) > 2 ? Set(fcall.args[3:end]) : Set()), - Set(fcall.args[2].args) - else - Set(), Set(fcall.args[2:end]) - end - esc(model_macro(__module__, fcall.args[1], expr; arglist, kwargs)) -end - function model_macro(mod, name, expr; arglist = Set([]), kwargs = Set([])) exprs = Expr(:block) dict = Dict{Symbol, Any}() @@ -324,7 +326,8 @@ function parse_variable_arg!(expr, vs, dict, mod, arg, varclass) v = Num(vv) name = getname(v) push!(vs, name) - def === nothing ? push!(expr.args, :($name = $v)) : push!(expr.args, :($name = $setdefault($v, $def))) + def === nothing ? push!(expr.args, :($name = $v)) : + push!(expr.args, :($name = $setdefault($v, $def))) end function parse_variables!(exprs, vs, dict, mod, body, varclass) diff --git a/test/model_parsing.jl b/test/model_parsing.jl index ce078923c7..8daf8a4f45 100644 --- a/test/model_parsing.jl +++ b/test/model_parsing.jl @@ -25,12 +25,15 @@ end @variables t D = Differential(t) -@connector Pin begin - v(t) = 0 # Potential at the pin [V] +@connector Pin(; v_start = 0) begin + v(t) = v_start # Potential at the pin [V] i(t), [connect = Flow] # Current flowing into the pin [A] @icon "pin.png" end +@named p = Pin(; v_start = π) +@test getdefault(p.v) == π + @model OnePort begin @components begin p = Pin() From 5f56f04e93bb2e7219a9ac0fb3273e5fc7a8c339 Mon Sep 17 00:00:00 2001 From: Venkateshprasad <32921645+ven-k@users.noreply.github.com> Date: Mon, 26 Jun 2023 16:36:19 +0530 Subject: [PATCH 11/12] refactor: implicitly promote vars/pars as kwargs + allow expressions as default val of pars/vars + all pars/vars are promoted as kwarg with constant or `nothing` as the default value and update them as dict[:kwargs] --- src/systems/model_parsing.jl | 67 +++++++++++++++++++++--------------- test/model_parsing.jl | 46 ++++++++++++++----------- 2 files changed, 66 insertions(+), 47 deletions(-) diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index 8ee58057e0..7bc9b09e8f 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -9,18 +9,6 @@ for f in (:connector, :model) macro $f(name::Symbol, body) esc($(Symbol(f, :_macro))(__module__, name, body)) end - - macro $f(fcall::Expr, body) - fcall.head == :call || "Couldn't comprehend the $f $arg" - - arglist, kwargs = if lastindex(fcall.args) > 1 && is_kwarg(fcall.args[2]) - (lastindex(fcall.args) > 2 ? Set(fcall.args[3:end]) : Set()), - Set(fcall.args[2].args) - else - Set(), Set(fcall.args[2:end]) - end - esc($(Symbol(f, :_macro))(__module__, fcall.args[1], body; arglist, kwargs)) - end end end @@ -43,6 +31,7 @@ function connector_macro(mod, name, body; arglist = Set([]), kwargs = Set([])) vs = [] icon = Ref{Union{String, URI}}() dict = Dict{Symbol, Any}() + dict[:kwargs] = Dict{Symbol, Any}() expr = Expr(:block) for arg in body.args arg isa LineNumberNode && continue @@ -50,7 +39,7 @@ function connector_macro(mod, name, body; arglist = Set([]), kwargs = Set([])) parse_icon!(icon, dict, dict, arg.args[end]) continue end - parse_variable_arg!(expr, vs, dict, mod, arg, :variables) + parse_variable_arg!(expr, vs, dict, mod, arg, :variables, kwargs) end iv = get(dict, :independent_variable, nothing) if iv === nothing @@ -68,18 +57,33 @@ function connector_macro(mod, name, body; arglist = Set([]), kwargs = Set([])) end end -function parse_variable_def!(dict, mod, arg, varclass) +function parse_variable_def!(dict, mod, arg, varclass, kwargs, def = nothing) arg isa LineNumberNode && return MLStyle.@match arg begin - ::Symbol => (generate_var!(dict, arg, varclass), nothing) - Expr(:call, a, b) => (generate_var!(dict, a, b, varclass), nothing) + a::Symbol => begin + push!(kwargs, Expr(:kw, a, def)) + var = generate_var!(dict, a, varclass) + dict[:kwargs][getname(var)] = def + (var, nothing) + end + Expr(:call, a, b) => begin + push!(kwargs, Expr(:kw, a, def)) + var = generate_var!(dict, a, b, varclass) + dict[:kwargs][getname(var)] = def + (var, nothing) + end Expr(:(=), a, b) => begin - var, _ = parse_variable_def!(dict, mod, a, varclass) + Base.remove_linenums!(b) def, meta = parse_default(mod, b) + var, _ = parse_variable_def!(dict, mod, a, varclass, kwargs, def) dict[varclass][getname(var)][:default] = def if typeof(def) != Symbol var = setdefault(var, def) def = nothing + else + def in [keys(dict[:kwargs])...;] || + error("$def is not a known parameter or variable") + var = setdefault(var, def) end if !isnothing(meta) if (ct = get(meta, VariableConnectType, nothing)) !== nothing @@ -90,7 +94,7 @@ function parse_variable_def!(dict, mod, arg, varclass) (var, def) end Expr(:tuple, a, b) => begin - var, _ = parse_variable_def!(dict, mod, a, varclass) + var, _ = parse_variable_def!(dict, mod, a, varclass, kwargs) meta = parse_metadata(mod, b) if (ct = get(meta, VariableConnectType, nothing)) !== nothing dict[varclass][getname(var)][:connection_type] = nameof(ct) @@ -110,6 +114,7 @@ function generate_var(a, varclass) end function generate_var!(dict, a, varclass) + #var = generate_var(Symbol("#", a), varclass) var = generate_var(a, varclass) vd = get!(dict, varclass) do Dict{Symbol, Dict{Symbol, Any}}() @@ -145,6 +150,14 @@ function parse_default(mod, a) (def, meta) end ::Symbol || ::Number => (a, nothing) + Expr(:call, a...) => begin + def = parse_default.(Ref(mod), a) + expr = Expr(:call) + for (d, _) in def + push!(expr.args, d) + end + (expr, nothing) + end _ => error("Cannot parse default $a") end end @@ -171,6 +184,7 @@ end function model_macro(mod, name, expr; arglist = Set([]), kwargs = Set([])) exprs = Expr(:block) dict = Dict{Symbol, Any}() + dict[:kwargs] = Dict{Symbol, Any}() comps = Symbol[] ext = Ref{Any}(nothing) eqs = Expr[] @@ -198,7 +212,7 @@ function model_macro(mod, name, expr; arglist = Set([]), kwargs = Set([])) nothing sys = :($ODESystem($Equation[$(eqs...)], $iv, [$(vs...)], [$(ps...)]; - systems = [$(comps...)], name, gui_metadata = $gui_metadata)) + systems = [$(comps...)], name, gui_metadata = $gui_metadata)) #, defaults = $defaults)) if ext[] === nothing push!(exprs.args, sys) else @@ -217,9 +231,9 @@ function parse_model!(exprs, comps, ext, eqs, icon, vs, ps, dict, elseif mname == Symbol("@extend") parse_extend!(exprs, ext, dict, body) elseif mname == Symbol("@variables") - parse_variables!(exprs, vs, dict, mod, body, :variables) + parse_variables!(exprs, vs, dict, mod, body, :variables, kwargs) elseif mname == Symbol("@parameters") - parse_variables!(exprs, ps, dict, mod, body, :parameters) + parse_variables!(exprs, ps, dict, mod, body, :parameters, kwargs) elseif mname == Symbol("@equations") parse_equations!(exprs, eqs, dict, body) elseif mname == Symbol("@icon") @@ -321,21 +335,20 @@ function parse_extend!(exprs, ext, dict, body) end end -function parse_variable_arg!(expr, vs, dict, mod, arg, varclass) - vv, def = parse_variable_def!(dict, mod, arg, varclass) +function parse_variable_arg!(expr, vs, dict, mod, arg, varclass, kwargs) + vv, _ = parse_variable_def!(dict, mod, arg, varclass, kwargs) v = Num(vv) name = getname(v) push!(vs, name) - def === nothing ? push!(expr.args, :($name = $v)) : - push!(expr.args, :($name = $setdefault($v, $def))) + push!(expr.args, :($name = $name === nothing ? $vv : $setdefault($vv, $name))) end -function parse_variables!(exprs, vs, dict, mod, body, varclass) +function parse_variables!(exprs, vs, dict, mod, body, varclass, kwargs) expr = Expr(:block) push!(exprs, expr) for arg in body.args arg isa LineNumberNode && continue - parse_variable_arg!(expr, vs, dict, mod, arg, varclass) + parse_variable_arg!(expr, vs, dict, mod, arg, varclass, kwargs) end end diff --git a/test/model_parsing.jl b/test/model_parsing.jl index 8daf8a4f45..103f4914da 100644 --- a/test/model_parsing.jl +++ b/test/model_parsing.jl @@ -10,12 +10,12 @@ end @connector RealOutput begin u(t), [output = true] end -@model Constant(; k = 1) begin +@model Constant begin @components begin output = RealOutput() end @parameters begin - k = k, [description = "Constant output value of block"] + k, [description = "Constant output value of block"] end @equations begin output.u ~ k @@ -25,13 +25,13 @@ end @variables t D = Differential(t) -@connector Pin(; v_start = 0) begin - v(t) = v_start # Potential at the pin [V] +@connector Pin begin + v(t) # Potential at the pin [V] i(t), [connect = Flow] # Current flowing into the pin [A] @icon "pin.png" end -@named p = Pin(; v_start = π) +@named p = Pin(; v = π) @test getdefault(p.v) == π @model OnePort begin @@ -64,10 +64,10 @@ end end resistor_log = "$(@__DIR__)/logo/resistor.svg" -@model Resistor(; R = 1) begin +@model Resistor begin @extend v, i = oneport = OnePort() @parameters begin - R = R + R end @icon begin """ @@ -90,10 +90,10 @@ l15 0" stroke="black" stroke-width="1" stroke-linejoin="bevel" fill="none"> Date: Mon, 26 Jun 2023 10:16:39 -0400 Subject: [PATCH 12/12] Evaluate default at runtime to handle parametric defaults --- src/systems/model_parsing.jl | 21 +++++++-------------- test/model_parsing.jl | 9 +++++---- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index 7bc9b09e8f..2051165e5a 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -61,13 +61,13 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, def = nothing) arg isa LineNumberNode && return MLStyle.@match arg begin a::Symbol => begin - push!(kwargs, Expr(:kw, a, def)) + push!(kwargs, Expr(:kw, a, nothing)) var = generate_var!(dict, a, varclass) dict[:kwargs][getname(var)] = def (var, nothing) end Expr(:call, a, b) => begin - push!(kwargs, Expr(:kw, a, def)) + push!(kwargs, Expr(:kw, a, nothing)) var = generate_var!(dict, a, b, varclass) dict[:kwargs][getname(var)] = def (var, nothing) @@ -77,14 +77,6 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, def = nothing) def, meta = parse_default(mod, b) var, _ = parse_variable_def!(dict, mod, a, varclass, kwargs, def) dict[varclass][getname(var)][:default] = def - if typeof(def) != Symbol - var = setdefault(var, def) - def = nothing - else - def in [keys(dict[:kwargs])...;] || - error("$def is not a known parameter or variable") - var = setdefault(var, def) - end if !isnothing(meta) if (ct = get(meta, VariableConnectType, nothing)) !== nothing dict[varclass][getname(var)][:connection_type] = nameof(ct) @@ -94,12 +86,12 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, def = nothing) (var, def) end Expr(:tuple, a, b) => begin - var, _ = parse_variable_def!(dict, mod, a, varclass, kwargs) + var, def = parse_variable_def!(dict, mod, a, varclass, kwargs) meta = parse_metadata(mod, b) if (ct = get(meta, VariableConnectType, nothing)) !== nothing dict[varclass][getname(var)][:connection_type] = nameof(ct) end - (set_var_metadata(var, meta), nothing) + (set_var_metadata(var, meta), def) end _ => error("$arg cannot be parsed") end @@ -336,11 +328,12 @@ function parse_extend!(exprs, ext, dict, body) end function parse_variable_arg!(expr, vs, dict, mod, arg, varclass, kwargs) - vv, _ = parse_variable_def!(dict, mod, arg, varclass, kwargs) + vv, def = parse_variable_def!(dict, mod, arg, varclass, kwargs) v = Num(vv) name = getname(v) push!(vs, name) - push!(expr.args, :($name = $name === nothing ? $vv : $setdefault($vv, $name))) + push!(expr.args, + :($name = $name === nothing ? $setdefault($vv, $def) : $setdefault($vv, $name))) end function parse_variables!(exprs, vs, dict, mod, body, varclass, kwargs) diff --git a/test/model_parsing.jl b/test/model_parsing.jl index 103f4914da..506b187a77 100644 --- a/test/model_parsing.jl +++ b/test/model_parsing.jl @@ -152,7 +152,7 @@ end cval jval kval - c(t) = cval + cval + c(t) = cval + jval d = 2 e, [description = "e"] f = 3, [description = "f"] @@ -173,11 +173,12 @@ kval = 5 @test hasmetadata(model.j, VariableDescription) @test hasmetadata(model.k, VariableDescription) +model = complete(model) @test getdefault(model.cval) == 1 -@test getdefault(model.c) == 2 +@test isequal(getdefault(model.c), model.cval + model.jval) @test getdefault(model.d) == 2 @test_throws KeyError getdefault(model.e) @test getdefault(model.f) == 3 @test getdefault(model.i) == 4 -@test getdefault(model.j) == :jval -@test getdefault(model.k) == kval +@test isequal(getdefault(model.j), model.jval) +@test isequal(getdefault(model.k), model.kval)