Using MTK when I *import* ModelingToolkit?

I have some ModelingToolkit model that works when importing via using. Because of a name conflict, I need to import MTK as import ModelingToolkit as mtk instead. The following code works with using.... What modifications do I need to do if I use import...?

using ModelingToolkit
using ModelingToolkit: t_nounits as t, D_nounits as D
using OrdinaryDiffEq
#
@mtkmodel Model begin
    @structural_parameters begin
        nonlinear = true
    end
    #
    @parameters begin
        a = 2
        b = 3
    end
    #
    @variables begin
        x(t) = 1
    end
    @equations begin
        if nonlinear
            D(x) ~ -a*x -b*x^3
        else
            D(x) ~ -a*x
        end
    end
end
#
@mtkcompile m_n = Model()
@mtkcompile m_L = Model(;nonlinear=false)

This works.

When I change using ModelingToolkit to import ModelingToolkit as mtk, do I also need to do something with the import of time and derivative?

What about the macros? Do I need to change @mtkmodel to @mtk.mtkmodel? What about @parameters, etc.?

You need to somehow import all symbols, you can list the macros also in the using statements, like

using ModelingToolkit: t_nounits as t, D_nounits as D, @mtkmodel, @structural_parameters, @parameters

and so on

I do:

using ModelingToolkit: @mtkmodel, @structural_parameters, @parameters, @variables, @equations, @mtkcompile
using ModelingToolkit: t_nounits as t, D_nounits as D
using OrdinaryDiffEq

and get warnings:

WARNING: Imported binding ModelingToolkit.@structural_parameters was undeclared at import time during import to Main.
WARNING: Imported binding ModelingToolkit.@equations was undeclared at import time during import to Main.

Next, doing

@mtkcompile m_n = Model()
@mtkcompile m_L = Model(;nonlinear=false)

produces error message:

UndefVarError: `ModelingToolkit` not defined in `Main`
Suggestion: check for spelling errors or missing imports.
Hint: ModelingToolkit is loaded but not imported in the active module Main.

Stacktrace:
 [1] __Model__(; name::Symbol, nonlinear::Bool, a::ModelingToolkit.NoValue, b::ModelingToolkit.NoValue, x::ModelingToolkit.NoValue)
   @ Main C:\Users\Bernt\.julia\packages\ModelingToolkit\b28X4\src\systems\model_parsing.jl:159
 [2] __Model__
   @ C:\Users\Bernt\.julia\packages\ModelingToolkit\b28X4\src\systems\model_parsing.jl:159 [inlined]
 [3] #_#432
   @ C:\Users\Bernt\.julia\packages\ModelingToolkit\b28X4\src\systems\model_parsing.jl:25 [inlined]
 [4] top-level scope
   @ C:\Users\Bernt\.julia\packages\ModelingToolkit\b28X4\src\systems\abstractsystem.jl:2152

Hm. I decided it was probably simpler to do using ModelingToolkit and instead import the other package as import Molly as mly. The conflict between these is that both appear to expose a System constructor.

@structural_parameters, @parameters, @variables, @equations,

These are not actually macros, but are rather just flags to the parsing that @mtkmodel does, so you don’t need to import them.

That being said I would highly recommend you switch to the @component function MyModel(; name, params...) style of defining components, since @mtkmodel will soon be deprecated.

Wait what? @mtkmodel will be deprecated? Where can I find more information on that? I am producing quite a lot of models for a model library currently :anxious_face_with_sweat:

On topic: here is the issue on the missing imports: `@mtkmodel` macro requires imports from MTK · Issue #3640 · SciML/ModelingToolkit.jl · GitHub

1 Like

IIRC that decision was taken recently (and it will probably be a soft deprecation, but I am not sure exactly what the plan is there).

Thanks for the link to the issue! It looks like there’s a PR Fix @mtkmodel to work without using ModelingToolkit by cstjean · Pull Request #3643 · SciML/ModelingToolkit.jl · GitHub that fixes that, but is still unmerged.

Darn it… and now that I had learned to use the @mtkmodel “macro” :wink:

From the documentation:

@component function AddOne(; name)
    @variables in(t) out(t)
    eqs = [out ~ in + 1]
    return System(eqs, t, [in, out], []; name)
end

How should I then write the very rudimentary Model above? I mean, this snippet from the documentation is very limited.

And – should we still use @mtkcompile to instansiate the model?

Oof. That documentation isn’t so great. @ChrisRackauckas could you set a claude on that and I’ll review?

Basically the structure is

@component function MyComponent(; name, params..., structural_params...)
vars = Any[]
params = Any[]
systems = Any[]

@variables a, b, c
append!(vars, [a, b, c])
@parameters d::Real = d
@parameters e::Real = e
@parameters f::Real = f
append!(params, [d, e, f])

...

and you can find some good examples in the MTKStdlib: ModelingToolkitStandardLibrary.jl/src/Blocks/continuous.jl at 0e75c63c866819aa7dc23a703d7c2768a52fd52e · SciML/ModelingToolkitStandardLibrary.jl · GitHub

But surely, you have not dropped the possibility of adding description, like in

# Model variables, with initial values needed
    @variables begin
        # Variables
        x_1(t)=0,           [description = "Particle 1, position unit nm"]
        v_1(t)=0,           [description = "Particle 1, velocity unit nm/ps"]
        x_2(t)=1.5r_e,      [description = "Particle 2, position unit nm"]
        v_2(t)=0,           [description = "Particle 2, velocity unit nm/ps"]
    end

Metadata syntax is the same as before, I think, you just drop that into the variables macro as you showed there

So the MTK DSL for specifying models is being dropped completely (I assume in V11), and one only has the programmatic macros now? What is the reasoning behind this?

The @mtkmodel macro is increasingly difficult to support. It is 1500+ lines of incredibly dense code with non-trivial control flow and its own set of bugs. It already does not support all the features that the plain programmatic form supports (can’t specify guesses or initialization equations, for example), and does not offer a big enough benefit to warrant the maintenance that it requires.

@component function MyModel(; name, x = 1, q = rand(2, 2))
  vars = @variables begin
    x(t) = x, [description = "my description"]
    # other variables
  end
  pars = @parameters begin
    p
    q[1:2, 1:2] = q
    # other parameters
  end
  eqs = [
    D(x) ~ p * x
    # other equations
  ]
  defaults = Dict(
    p => 2.0
    # other defaults
  )
  return System(eqs, t, vars, pars; defaults)
end

As an example, the above form is pretty close to the @mtkmodel equivalent.

5 Likes

And yes, the plan is for a soft deprecation. We’re not just removing it. It’ll continue to work in about the same way as it does now but it won’t get additional maintenance or updates. Of course, anyone free to open PRs to fix bugs in @mtkmodel and I’ll happily review them.

Sorry for slightly hijacking the thread, but would it be conceivable to flesh out the @component style of model by adding a few more convenience macros? Not to “hide” the underlying construction in a full blown DSL but rather avoiding defining stuff in multiple places.

I envision something like that:

@component function MyModel(; name, struct_p = false, kwargs...)
    vars = @variables begin
        x(t)=1
        y(t)
    end
    params = @parameters begin
        p=2
    end
    systems = @components begin
        sub1 = SubSystem()
        sub2 = SubSystem()
    end
    eqs = @equations begin
        sub1.x ~ y
        if struct_p
            sub2.x ~ x
        else
            sub2.x ~ x+1
        end
    end
    System(eqs, t, vars, params; systems, name, kwargs...)
end

This would:

  • somehow automatically overwrite defaults for vars and params without explicitly listing them in the signature (i don’t know if “parsing” additional kwargs in the system constructor would be the way to go, but the important feature would be to not explicitly list all vars and params in the signature)
  • introduce @components to collect subsystems without explicitly listing them again in the System constructor
  • introduce @equations to define equations without leading , and inline eval based on structural parameters

I think those features could bring back most of the convenience of the macro without need for duplicated parsing structures. If the underlying constructor is never abstracted away, every new feature can be easily accessed without complicating the dsl.

