ModelingToolkit.jl icon indicating copy to clipboard operation
ModelingToolkit.jl copied to clipboard

Handling of dependent defaults

Open YingboMa opened this issue 2 years ago • 5 comments

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.

YingboMa avatar Aug 22 '23 20:08 YingboMa

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.

ven-k avatar Aug 23 '23 06:08 ven-k

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))

ven-k avatar Aug 23 '23 06:08 ven-k

N/N I can update it to:

  1. Have a @misc_kwarg - a way to specify an input that isn't a parameter or a variable
  2. Whenever defaults are available, we can set them in the kwarg; if they aren't available we can default to nothing.
  3. 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)
  4. The kwargs of partial component are treated like kwargs of main component. So
    @mtkmodel Force begin
          ...
          # parameter block without `use_support`
        @extend (flange,) = partial_element = PartialElementaryOneFlangeAndSupport2(; use_support = false)
     end
    
    Will have Force(; name = :force, use_support = false, <other params>)

ven-k avatar Aug 23 '23 06:08 ven-k

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.

YingboMa avatar Aug 23 '23 14:08 YingboMa

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.

ven-k avatar Aug 24 '23 12:08 ven-k

Was this handled?

ChrisRackauckas avatar Feb 22 '24 13:02 ChrisRackauckas

It was handled by following PR. This can be closed.

  • https://github.com/SciML/ModelingToolkit.jl/pull/2226

ven-k avatar Feb 22 '24 17:02 ven-k