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.?
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.
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.
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.
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])
...
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
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.
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.
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.
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.