ModelingToolkit.jl
ModelingToolkit.jl copied to clipboard
Handling of dependent defaults
Consider:
@mtkmodel Force begin
@parameters begin
use_support = false
s = 0
end
@extend (flange,) = partial_element = PartialElementaryOneFlangeAndSupport2(;
use_support = use_support)
@components begin
flange = Flange(; s = s)
f = RealInput()
end
@equations begin
flange.f ~ -f.u
end
end
the current macro expands it to
:(Force = (ModelingToolkit.Model)(((; name, use_support = nothing, s = nothing, partial_element__use_support = use_support, flange__s = s)->begin
#= /Users/scheme/src/julia/ModelingToolkit/src/systems/model_parsing.jl:243 =#
begin
begin
use_support = if use_support === nothing
(ModelingToolkit.setdefault)(use_support, false)
else
(ModelingToolkit.setdefault)(use_support, use_support)
end
s = if s === nothing
(ModelingToolkit.setdefault)(s, 0)
else
(ModelingToolkit.setdefault)(s, s)
end
end
begin
partial_element = PartialElementaryOneFlangeAndSupport2(name = :partial_element; use_support = partial_element__use_support)
begin
#= /Users/scheme/.julia/packages/UnPack/EkESO/src/UnPack.jl:100 =#
local var"##758" = partial_element
#= /Users/scheme/.julia/packages/UnPack/EkESO/src/UnPack.jl:101 =#
begin
flange = (UnPack).unpack(var"##758", Val{:flange}())
end
#= /Users/scheme/.julia/packages/UnPack/EkESO/src/UnPack.jl:102 =#
var"##758"
end
end
begin
flange = Flange(name = :flange; s = flange__s)
f = RealInput(name = :f)
end
(ModelingToolkit.extend)((ODESystem)((Equation)[flange.f ~ -(f.u)], t, [], [use_support, s]; systems = [flange, f], name, gui_metadata = ModelingToolkit.GUIMetadata(:(Main.Force), nothing)), partial_element)
end
end), Dict{Symbol, Any}(:components => [[:flange, :Flange], [:f, :RealInput]], :kwargs => Dict{Symbol, Any}(:use_support => false, :s => 0), :independent_variable => t, :extend => Any[[:flange], :partial_element, :PartialElementaryOneFlangeAndSupport2], :parameters => Dict{Symbol, Dict{Symbol, Any}}(:use_support => Dict(:default => false), :s => Dict(:default => 0)), :equations => ["flange.f ~ -(f.u)"]), false))
Note the use_support = nothing, s = nothing, partial_element__use_support = use_support in the arguments and the later instantiation partial_element = PartialElementaryOneFlangeAndSupport2(name = :partial_element; use_support = partial_element__use_support). This makes partial_element__use_support to be default to nothing.
@ven-k is there any particular reason to ways default a variable to be nothing in the argument? If we have use_support = false, s = nothing, partial_element__use_support = use_support in the argument, this particular problem will be fixed.
1/N
I remember implementing that and later we decided to default all kwargs to nothing.
However, whenever defaults are available, we can set them in the kwarg; if they aren't available we can default to nothing[A]. This would allow us to use that while setting the components' kwargs.
But then use_support will be an unnecessary parameter for Force component. We should have an @additional_kwargs or @misc_kwargs which allows us to set kwargs that need not be parameters of the main component.
This would additionally provide a way to pass functions etc... to the components.
2/N
- This PR https://github.com/SciML/ModelingToolkit.jl/pull/2226/ makes following changes:
[1] Sets the default value of sub-components using the symbolic values of the parameters. This allows s = 0 in parameters to be set to the Flange's s.
[2] The kwargs of partial components (the base sys in extend) are treated as kwargs of main component. So use_support of PartialElementaryOneFlangeAndSupport2 is now a kwarg of Force as is.
@mtkmodel Force begin
@parameters begin
s = 0
end
@extend (flange,) = partial_element = PartialElementaryOneFlangeAndSupport2(;
use_support)
@components begin
flange = Flange(; s = s)
f = RealInput()
end
@equations begin
flange.f ~ -f.u
end
end
expands to
:(Force = (ModelingToolkit.Model)(((; name, s = nothing, use_support = nothing, flange__s = nothing)->begin
#= c:\Users\user\.julia\dev\ModelingToolkit.jl\src\systems\model_parsing.jl:243 =#
begin
begin
s = if s === nothing
s
else
(ModelingToolkit.setdefault)(s, s)
end
end
begin
end
begin
partial_element = PartialElementaryOneFlangeAndSupport2(name = :partial_element; use_support)
begin
#= C:\Users\user\.julia\packages\UnPack\EkESO\src\UnPack.jl:100 =#
local var"##313" = partial_element
#= C:\Users\user\.julia\packages\UnPack\EkESO\src\UnPack.jl:101 =#
begin
flange = (UnPack).unpack(var"##313", Val{:flange}())
end
#= C:\Users\user\.julia\packages\UnPack\EkESO\src\UnPack.jl:102 =#
var"##313"
end
end
begin
flange__s = if flange__s === nothing
s
else
flange__s
end
end
begin
flange = begin
#= c:\Users\user\.julia\dev\ModelingToolkit.jl\src\systems\abstractsystem.jl:1005 =#
var"#####__is_system_construction####314" = Flange isa DataType && Flange <: ModelingToolkit.AbstractSystem
#= c:\Users\user\.julia\dev\ModelingToolkit.jl\src\systems\abstractsystem.jl:1006 =#
Flange(; name = :flange, s = if var"#####__is_system_construction####314"
flange__s
else
(ModelingToolkit.default_to_parentscope)(flange__s)
end)
end
f = begin
#= c:\Users\user\.julia\dev\ModelingToolkit.jl\src\systems\abstractsystem.jl:1005 =#
var"#####__is_system_construction####315" = RealInput isa DataType && RealInput <: ModelingToolkit.AbstractSystem
#= c:\Users\user\.julia\dev\ModelingToolkit.jl\src\systems\abstractsystem.jl:1006 =#
RealInput(; name = :f)
end
[flange; f]
end
(ModelingToolkit.extend)((ODESystem)((Equation)[flange.f ~ -(f.u)], t, [], [s]; systems = [flange, f], name, gui_metadata = ModelingToolkit.GUIMetadata(:(Main.Force), nothing)), partial_element)
end
end), Dict{Symbol, Any}(:components => [[:flange, :Flange], [:f, :RealInput]], :kwargs => Dict{Symbol, Any}(:use_support => nothing, :s => 0, :flange__s => nothing), :independent_variable => t, :extend => Any[[:flange], :partial_element, :PartialElementaryOneFlangeAndSupport2], :parameters => Dict{Symbol, Dict{Symbol, Any}}(:s => Dict(:default => 0)), :equations => ["flange.f ~ -(f.u)"]), false))
N/N I can update it to:
- Have a
@misc_kwarg- a way to specify an input that isn't a parameter or a variable - Whenever defaults are available, we can set them in the kwarg; if they aren't available we can default to nothing.
- Thus defaults of sub-components will be set in the argument list (and will not have symbolic defaults internally)
ex: in above case,
Force(; name = :force, s = 0, flange.s = s) - The kwargs of partial component are treated like kwargs of main component. So
Will have@mtkmodel Force begin ... # parameter block without `use_support` @extend (flange,) = partial_element = PartialElementaryOneFlangeAndSupport2(; use_support = false) endForce(; name = :force, use_support = false, <other params>)
I still don't quite understand why propagating defaults in the arguments would be undesirable from
However, whenever defaults are available, we can set them in the kwarg; if they aren't available we can default to nothing[A]. This would allow us to use that while setting the components' kwargs.
In #2226, with
@mtkmodel A begin
@parameters begin
a1 = 1
end
@components begin
bb = B(; b1 = a1)
end
end
If the argument looks like A(; a1 = nothing, B.b1 = nothing) doesn't that mean A(; a1 = 2) won't set B.b1 to 2? I think that's probably not intended. Or, do we have a branch that sets the default of B.b1 to the symbolic variable a1 whenever B.b1's argument is nothing?
Have a @misc_kwarg - a way to specify an input that isn't a parameter or a variable
We can call this a @structural_parameters, but maybe that's too long. Since it should be used for parameters that change the structure of equations and variables.
Thus defaults of sub-components will be set in the argument list (and will not have symbolic defaults internally) ex: in above case, Force(; name = :force, s = 0, flange.s = s)
I think we should still keep them symbolic, so that the higher level components can set them symbolic as well.
Or, do we have a branch that sets
Yes, that's exactly what 2226 PR does. With following snippet (note s is parameter)
begin
flange__s = if flange__s === nothing
s
else
flange__s
end
end
(taken from above comment)
@structural_parameters sounds good to me. If it is too long, I can think of @quasi_parameters (it is like a parameter, but not really one) or just @constants.
I think we should still keep them symbolic
If we decide to propagate via arguments and also keep the sub-component defaults symbolic, when Force(; name = :force, s = 0, flange__s = s) is called, flange__s is immediately assigned with s-argument-value. We have to assign flange__s to s-parameter inside the function* once again, rendering the former step unnecessary.
*Inside the function, we'll have to check if flange__s-argument-value is same as default value of s-parameter then assign s-parameter. If it isn't, then flange__s has been given a new value that should be directly be propagated.
Was this handled?
It was handled by following PR. This can be closed.
- https://github.com/SciML/ModelingToolkit.jl/pull/2226