MTK: updated documentation?

I am trying to use ModelingToolkit (MTK). In the first example in the documentation,

using ModelingToolkit, Plots, OrdinaryDiffEq
using ModelingToolkit: t_nounits as t, D_nounits as D
@mtkmodel FOL begin
    @parameters begin
        τ = 3.0 # parameters
    end
    @variables begin
        x(t) = 0.0 # dependent variables
    end
    @equations begin
        D(x) ~ (1 - x) / τ
    end
end

@mtkcompile fol = FOL()

This example doesn’t run with the latest version of MTK: I get an error message that @mtkmodel is not defined in Main.


OK – based on previous discussion, I know that MTK is in a transition to a new way to describe components – but my understanding was that the “old way” would still work, but with a warning. The new style works, though:

@component function FOL(;name)
    params = @parameters begin
        τ = 3.0 # parameters
    end
    vars = @variables begin
        x(t) = 0.0 # dependent variables
    end
    eqs = [
        D(x) ~ (1 - x) / τ
    ]
    System(eqs, t, vars, params; name)
end

@mtkcompile fol = FOL()

Question: Where is the “new style” documented?? Although I know the basics of the new style, it is not easy to guess how to make acausal models. E.g., @components doesn’t exist any more, the error message tells me.

1 Like

Is it a bug in the documentation deploy on github ? Because releasing a breaking release without documenting it properly (in the official documentation) is reckless for a package as important as MTK. That piles up on the reasons why I still don’t consider MTK as a mature package.

2 Likes

ModelingToolkit recently updated to a v11 release and deprecated the @mtkmodel macro, see ModelingToolkit.jl/NEWS.md at master · SciML/ModelingToolkit.jl for more information. It seems the MTK docs are not out yet for v11 (you can see the release version in the bottom dropdown menu).

2 Likes

I wouldn’t be so categorical: I think the developers have done excellent work in a relatively short time span. And I like the improvements I see; I hope these will solve some problems of the past (from my experience), like better handling of initialization with guesses, improved linearization; I love the moving of “analysis points” out of the standard library and to the general tool. In comparison, when I used OpenModelica the first time (ca. 2003-2004), it could only handle models with a couple of states.

Still, I look forward to seeing some updated documentation.

2 Likes

OK… Microsoft Copilot suggested the following modification of the Acausal example in the documentation… (the first couple of attempts failed, but by providing it with the error messages, this code works) :slight_smile:

using ModelingToolkit, Plots, OrdinaryDiffEq
using ModelingToolkit: t_nounits as t, D_nounits as D

@connector function Pin(; name)
    sts = @variables v(t) [guess = 1.0]  i(t) [guess = 1.0, connect = Flow]
    ODESystem(Equation[], t, sts, []; name = name)
end

@component function Ground(; name)
    @named g = Pin()
    eqs = [g.v ~ 0]
    compose(ODESystem(eqs, t, [], []; name = name), g)
end

@component function OnePort(; name)
    @named p = Pin()
    @named n = Pin()
    sts = @variables v(t) [guess = 1.0]  i(t) [guess = 1.0]
    eqs = [
        v ~ p.v - n.v
        0 ~ p.i + n.i
        i ~ p.i
    ]
    compose(ODESystem(eqs, t, sts, []; name = name), p, n)
end

@component function Resistor(; name, R = 1.0)
    @named oneport = OnePort()
    @unpack v, i = oneport
    ps = @parameters R = R
    eqs = [v ~ i * R]
    extend(ODESystem(eqs, t, [], ps; name = name), oneport)
end

@component function Capacitor(; name, C = 1.0)
    @named oneport = OnePort()
    @unpack v, i = oneport
    ps = @parameters C = C
    eqs = [D(v) ~ i / C]
    extend(ODESystem(eqs, t, [], ps; name = name), oneport)
end

@component function ConstantVoltage(; name, V = 1.0)
    @named oneport = OnePort()
    @unpack v = oneport
    ps = @parameters V = V
    eqs = [V ~ v]
    extend(ODESystem(eqs, t, [], ps; name = name), oneport)
end

# --- Build the RC circuit ---

R = 2.0
C = 1.0
V = 1.0

@named resistor  = Resistor(R = R)
@named capacitor = Capacitor(C = C)
@named source    = ConstantVoltage(V = V)
@named ground    = Ground()

rc_eqs = [
    connect(source.p, resistor.p)
    connect(resistor.n, capacitor.p)
    connect(capacitor.n, source.n)
    connect(capacitor.n, ground.g)
]

@named _rc_model = ODESystem(rc_eqs, t)
@named rc_model  = compose(_rc_model, [resistor, capacitor, source, ground])

sys = structural_simplify(rc_model)

