Real-time input and plotting with ModelingToolkit

I’d like to have an MTK model like D(x) = k*x, and plot the solution in real time (after 1 second, the plot shows the solution from t=0 to t=1, etc.)

I’d like k to be supplied by a real-time interactive slider (so that when the user moves it, the real-time plot reacts accordingly). What’s the easiest way to achieve something like that? Ideally in Pluto (perhaps this is asking too much :slightly_smiling_face:)

From Playing with live plots in Pluto - Specific Domains / Visualization - Julia Programming Language (julialang.org) I can make pluto-side animations, but each time-step has to be a single call (i.e. that wouldn’t work with a callback). So at each animation frame I’d have to setup the MTK problem and solve it over (current_t, current_t+1/30) (to target 30FPS). This might be doable, but I’d have to build the current total solution (t=0 to t=current_t) from the individual solutions, and set up each ODEProblem’s initial condition from the previous ODEProblem’s final values. This would definitely work, but it looks rather painful to setup.

Perhaps there is a solution using MTK callbacks? As in, I setup a periodic callback corresponding to my target FPS, somehow update k from the current slider value, somehow update the plot, then sleep() until the next frame? I’m a lot fuzzier on the details of that solution. It might involve… threads?

Somewhat related: SciML: optimizing a control signal - Specific Domains / Modelling & Simulations - Julia Programming Language (julialang.org)

Here’s an implementation of a discrete callback that makes the simulation progress in real time

you can probably adapt this to your use case. To achieve the slider controlling a parameter, you can probably go through a global variable, but you need to make sure that the simulation and the slider update happens on different threads. The solution I linked above uses systemsleep which does not yield, but has better accuracy than sleep. If your real-time requirements are not very tight, you can use sleep instead, which is yielding. Then it’s enough for the simulation and the slider to run on different tasks (which may be on the same thread).

1 Like

@ufechner7 is this possible with ControlPlots.jl?

Callbacks are one option. Another option is to run the simulation for 0…dt, then update the display, than from dt…2dt etc.

Example code:

function simulate(integrator, steps)
    for i in 1:steps
        KiteModels.next_step!(kps4, integrator; set_speed=0, dt)

        sys_state = SysState(kps4)
        q = QuatRotation(sys_state.orient)
        roll, pitch, yaw = rad2deg.(quat2euler(q))
        println("roll: ", roll, " pitch: ", pitch, " yaw: ", yaw)
        
        reltime = i*dt-dt
        if mod(i, 5) == 1
            plot2d(kps4.pos, reltime; zoom=ZOOM, xlim=(40,60), front=FRONT_VIEW, segments=set.segments)                       
        end
    end
end

In this example I run the simulation with steps of 20ms and update the GUI on every 5th step.

ControlPlots.jl does not (yet) offer sliders, for sliders you must use other options like Makie.jl or QML.jl or others.

The integrator interface from DifferentialEquations.jl can be used with MTK models.

1 Like

That looks perfect for the Pluto interaction code, I’ll give it a try!

One question: from reading the integrator interface documentation, it looks like mutating the state between each step! call is not a good idea. So I guess I should set up a periodic callback to read the slider’s current value and assign it to k. I’m assuming that this is OK? Are periodic callbacks ever called more than once for a given t? (in which case, they might read a different value, which sounds bad…?)

Well, normally a system has inputs that are different variables than the state variables. You can always modify the inputs. No need for callbacks.

1 Like

Worked great, thank you. For the Pluto interaction I had to use a function of t which accesses a global Ref, that is set by the Pluto slider (through another intermediary variable + cell). So quite roundabout, but nothing too difficult.

2 Likes

This was working fine for a while, but after reformulating my model I’m now hitting return codes of Unstable. To be clear, I’ve got a loop like

my_value = 10
read_my_value(t) = my_value
@register_symbolic read_my_value(t)
while true
    step!(integrator, dt, true)
    my_value += 1   # super-simplified; in reality it's a GUI slider
end

I read the PSA and this part:

Double check if you’re doing anything that violates assumptions of being an ODE. As an ODE, the right-hand side f function should always give you the same result: u' = f(u,p,t) needs to be uniquely defined. These issues are just fundamental to the mathematics: if you do these things in f , then f no longer defines an ODE so of course it cannot be solved!

makes me worry that maybe step! can go before the current time t and get upset that f(t-1) no longer returns the value it did before.

I’ve basically confirmed from experiment that

  • A) The solver does go back in the past, to values before integrator.t
  • B) Depending on the equations MTK simplifies to, it may not appreciate sudden jumps in inputs (yielding Unstable, or MaxIter)

My solution is to have a LinearInterpolation, and push! onto its t and u. That itself may be dangerous, but for now it satisfies the solver…

Yes, it cannot go before integrator.t.

Yes. That’s general for ODE solvers.

You might want to u_modified!(integrator. true) to let it know that you just placed a derivative discontinuity when using the integrator interface live like that.

2 Likes