Anshul Singhvi / May 15 2019
Remix of Julia by Nextjournal

Interactive Differential Equations with Makie.jl

First, the setup (installing the requisite packages and all):

# setup
using Pkg
pkg"add DifferentialEquations ParameterizedFunctions Observables Makie"

1. Defining the ODE

This is a bog-standard use of DifferentialEquations.jl - you could substitute your own differential equation here with ease.

using DifferentialEquations,
	    ParameterizedFunctions

lorenz = @ode_def Lorenz begin             # define the system
 dx = σ * (y - x)
 dy = x * (ρ - z) - y
 dz = x * y - β*z
    end σ ρ β

u0 = [1.0,0.0,0.0]                         # initial conditions
tspan0 = (0.0,100.0)                       # initial timespan
p0 = [10.0,28.0,8/3]                       # initial parameters
prob = ODEProblem(lorenz, u0, tspan0, p0)  # define the problem
ODEProblem with uType Array{Float64,1} and tType Float64. In-place: true timespan: (0.0, 100.0) u0: [1.0, 0.0, 0.0]

2. Plotting and Interactivity

2.1. Setting up sliders

One of the easiest ways to get interactivity in Makie.jl is the slider. In this case, I've used textslider, because it automatically vboxes a title with the slider, and it returns both the slider plot object and the Observable corresponding to its value.

using Makie
using AbstractPlotting: textslider

OME = 8     # the order of magnitude to range between

,  = textslider(exp10.(-OME:0.001:OME), ", start = p0[1]);

,  = textslider(exp10.(-OME:0.001:OME), ", start = p0[2]);

,  = textslider(exp10.(-OME:0.001:OME), ", start = p0[3]);

st, ot = textslider(exp10.(-OME:0.001:OME), "tₘₐₓ", start = tspan0[end]);

sr, or = textslider(100:10000, "resolution", start = 2000);

2.2. Setting up Observables

Now comes the real magic! With Observables, it's easy to map the user input to some plottable data.

trange = lift(ot, or) do tmax, resolution

            LinRange(0.0, tmax, resolution)

        end

data = lift(, , , trange) do σ, ρ, β, ts

            Point3f0.(
                solve(
                    remake(
                        prob;
                        p = [σ, ρ, β],
                        tspan = (ts[1], ts[end])
                        )
                    )(ts).u
                )# change to fit the dimensionality - maybe even return 2 arrays,                    #  or `Point2`s...
    end
Observable{Array{Point{3,Float32},1}} with 0 listeners. Value: Point{3,Float32}[[1.0, 0.0, 0.0], [0.870411, 1.2043, 0.0255712], [1.27426, 2.5588, 0.114683], [2.2047, 4.73013, 0.392357], [3.93103, 8.47733, 1.27947], [6.95172, 14.6847, 4.0488], [11.7644, 23.1173, 11.9061], [17.5358, 27.6391, 28.6857], [19.7077, 16.5379, 46.0587], [14.4848, -1.19503, 46.4486] … [-3.84543, -1.40298, 25.6916], [-2.99238, -1.96104, 22.742], [-2.75226, -2.77679, 20.2129], [-2.98538, -3.87808, 18.1292], [-3.64744, -5.41572, 16.58], [-4.78281, -7.59138, 15.7858], [-6.49026, -10.5425, 16.2019], [-8.82477, -14.0388, 18.6174], [-11.5318, -16.8149, 23.779], [-13.6645, -16.3805, 30.9178]]

2.3. Plotting the data

We now have plottable data! It's a simple matter to plot the data and the slider together (we'll use a line plot for the data):

three = lines(data)

vbox(hbox(, , , st, sr), three)