u0 = [
    capacitor.v => 0.0
]

prob = ODEProblem(sys, u0, (0, 10.0))
sol  = solve(prob, Tsit5())
plot(sol)

The new docs just need some downstream stuff updated in order to build. Should happen rather soon.

2 Likes

I should say that I struggle somewhat with understanding when I can use @mtkcompile, when I should use structural_simplify, and – is there mtkcompile?? I had a “session” with MS Copilot on this, and didn’t get much wiser.

1 Like

There is no more structural_simplify, use mtkcompile. Or the macro @mtkcompile

from ModelingToolkit.jl/NEWS.md at b8940e544bbf04f518a8f5d5bc340fd90db68133 · SciML/ModelingToolkit.jl · GitHub

New mtkcompile and @mtkcompile

structural_simplify is now renamed to mtkcompile. @mtkbuild is renamed to
@mtkcompile. Their functionality remains the same. However, instead of a second
positional argument structural_simplify(sys, (inputs, outputs)) the inputs and outputs
should be specified via keyword arguments as mtkcompile(sys; inputs, outputs, disturbance_inputs).

To answer the original question: @mtkmodel is now in a separate package called SciCompDSL, which is inside of the MTK repo. using SciCompDSL will make your original example work with a deprecation warning. If @component works for you, using it is more forward-looking.

I guess MS Copilot was “under the influence”… it claimed first that there was no mtkcompile and that @mtkcompile had been removed. When I pushed back, it claimed that @mtkcompile only works for systems in state space form (DEs of index 0), and that mtkcompile and structural_simplify served different purposes.

Anyways, structural_simplify still works.

I am still wondering about when to use @mtkcompile and when to use mtkcompile. It must be possible to present that in a simple way(?).

Another new thing… When should we use System and when should we use ODESystem?

In recent MTK versions, you should always prefer System and mtkcompile. I think that ODESystem and structural_simplify are just there for backward compatibility.

1 Like

So… to answer my own question – is the following correct?

When to use @mtkcompile:

When the complete model is “baked” into a component:

@component function FOL(;name)
    params = @parameters begin
        τ = 3.0 # parameters
    end
    vars = @variables begin
        x(t) = 0.0 # dependent variables
    end
    eqs = [
        D(x) ~ (1 - x) / τ
    ]
    System(eqs, t, vars, params; name)
end

@mtkcompile fol = FOL()
prob = ODEProblem(fol, [], (0.0, 10.0))
sol = solve(prob)

When to use mtkcompile:

When the complete model is composed in Main/outside of a component?

params = @parameters begin
        τ = 3.0 # parameters
end

vars = @variables begin
        x(t) = 0.0 # dependent variables
end

eqs = [
        D(x) ~ (1 - x) / τ
    ]

@named _fol2 = System(eqs, t, vars, params) 

fol2 = mtkcompile(_fol2)
prob = ODEProblem(fol2, [], (0.0, 10.0))
sol = solve(prob)

Is this correct?

Yes, the macro just does named and mtkcompile as one step.

1 Like

From the docs: Model building reference · ModelingToolkit.jl

@mtkcompile sys = Constructor(args...; kwargs....)

Is shorthand for

@named sys = Constructor(args...; kwargs...)
sys = mtkcompile(sys)
2 Likes

I interpret these two answers as in order to use @mtkcompile, there needs to be a constructor for the complete model.

True. And, gotta say, OrdinaryDiffEq.jl package, so awesome :racing_car::racing_car::racing_car:

1 Like

In my case, coming from a Simulink and Modelica background, I found the @mtkmodel macro especially convenient and intuitive, and for that reason I have based most of my models on it. That said, I fully understand that if this macro introduces serious difficulties in terms of maintenance, bug fixing, and debugging, then it makes sense to move away from it or deprecate it.

I do hope, however, that the new documentation clearly explains how to proceed without this macro (ideally by reworking the examples that currently rely on it) so that users like myself, who had grown accustomed to using @mtkmodel, can adapt to the new recommended workflow more easily.

Further problems… I figured out how to make the acausal example in the old documentation work under the new style.

So I tried to use this working style and make my own “library” of components – I want to understand how things work. When doing mtkcompile, there is no error message, and the latexify of the model works and is correct. Then I try to create an ODEProblem, and get an error message:

A completed system is required. Call `complete` or `mtkcompile` on the system before creating a `ODEProblem`.

But I have done mtkcompile!! And if I do complete of the named system before I do mtkcompile, I get another error message:

Encountered operator `Differential(t, 1)` which has different independent variable than the one used in the system `nothing`.

Context:
c₊C*Differential(t, 1)(c₊u(t))

Any ideas?

