3d animation plot of 2 ode solutions

Hi, I’m very new to Julia and the plots package. I’d like to plot 2 ode solutions within the same gif. For example, say I’m solving the lorenz function and want to plot two solutions with different parameters in the same animation.

#lorenz solver 
function simulate(params)

    function lorenz(du, u, p, t)
        du[1] = p[1]*(u[2]-u[1])
        du[2] = u[1]*(p[2]-u[3]) - u[2]
        du[3] = u[1]*u[2] - p[3]*u[3]  

    u0 = [1.; 5.; 10.]
    tspan = (0, 100)
    p = params
    prob = ODEProblem(lorenz, u0, tspan, p)
    sol = solve(prob)
    return sol

#two different solutions
sol1 = simulate([10; 28; 8/3])
sol2 = simulate([5;32;10])

n = length(sol1.t)
plt = plot3d(1, xaxis = ("x" ,(-30, 30)), yaxis = ("y", (-30,30)), zaxis=("z", (0, 60)))
anim = @animate for i in 1:n
    push!(plt, sol1[1,i], sol1[2,i], sol1[3,i])

gif(anim, "myGif.gif")

I can’t seem to figure out an elegant way to allow both solutions to be in the same gif. Please let me know of any ideas! Thank you in advance

This is what I typically do.

nframes = 300
anim = @animate for t in LinRange(first(sol1.t), last(sol1.t), nframes)
    plot(sol1, vars = (1,2,3), tspan = (0.0, t), lab = "Solution 1")
    plot!(sol2, vars = (1,2,3), tspan = (0.0, t), lab = "Solution 2")
    #current time marker
    scatter!(sol1, vars = (1,2,3), tspan = (t, t), lab = nothing, color = 1)
    scatter!(sol2, vars = (1,2,3), tspan = (t, t), lab = nothing, color = 2)
    # axis setting... need to be last b/c DiffEq recipes will overwrite.
    plot!(xaxis = ("x" ,(-30, 30)), yaxis = ("y", (-30,30)), zaxis=("z", (0, 60)),
        title = "t = $(round(t, digits = 2))")

gif(anim, "myGif.gif"; fps = 30)

A couple things of note

  • Inside each @animate loop you need to create a new figure, not push to an existing one. Each loop will be a frame.
  • I am using a plot recipe for DiffEq. see Plot Functions · DifferentialEquations.jl
    • vars = (1,2,3) indicates to make a 3D plot of the states, 1,2,and 3. You use “0” for time. So, if you just wanted a 2D plot of x vs t you use vars = (0, 1)
    • tspan = (0,t) will interpolate the solution only in that time horizon. Because t is our loop variable it keeps growing
    • I also show using this for adding a scatter pt for the current time for the frame. Note how I used tspan = (t,t) so only plotting for that time.
    • Plot color can be specified various ways, by giving a number as color = 1, you specify the first in the cycle of colors. So, here I ensure the scatter colors match the other plots
    • Note that the DiffEq plot recipes will overwrite things like xaxis, so I specify them last.
  • Plotting functions appended with ! will add a new series to the previous plot.
  • The fps = 30 kwarg in gif specifies the frames / s

A big advantage of this approach is the each frame will be linearly spaced in time. Depending on the integrator you use, variable time steps may be used. So, by indexing directly in the solution like you were, the animation may not look smooth in time depending on the system and integrator.


Thank you so much for your reply! This is incredibly helpful, much appreciated.

My pleasure. Welcome to the Julia community.

As a bonus answer, here is how to do it with subplots

anim = @animate for t in LinRange(first(sol1.t), last(sol1.t), nframes)
    plt1 = plot(sol1, vars = (1,2,3), tspan = (0.0, t), title = "Solution 1", lab = false)
    scatter!(sol1, vars = (1,2,3), tspan = (t, t), lab = nothing)

    plt2 = plot(sol2, vars = (1,2,3), tspan = (0.0, t), title = "Solution 2", lab = false)
    scatter!(sol2, vars = (1,2,3), tspan = (t, t), lab = nothing)
    # combine into a single plot of layout 1 row 2 colums
    plot(plt1, plt2, layout = (1,2),
            xaxis = ("x" ,(-30, 30)), yaxis = ("y", (-30,30)), zaxis=("z", (0, 60)),
            plot_title = "t = $(round(t, digits = 2))")

The key difference here is I create two figures with plot() vs a single plot() and plot!() is the previous example. I then combine them into a new plot with plot(plt1, plt2, ...) The layout kwarg specifies the number of rows/colums. Any setting like xaxis passed in this call will get assigned to all of the subplots. Note the usage of plot_title vs title. If I used title, it would have those titles for each subplot vs, as a “supertitle” for the entire figure.

1 Like

Great! Thank you so much for the welcome and the very thorough explanation