Model-based design in Julia: Composing components with ModelingToolkit.jl

Published:

The ModelingToolkit.jl is a way to easily build models of physical systems and then simulate those models. It comes with a standard library : ModelingToolkit.jl which contains various components to model physical systems such as electrical circuits and mechanics.

In this blog article I will explore how to build up new ModelingToolkit components from existing components.

I will take the DC motor example from the sciml.ai website and turn the DC motor into a component that can be used in simulations.

First we’ll define the DC motor component:

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

    connections = [
    connect(support, 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)
    ]

    systems = [
        p,
        n,
        flange,
        support,
        R1,
        L1,
        emf,
        inertia,
        friction,
    ]

    ODESystem(connections, t, [], [], systems = systems, name = name)

end

We can then use this component in a simulation like this:



using ModelingToolkit
using ModelingToolkitStandardLibrary.Electrical
using ModelingToolkitStandardLibrary.Mechanical.Rotational
using ModelingToolkitStandardLibrary.ElectroMechanical.Rotational
using ModelingToolkitStandardLibrary.Blocks
using OrdinaryDiffEq
using Plots

@parameters t

R = 0.5 # [Ohm] armature resistance
L = 4.5e-3 # [H] armature inductance
k = 0.5 # [N.m/A] motor constant
J = 0.02 # [kg.m²] inertia
f = 0.01 # [N.m.s/rad] friction factor
tau_L_step = -0.3 # [N.m] amplitude of the load torque step

@named ground = Ground()
@named source = Voltage()
@named ref = Blocks.Step(height=1, start_time=0)
@named pi_controller = Blocks.LimPI(k=1.1, T=0.035, u_max=10, Ta=0.035)
@named feedback = Blocks.Feedback()
@named fixed = Fixed()
@named load = Torque(use_support=false)
@named load_step = Blocks.Step(height=tau_L_step, start_time=3)
@named speed_sensor = SpeedSensor()
@named motor = DcMotor()

connections = [connect(fixed.flange, motor.support)
   connect(motor.flange, load.flange)
   connect(motor.flange, speed_sensor.flange)
   connect(load_step.output, load.tau)
   connect(ref.output, feedback.input1)
   connect(speed_sensor.w, :y, feedback.input2)
   connect(feedback.output, pi_controller.err_input)
   connect(pi_controller.ctr_output, :u, source.V)
   connect(source.p, motor.p)
   connect(motor.n, source.n, ground.g)]

@named model = ODESystem(connections, t,
   systems=[
      motor,
      ground,
      ref,
      pi_controller,
      feedback,
      source,
      fixed,
      load,
      load_step,
      speed_sensor,
   ])

sys = structural_simplify(model)
prob = ODEProblem(sys, [], (0, 6.0))
sol = solve(prob, Rodas4())

plot(sol)