I do think some additional macros could be pretty useful. However, it’s worth noting that

  • @named is exactly the @components macro you’re looking for
  • @equations is redundant. Julia’s array syntax does exactly what you want. For example:
julia> let
           @variables x(t) y(t) z(t)
           eqs = [
               x ~ y
               if rand(Bool)
                   y ~ z
               else
                   x ~ z
               end
               y ~ z
           ]
       end
3-element Vector{Equation}:
 x(t) ~ y(t)
 y(t) ~ z(t)
 y(t) ~ z(t)

I can definitely see the utility of @component automatically adding the @variables/@parameters to the function signature. However, I will need some time to think about potential edge cases here.

2 Likes

Ah great, yeah @named and eqs is solved then already. To be clear: for parameter and symbols I don’t necessarily look for the full complexity of the @mtkmodel macro (there is something like namepace__subparam to even set defaults for subsystems). I am mainly looking for toplevel parmeters and variables. My models commonly have ~30 parameters, putting all of them in the signature in addition to the @paramters blocks feels error prone (easy to forget some).

Additionally I think there’s one more aspect that the discussion would be incomplete without. We need to be able to draw a line with these kinds of convenience macros. All of @variables, @parameters, @named, and @component do exactly one thing without many frills. This makes them particularly easy to maintain, which is important considering we’re deprecating @mtkmodel for exactly this reason.

@variables was essentially untouched throughout MTKv9 until we added callable parameters. The next time I did anything to it is with the very recent release of Symbolics v7, and that just deleted most of the code since the parsing is now centralized in SymbolicUtils. To my knowledge, @named has been changed once since v9 released specifically to fix a parsing bug. I don’t ever remember touching @component.

I’m not opposed to adding convenience macros. But we need to make sure they’re built with the right purpose: for abstraction, to help avoid errors, and then to avoid boilerplate. I think the proposed change to @component comes under avoiding errors more than it does boilerplate and is a great idea. ModelingToolkit.jl should definitely have a friendly user experience, but in my opinion @mtkmodel has proven that it is not the place for a full-blown DSL which I’m concerned excessive convenience macros can turn into.

1 Like

I should probably take a closer look into Dyad some day (I have had a short exposure at an early stage); I assume this is considered a full blown DSL. Still, I do hope that ModelingToolkit.jl is not “down-graded”. The way I have used @mtkmodel, MTK has been relatively close to Modelica in structure – for simple use.

At some stage, I will also look more into connectors. Although I have been a (casual?) user of Modelica since 2003, I have never myself worked with connectors in Modelica. But it does make sense to make this distinction between components and connectors, with an emphasis on building re-useable code/libraries. And I fully understand the need for maintainability.

My use of Modelica has mainly been as a DAE solver for my modeling-courses. These are basic courses, where the main focus has been on understanding key principles, and not on building re-usable components. So I have needed a DAE solver, and have used MTK for that the last 3-4 years. I am hesitant to using a DSL with libraries in the courses I have had – my key focus has been to make the students understand how to make models, and not to use prebuilt code. But when a student grasps the basic idea and understand how things work, then it is ok to use a more fancy system with libraries. So I see the need for also understanding components and libraries in education, especially for MSc theses and PhD level studies.

If I in any way implied that ModelingToolkit.jl is being downgraded, please rest assured that this is not the case. We’re not interested in stripping out features, and the package will continue to do what it does. The problem is more of a cost-benefit analysis. ModelingToolkit takes a lot of effort to develop. While @mtkmodel is really useful, fixing issues and updating to new semantics takes an inordinate amount of effort and diverts too many resources from actually building a better, more useful tool. Debugging an issue in that macro is quite easily 2-4x more time-consuming than most other bugs I have encountered, and the tripping hazards make for a poorer user experience. For example, if you do enough with symbolic arrays you’ll run into weird bugs with their defaults in @mtkmodel. It’s really difficult to justify to new users, and not as robust as we’re trying to make MTK be. There’s a long list of features I have planned, ranging from symbolic structs to better tearing algorithms, which should provide a better and (more importantly) more robust experience.

2 Likes