Note: when I created my own “components”, I did not use the OnePort “container” with extension. Below is my entire code for this case. Note: I used u for voltage instead of v…; u is more common in Europe, and to me v is velocity… Also, I use slightly different names for various things, e.g., a and b for the pins on the one port.

using ModelingToolkit
using ModelingToolkit: t_nounits as t, D_nounits as D
using OrdinaryDiffEq

@connector function ElectricPort(; name)
    vars = @variables begin 
        u(t), [guess = 0.0]
        i(t), [guess = 0.0, connect = Flow]
    end
    System(Equation[], t, vars, []; name = name)
end

@component function Ground(; name)
    @named a = ElectricPort()
    eqs = [a.u ~ 0]
    compose(System(eqs, t, [], []; name = name), a)
end

@component function Resistor(; name, R = 1.0)
    @named a = ElectricPort()
    @named b = ElectricPort()
    pars = @parameters begin
        R = R, [description = "Resistance, Ohm"]
    end 
    vars = @variables begin
        u(t), [guess = 0.0]
        i(t), [guess = 0.0]
    end
    eqs = [
        u ~ a.u - b.u
        0 ~ a.i + b.i
        i ~ a.i
        u ~ R * i
    ]
    compose(System(eqs, t, vars, pars; name = name), a, b)
end

@component function Capacitor(; name, C = 1.0)
    @named a = ElectricPort()
    @named b = ElectricPort()
    pars = @parameters begin
        C = C, [description = "Capacitance, Farad"]
    end 
    vars = @variables begin
        u(t), [guess = 0.0]
        i(t), [guess = 0.0]
    end
    eqs = [
        u ~ a.u - b.u
        0 ~ a.i + b.i
        i ~ a.i
        i ~ C * D(u)
    ]
    compose(System(eqs, t, vars, pars; name = name), a, b)
end

@component function Inductor(; name, L = 1.0)
    @named a = ElectricPort()
    @named b = ElectricPort()
    pars = @parameters begin
        L = L, [description = "Inductance, Henry"]
    end 
    vars = @variables begin
        u(t), [guess = 0.0]
        i(t), [guess = 0.0]
    end
    eqs = [
        u ~ a.u - b.u
        0 ~ a.i + b.i
        i ~ a.i
        u ~ L * D(i)
    ]
    compose(System(eqs, t, vars, pars; name = name), a, b)
end

@component function VoltageSourceAC(; name, U = 1.0, f = 50.0)
    @named a = ElectricPort()
    @named b = ElectricPort()
    pars = @parameters begin
        U = U, [description = "Amplitude, Volt"]
        f = f, [description = "Frequency, Hz"]
    end
    vars = @variables begin
        u(t), [guess = 0.0]
    end
    eqs = [
        u ~ U * sin(2 * pi * f * t)
        u ~ a.u - b.u
    ]
    compose(System(eqs, t, vars, pars; name = name), a, b)
end

# Parameters
# 
R = 20 #,     [description = "RCL resistance, ohm"]
R_ell = 40 #, [description = "RCL load resistance, ohm"]
C = 10e-6 #,  [description = "RCL capacitance, F"]
L = 10e-3 #,  [description = "RCL inductance, H"]
U = 10 #,     [description = "Voltage source amplitude, V"] 
f = 500 #,    [description = "Voltage source frequency, Hz"]  
#
#
@named r = Resistor(; R=R)
@named c = Capacitor(; C=C)
@named l = Inductor(; L=L)
@named r_ell = Resistor(; R=R_ell)
@named source = VoltageSourceAC(; U=U, f=f)
@named ground = Ground()

rcl2_eqs = [
    connect(source.a, r.a)
    connect(r.b, c.a)
    connect(r.b, l.a)
    connect(l.b, r_ell.a)
    connect(c.b, ground.a)
    connect(r_ell.b, ground.a)
    connect(ground.a, source.b)
]

@named _rclsys2 = System(rcl2_eqs, t)
@named rclsys2  = compose(_rclsys2, [r, c, l, r_ell, source, ground])
#rclsys2 = complete(rclsys2)

sys = mtkcompile(rclsys2)

tspan = (0,5e-3)

prob = ODEProblem(rclsys2, [], tspan)

This gives the indicated error messages.

1 Like

Hi, first of all thank you very much for sharing the whole process and building everything from scratch, that is always very helpful for those of us who are just starting and trying to understand how things work.

Secondly, in your example, shouldn’t you write

sys = mtkcompile(rclsys2)

tspan = (0, 5e-3)

prob = ODEProblem(sys, [], tspan)

instead of

sys = mtkcompile(rclsys2)

tspan = (0, 5e-3)

prob = ODEProblem(rclsys2, [], tspan)

?

2 Likes