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

Making parameters `@parameters`

Open baggepinnen opened this issue 2 years ago • 8 comments

It would be nice if we could figure out a way to have the parameters of components be @parameters rather than hard-coded. Take this PID controller as an example

julia> pid
Model pid with 40 equations
States (42):
  reference₊u(t) [defaults to 0.0]
  measurement₊u(t) [defaults to 0.0]
  ctr_output₊u(t) [defaults to 0.0]
  addP₊input1₊u(t) [defaults to 0.0]
  addP₊input2₊u(t) [defaults to 0.0]
  addP₊output₊u(t) [defaults to 0.0]
  gainPID₊u(t) [defaults to 0.0]
  gainPID₊y(t) [defaults to 0.0]
  gainPID₊input₊u(t) [defaults to 0.0]
  gainPID₊output₊u(t) [defaults to 0.0]
  addPID₊input1₊u(t) [defaults to 0.0]
⋮
Parameters (19):
  addP₊k1 [defaults to 1]
  addP₊k2 [defaults to -1]
  gainPID₊k [defaults to 400]
  addPID₊k1 [defaults to 1]
  addPID₊k2 [defaults to 1]
  addPID₊k3 [defaults to 1]
  limiter₊y_max [defaults to 350]
  limiter₊y_min [defaults to -350]
  addSat₊k1 [defaults to 1]
  addSat₊k2 [defaults to -1]
  gainTrack₊k [defaults to 0.00176777]

where are k, Ti, Td that the user provided? They are likely in there somewhere, but not under their original names. I tried this a while back but ran into problems and made a comment https://github.com/baggepinnen/ModelingToolkitStandardLibrary.jl/blob/281ee9715512bbeacd054240f4fb56165f9b1b98/src/Blocks/continuous.jl#L157

When models get more complicated, it would be good to

  1. Keep names of parameters similar to the arguments the user provide, in the PID case, k, Ti, Td
  2. make sure they appear once and only once in the parameter list, i.e., not several times. In the current PID implementation, Ti=3 leads to int₊k => 0.333333 being the parameter, while the input argument k influences multiple different parameters in the resulting system.

What I have in mind is workflows like parameter tuning etc. where the user must be able to specify which parameters to tune. It's going to be rather hard to figure out how to tune the PID controller if the PID parameters do not appear (directly) in the system equations.

baggepinnen avatar May 10 '22 10:05 baggepinnen

Have you tested simply adding k, Ti, Td as @parameters to: https://github.com/SciML/ModelingToolkitStandardLibrary.jl/blob/f8bdbb9f91eadcf274c54dfcd5df94f189ffbd15/src/Blocks/continuous.jl#L310

They will end up as default values in other blocks, but that should not be a problem anymore: https://github.com/SciML/ModelingToolkit.jl/blob/master/test/symbolic_parameters.jl

ValentinKaisermayer avatar May 10 '22 10:05 ValentinKaisermayer

Ah cool, I didn't know that was fixed, thanks for pointing that out! Then we have one less problem to worry about. It currently appears to work for some parameters but not for others

julia> @parameters k=2 Ti=3 Td=4;

julia> @named pid = LimPID(; k) # works

julia> @named pid = LimPID(; k, Ti, Td)
ERROR: TypeError: non-boolean (Num) used in boolean context
Stacktrace:
 [1] top-level scope
   @ REPL[9]:1

It does not work for the time constants due to the argument checking

!isequal(Ti, false) && (Ti ≥ 0 || throw(ArgumentError("Ti out of bounds, got $(Ti) but expected Ti ≥ 0")))

I'm wondering if argument checking can be handled slightly differently. Either we can introduce some convenience functionality like a macro that only checks numerical values and leaves @parameters alone. Or we could somehow make use of the metadata system

julia> @parameters Ti [bounds=(0, Inf)]
1-element Vector{Num}:
 Ti

julia> getbounds(Ti)
(0, Inf)

and check parameter values once the final numerical values have been provided before the call to solve.

I would eventually like to add this kind of metadata everywhere it makes sense, it would make it really smooth when optimizing over parameters if trivial bounds like (0, Inf) were already in place by default. Not sure if it's the right tool for this kind of argument checking though. Suggestions welcome :)

baggepinnen avatar May 10 '22 11:05 baggepinnen

For normal stuff (e.g. parameter must be positive,...) it might be easy to use such a system. But in this specific case the parameter is "misused" as an option as well. Ti can be a numeric value or a bool. I'm not sure that MTK can handle this and maybe it should not and this is simply a bad way to do it.

But it would be nice to use the parameter bounds and simply let MTK do the check if the provided default values is ok.

ValentinKaisermayer avatar May 10 '22 15:05 ValentinKaisermayer

How could a relational check be done? e.g. a <= b where a and b are @parameters?

ValentinKaisermayer avatar May 12 '22 19:05 ValentinKaisermayer

The following already works, so maybe a function that checks parameters before solving could evaluate any symbolic expressions appearing in the bounds using the numerical values provided in the varmap.

julia> @parameters T [bounds = (0, Inf)]
1-element Vector{Num}:
 T

julia> @parameters k [bounds = (0, T^2+1)]
1-element Vector{Num}:
 k

julia> getbounds(k)
(0, 1 + T^2)

baggepinnen avatar May 13 '22 06:05 baggepinnen

You mean somewhere like here ?

ValentinKaisermayer avatar May 13 '22 11:05 ValentinKaisermayer

Exactly, the check could probably be performed as soon as numerical values are known for all parameters?

baggepinnen avatar May 13 '22 12:05 baggepinnen

Similar problem with Limiter

using ModelingToolkit
using ModelingToolkitStandardLibrary.Blocks: Limiter
@parameters y1=1 y2=2
Limiter(name=:foo, y_min=y1, y_max=y2)

giving

ERROR: TypeError: non-boolean (Num) used in boolean context
Stacktrace:
 [1] Limiter(; name::Symbol, y_max::Num, y_min::Num)
   @ ModelingToolkitStandardLibrary.Blocks C:\Users\jaakkor2\.julia\packages\ModelingToolkitStandardLibrary\iRfHC\src\Blocks\nonlinear.jl:18
 [2] top-level scope
   @ REPL[13]:1

jaakkor2 avatar Oct 07 '22 00:10 jaakkor2