Mass Spring Damper with BlockSystems.jl

How does one add a proportional constant k and a damping constant b to this IOBlock to setup a spring mass damper transfer function?

using BlockSystems
using ModelingToolkit

@parameters t M F(t)
@variables x(t) v(t)
D = Differential(t)

msd = IOBlock([D(v) ~ F/M, D(x) ~ v], # define the equation
                     [F], # inputs of the system
                     [x], # outputs of the system
                     name = :msd)

You’d have to modify those equations to include the stiffness and damping terms.

Alternatively, if you want to use a component-based modeling approach, you could use ModelingToolkit directly. An example building a MSD system can be found here

1 Like

This is what I came up with.

spacecraft = IOBlock([D(v) ~ -(KK/M)*x + -(B/M)*v + (F/M), D(x) ~ v], # define the equation
                     [F], # inputs of the system
                     [x], # outputs of the system
                     name = :spacecraft)

Now how do I set the K and B variables in the ODE Solver? They go in the p vector right? But what determines the ordering?

From the spacecraft example:

p = [0.5, 1.0] # K, m  # Here we need to add KK and B 
u0 = [0.0, 0.0] # altitude, v
tspan = (0.0, 30.0)
prob = ODEProblem(odefun, u0, tspan, p)
sol = solve(prob, Tsit5())

I’ve created BlockSystems.jl at a time where component based modeling was far more complicated in vanilla MTK than it is now. Since it is/was highly specialized for my specific usecase I would not necessarily recommend it for starting a new project now – i think it is worth using MTK directly. Eventually I want to revisit BlockSystems to make it a much thinner MTK wrapper than it is now, but that for sure will be quite breaking.

If you still want to use BlockSystems: in order to solve the system you always have to generate a callable Julia function based on the symbolic model. You can use generate_io_function for a low level interface or

odefun = ODEFunction(iob::IOBlock; f_states=Symbol[], f_params=Symbol[])

for a more high level interface. The ordering of states and parameters can be passed explicitly. Otherwise they’ll match the order of iob.iparams (the internal parameters) and states = vcat(iob.outputs, iob.istates) (stacked vector of output states and internal states).

However in order to create a ODEFunction/ODEProblem you need to close all the inputs first. There are plenty options to do that, either create other blocks and connect them or use something like

@variables t
@parameters omega
closed_spacecraft = set_input(spacecraft, F => sin(omega * t))

to direclty close the input with that specific function. Now closed_spacecraft.iparams should show [:omega, :KK, :B] in some order.

If you don’t want to specify the parameters at solve time you may also bake them into the generated code by something like

closed_spacecraft = replace_vars(closed_spacecraft, :omega=>10, :B=>4)

which would leave you with a single free parameter :KK and so on.

Thanks for the info. Im also looking at ControlSystems.jl and related libs.

Will have to try what you have suggested.