Getting Started with JuliaSimCompiler

To use JuliaSimCompiler as an MTK backend, one just needs to convert MTK systems into an IRSystem. There really isn't much more to it!

As an example, let's take the the following MTK system of a motor. It's similar to the DC Motor example from the ModelingToolkitStandardLibrary, though without the controller. The code is as follows:

using ModelingToolkit, DifferentialEquations, Plots
using ModelingToolkitStandardLibrary.Electrical
using ModelingToolkitStandardLibrary.Mechanical.Rotational
using ModelingToolkitStandardLibrary.Blocks

@component function Motor(; name, R = 0.5, L = 4.5e-3, k = 0.5, J = 0.02, f = 0.01)
    @named p = Pin()
    @named n = Pin()
    @named R1 = Resistor(R = R)
    @named L1 = Inductor(L = L)
    @named emf = EMF(k = k)
    @named fixed = Fixed()
    @named inertia = Inertia(J = J, phi = 0.0, w = 0.0)
    @named friction = Damper(d = f)
    @named flange = Flange()

    connections = [connect(fixed.flange, emf.support, friction.flange_b)
                   connect(emf.flange, friction.flange_a, inertia.flange_a)
                   connect(inertia.flange_b, flange)
                   connect(p, R1.p)
                   connect(R1.n, L1.p)
                   connect(L1.n, emf.p)
                   connect(emf.n, n)]

    model = ODESystem(connections, t,
        systems = [
            flange,
            p, n,
            R1,
            L1,
            emf,
            fixed,
            inertia,
            friction
        ], name = name)
end

t = Blocks.t

@named motor = Motor()
@named ground = Ground()
@named load = Torque(use_support = false)
@named load_value = Blocks.Sine(frequency = 2)
@named source = Voltage()
@named voltage_constant = Blocks.Constant(k = 10)
@named speed_sensor = SpeedSensor()
eqs = [connect(voltage_constant.output, source.V)
       connect(load_value.output, load.tau)
       connect(motor.p, source.p)
       connect(motor.n, source.n, ground.g)
       connect(motor.flange, load.flange)
       connect(motor.flange, speed_sensor.flange)]
@named complete_motor = ODESystem(eqs, t,
    systems = [
        motor,
        ground,
        load,
        load_value,
        source,
        voltage_constant,
        speed_sensor
    ]);
sys = structural_simplify(complete_motor)
prob = ODEProblem(sys, [], (0, 10.0))
sol = solve(prob)
plot(sol, idxs = [speed_sensor.w.u], title = "Motor speed", xlab = "time (s)",
    ylab = "speed (rad/s)", lab = "MTK")
Example block output

In order to transform this into a system for JuliaSimCompiler, we simply transform the ODESystem into an IRSystem via the IRSystem constructor. Let's convert to JuliaSimCompiler and perform structrual simplification on the IR form:

using JuliaSimCompiler
complete_motor_ir = IRSystem(complete_motor)
sys_ir = structural_simplify(complete_motor_ir)
States (3):
 motor₊inertia₊phi
 motor₊inertia₊w
 motor₊L1₊i
Variables (79):
  1 => motor₊R1₊p₊v
  2 => motor₊R1₊n₊v
  3 => motor₊R1₊v
  4 => motor₊R1₊n₊i
  5 => motor₊R1₊p₊i
  6 => motor₊R1₊i
  7 => motor₊L1₊p₊v
  8 => motor₊L1₊n₊v
  9 => motor₊L1₊v
 10 => motor₊L1₊p₊i
    ⋮
 71 => motor₊fixed₊flange₊tau
 72 => motor₊emf₊support₊tau
 73 => Dt(motor₊fixed₊flange₊phi, 1, true)
 74 => Dt(motor₊friction₊flange_b₊phi, 1, true)
 75 => Dt(motor₊emf₊phi, 2, true)
 76 => Dt(motor₊inertia₊phi, 2, true)
 77 => Dt(motor₊friction₊phi_rel, 2, true)
 78 => Dt(motor₊fixed₊flange₊phi, 2, true)
 79 => Dt(motor₊friction₊flange_b₊phi, 2, true)
Matched SystemStructure with 76 equations and 79 variables
 #   ∂ₜ         eq                   #   ∂ₜ       v
 1              [3, (7), 58]      |  1            [(57)]
 2              [(4), 6, 5]       |  2            [(59)]
 3              [(5), 10, 6]      |  3            [1, (4), 5]
 4              [(3), 6]          |  4            [(2)]
 5              [3, (9), 16, 58]  |  5            [(3), 2]
 6              [(11), 19, 10]    |  6            [2, 4, 42, (60), 45, 58, 3]
 7              [12, (19)]        |  7            [(1), 59]
 8              [9, (13)]         |  8            [(61)]
 9              [(14), 16]        |  9            [(5), 8]
 ⋮                                   ⋮          
 71      23↓    [(42), 77]        |  71           [(51)]
 72      66↓    [75, (77)]        |  72           [(65), 51]
 73      67↓    [(75), 76, 79]    |  73           [(68), 69]
 74      68↓    [(78)]            |  74           [67, (69)]
 75      69↓    [78, (79)]        |  75           [72, (73)]
 76      55↓    [31, (60)]        |  76           [(70), 73]
                                 |  77           [71, (72)]
                                 |  78           [(74), 75]
                                 |  79           [73, (75)]

Legend: Solvable | (Solvable + Matched) | Unsolvable | (Unsolvable + Matched) |  SelectedState

While such a system is internally different from the ModelingToolkit ODESystem, its interfacces are the same. To show this, let's build the ODE problem, solve the model, and plot the system.

prob_ir = ODEProblem(sys_ir, [], (0, 10.0))
sol_ir = solve(prob_ir)
plot!(sol_ir, idxs = [speed_sensor.w.u], lab = "JuliaSimCompiler")
Example block output

Notice that the code is exactly the same as if we had an ODESystem. It's interopable and simply uses different internal representations to improve performance.