Visualizations and Animations for Billiards
All plotting and animating for DynamicalBilliards.jl lies within a few well-defined functions that use the Makie ecosystem.
- For static plotting, you can use the function
bdplotandbdplot_boundarymap. - For interacting/animating, you can use the function
bdplot_interactive. This function also allows you to create custom animations, see Custom Billiards Animations. - For producing videos of time evolution of particles in a billiard, use
bdplot_video.
Plotting
DynamicalBilliards.bdplot — Functionbdplot(x; kwargs...) → fig, ax
bdplot!(ax::Axis, x; kwargs...)Plot an object x from DynamicalBilliards into a given axis (or a new figure). x can be an obstacle, a particle, a vector of particles, or a billiard.
bdplot!(ax,::Axis, o::Obstacle; kwargs...)Keywords are propagated to lines! or poly!. Functions obfill, obcolor, obls, oblw (not exported) decide global defaults for linecolor, fillcolor, linestyle, linewidth, when plotting obstacles.
bdplot!(ax,::Axis, bd::Billiard; clean = true, kwargs...)If clean = true, all axis elements are removed and an equal aspect ratio is establised. Other keywords are propagated to the obstacle plots.
bdplot!(ax,::Axis, bd::Billiard, xmin, xmax, ymin, ymax; kwargs...)This call signature plots periodic billiards: it plots bd along its periodic vectors so that it fills the total amount of space specified by xmin, xmax, ymin, ymax.
bdplot!(ax,::Axis, ps::Vector{<:AbstractParticle}; kwargs...)Plot particles as a scatter plot (positions) and a quiver plot (velocities). Keywords particle_size = 5, velocity_size = 0.05 set the size of plotted particles. Keyword colors = JULIADYNAMICS_CMAP decides the color of the particles, and can be either a colormap or a vector of colors with equal length to ps. The rest of the keywords are propagated to the scatter plot of the particles.
DynamicalBilliards.bdplot_boundarymap — Functionbdplot_boundarymap(bmap, intervals; figkwargs = NamedTuple(), kwargs...)Plot the output of DynamicalBilliards.boundarymap into an axis that correctly displays information about obstacle arclengths. Return figure, axis.
Also works for the parallelized version of boundary map.
Keyword Arguments
figkwargs = NamedTuple()keywords propagated toFigure.color: The color to use for the plotted points. Can be either a single color or a vector of colors of lengthlength(bmap), in order to give each initial condition a different color (for parallelized version).- All other keywords propagated to
scatter!.
Plotting an obstacle with keywords
using DynamicalBilliards, CairoMakie
bd = billiard_sinai()
fig, ax = bdplot(bd[2])
bdplot!(ax, bd[4]; color = "blue", linestyle = :dot, linewidth = 5.0)
bdplot!(ax, bd[1]; color = "yellow", strokecolor = "black")
fig
Plotting a billiard
using DynamicalBilliards, CairoMakie
bd = billiard_logo()[1]
fig, ax = bdplot(bd)
fig
Plotting some particle trajectories
using DynamicalBilliards, CairoMakie
timeseries! = DynamicalBilliards.timeseries!
bd = billiard_hexagonal_sinai()
p1 = randominside(bd)
p2 = randominside(bd, 1.0)
colors = [:red, :black]
markers = [:circle, :rect]
fig, ax = bdplot(bd)
for (p, c) in zip([p1, p2], colors)
x, y = timeseries!(p, bd, 20)
lines!(ax, x, y; color = c)
end
bdplot!(ax, [p1, p2]; colors, particle_size = 10, marker = markers)
fig
Periodic billiard plot
Rectangle periodicity:
using DynamicalBilliards, CairoMakie
r = 0.25
bd = billiard_rectangle(2, 1; setting = "periodic")
d = Disk([0.5, 0.5], r)
d2 = Ellipse([1.5, 0.5], 1.5r, 2r/3)
bd = Billiard(bd.obstacles..., d, d2)
p = Particle(1.0, 0.5, 0.1)
xt, yt, vxt, vyt, t = DynamicalBilliards.timeseries!(p, bd, 10)
fig, ax = bdplot(bd, extrema(xt)..., extrema(yt)...)
lines!(ax, xt, yt)
bdplot!(ax, p; velocity_size = 0.1)
fig
Hexagonal periodicity:
using DynamicalBilliards, CairoMakie
bd = billiard_hexagonal_sinai(0.3, 1.50; setting = "periodic")
d = Disk([0.7, 0], 0.2)
d2 = Antidot([0.7/2, 0.65], 0.35)
bd = Billiard(bd..., d, d2)
p = MagneticParticle(-0.5, 0.5, π/5, 1.0)
xt, yt = DynamicalBilliards.timeseries(p, bd, 10)
fig, ax = bdplot(bd, extrema(xt)..., extrema(yt)...)
lines!(ax, xt, yt)
bdplot!(ax, p; velocity_size = 0.1)
fig
Boundary map plot
using DynamicalBilliards, CairoMakie
bd = billiard_mushroom()
n = 100 # how many particles to create
t = 200 # how long to evolve each one
bmap, arcs = parallelize(boundarymap, bd, t, n)
randomcolor(args...) = RGBAf(0.9 .* (rand(), rand(), rand())..., 0.75)
colors = [randomcolor() for i in 1:n] # random colors
fig, ax = bdplot_boundarymap(bmap, arcs, color = colors)
fig
Interactive GUI
DynamicalBilliards.bdplot_interactive — Functionbdplot_interactive(bd::Billiard, ps::Vector{<:AbstractParticle}; kwargs...)Create a new figure with bd plotted, and in it initialize various data for animating the evolution of ps in bd. Return fig, phs, chs, where fig is a figure instance and phs, chs can be used for making custom animations, see below.
Keywords (interactivity-related)
playback_controls = true: If true, add controls that allow live-interaction with the figure, such as pause/play, reset, and creating new particles by clicking and dragging on the billiard plot.
Keywords (visualization-related)
dt = 0.001: The animation always occurs in steps of timedt. A slider can decide how many stepsdtto evolve before updating the plots.plot_bmap = false: If true, add a second plot with the boundary map.colors = JULIADYNAMICS_CMAP: If a symbol (colormap name) each particle gets a color from the map. If Vector of lengthN, each particle gets a color form the vector. If Vector with length <N, linear interpolation across contained colors is done.tail_length = 1000: The lasttail_lengthpositions of each particle are visualized.tail_width = 1: Width of the dtail plot.fade = true: Tail color fades away.plot_particles = true: Besides their tails, also plot the particles as a scatter and quiver plot.particle_size = 5: Marker size for particle scatter plot.velocity_size = 0.05: Multiplication of particle velocity before plotted as quiver.bmap_size = 4: Marker size of boundary map scatter plot.figure = NamedTuple(): Keywords propagated toFigurecreation.kwargs...: Remaining keywords are propagated to the billiard plotting.
Custom Animations
Two helper structures are defined for each particle:
ParticleHelper: Contains quantities that are updated eachdtstep: the particle, time elapsed since last collision, total time ellapsed, tail (positions in the lasttail_lengthdt-sized steps).CollisionHelper: Contains quantities that are only updated at collisions: index of obstacle to be collided with, time to next collision, total collisions so far, boundary map point at upcoming collision.
These two helpers are necessary to transform the simulation into real-time stepping (1 step = dt time), instead of the traditional DynamicalBilliards.jl setup of discrete time stepping (1 step = 1 collision).
The returned phs, chs are two observables, one having vector of ParticleHelpers, the other having vector of CollisionHelpers. Every plotted element is lifted from these observables.
An exported high-level function bdplot_animstep!(phs, chs, bd, dt; update, intervals) progresses the simulation for one dt step. Users should be using bdplot_animstep! for custom-made animations, examples are shown in the documentation online. The only thing the update keyword does is notify!(phs). You can use false for it if you want to step for several dt steps before updating plot elements. Notice that chs is always notified when collisions occur irrespectively of update. They keyword intervals is nothing by default, but if it is arcintervals(bd) instead, then the boundary map field of chs is also updated at collisions.
For example, the animation above was done with:
using DynamicalBilliards, GLMakie
l, w, r = 0.5, 0.75, 1.0
bd = billiard_mushroom(l, w, r)
N = 20
ps = vcat(
[MushroomTools.randomchaotic(l, w, r) for i in 1:N],
[MushroomTools.randomregular(l, w, r) for i in 1:N],
)
colors = [i ≤ N ? RGBf(0.1, 0.4 + 0.3rand(), 0) : RGBf(0.4, 0, 0.6 + 0.4rand()) for i in 1:2N]
fig, phs, chs = bdplot_interactive(bd, ps;
colors, plot_bmap = true, bmap_size = 8, tail_length = 2000,
);Custom Billiards Animations
To do custom animations you need to have a good idea of how Makie's animation system works. Have a look at this tutorial if you are not familiar yet.
Following the docstring of bdplot_interactive let's add a couple of new plots that animate some properties of the particles. We start with creating the billiard plot and obtaining the observables:
using DynamicalBilliards, CairoMakie
bd = billiard_stadium(1, 1)
N = 100
ps = particlebeam(1.0, 0.6, 0, N, 0.001)
fig, phs, chs = bdplot_interactive(bd, ps; playback_controls=false)(Scene (800px, 600px):
0 Plots
1 Child Scene:
└ Scene (800px, 600px), Observable(DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}[DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.5995]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.5995], [1.0, 0.5995], [1.0, 0.5995], [1.0, 0.5995], [1.0, 0.5995], [1.0, 0.5995], [1.0, 0.5995], [1.0, 0.5995], [1.0, 0.5995], [1.0, 0.5995] … [1.0, 0.5995], [1.0, 0.5995], [1.0, 0.5995], [1.0, 0.5995], [1.0, 0.5995], [1.0, 0.5995], [1.0, 0.5995], [1.0, 0.5995], [1.0, 0.5995], [1.0, 0.5995]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.5995101010101009]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.5995101], [1.0, 0.5995101], [1.0, 0.5995101], [1.0, 0.5995101], [1.0, 0.5995101], [1.0, 0.5995101], [1.0, 0.5995101], [1.0, 0.5995101], [1.0, 0.5995101], [1.0, 0.5995101] … [1.0, 0.5995101], [1.0, 0.5995101], [1.0, 0.5995101], [1.0, 0.5995101], [1.0, 0.5995101], [1.0, 0.5995101], [1.0, 0.5995101], [1.0, 0.5995101], [1.0, 0.5995101], [1.0, 0.5995101]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.599520202020202]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.5995202], [1.0, 0.5995202], [1.0, 0.5995202], [1.0, 0.5995202], [1.0, 0.5995202], [1.0, 0.5995202], [1.0, 0.5995202], [1.0, 0.5995202], [1.0, 0.5995202], [1.0, 0.5995202] … [1.0, 0.5995202], [1.0, 0.5995202], [1.0, 0.5995202], [1.0, 0.5995202], [1.0, 0.5995202], [1.0, 0.5995202], [1.0, 0.5995202], [1.0, 0.5995202], [1.0, 0.5995202], [1.0, 0.5995202]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.599530303030303]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.5995303], [1.0, 0.5995303], [1.0, 0.5995303], [1.0, 0.5995303], [1.0, 0.5995303], [1.0, 0.5995303], [1.0, 0.5995303], [1.0, 0.5995303], [1.0, 0.5995303], [1.0, 0.5995303] … [1.0, 0.5995303], [1.0, 0.5995303], [1.0, 0.5995303], [1.0, 0.5995303], [1.0, 0.5995303], [1.0, 0.5995303], [1.0, 0.5995303], [1.0, 0.5995303], [1.0, 0.5995303], [1.0, 0.5995303]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.599540404040404]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.5995404], [1.0, 0.5995404], [1.0, 0.5995404], [1.0, 0.5995404], [1.0, 0.5995404], [1.0, 0.5995404], [1.0, 0.5995404], [1.0, 0.5995404], [1.0, 0.5995404], [1.0, 0.5995404] … [1.0, 0.5995404], [1.0, 0.5995404], [1.0, 0.5995404], [1.0, 0.5995404], [1.0, 0.5995404], [1.0, 0.5995404], [1.0, 0.5995404], [1.0, 0.5995404], [1.0, 0.5995404], [1.0, 0.5995404]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.599550505050505]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.5995505], [1.0, 0.5995505], [1.0, 0.5995505], [1.0, 0.5995505], [1.0, 0.5995505], [1.0, 0.5995505], [1.0, 0.5995505], [1.0, 0.5995505], [1.0, 0.5995505], [1.0, 0.5995505] … [1.0, 0.5995505], [1.0, 0.5995505], [1.0, 0.5995505], [1.0, 0.5995505], [1.0, 0.5995505], [1.0, 0.5995505], [1.0, 0.5995505], [1.0, 0.5995505], [1.0, 0.5995505], [1.0, 0.5995505]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.599560606060606]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.5995606], [1.0, 0.5995606], [1.0, 0.5995606], [1.0, 0.5995606], [1.0, 0.5995606], [1.0, 0.5995606], [1.0, 0.5995606], [1.0, 0.5995606], [1.0, 0.5995606], [1.0, 0.5995606] … [1.0, 0.5995606], [1.0, 0.5995606], [1.0, 0.5995606], [1.0, 0.5995606], [1.0, 0.5995606], [1.0, 0.5995606], [1.0, 0.5995606], [1.0, 0.5995606], [1.0, 0.5995606], [1.0, 0.5995606]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.599570707070707]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.5995707], [1.0, 0.5995707], [1.0, 0.5995707], [1.0, 0.5995707], [1.0, 0.5995707], [1.0, 0.5995707], [1.0, 0.5995707], [1.0, 0.5995707], [1.0, 0.5995707], [1.0, 0.5995707] … [1.0, 0.5995707], [1.0, 0.5995707], [1.0, 0.5995707], [1.0, 0.5995707], [1.0, 0.5995707], [1.0, 0.5995707], [1.0, 0.5995707], [1.0, 0.5995707], [1.0, 0.5995707], [1.0, 0.5995707]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.599580808080808]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.5995808], [1.0, 0.5995808], [1.0, 0.5995808], [1.0, 0.5995808], [1.0, 0.5995808], [1.0, 0.5995808], [1.0, 0.5995808], [1.0, 0.5995808], [1.0, 0.5995808], [1.0, 0.5995808] … [1.0, 0.5995808], [1.0, 0.5995808], [1.0, 0.5995808], [1.0, 0.5995808], [1.0, 0.5995808], [1.0, 0.5995808], [1.0, 0.5995808], [1.0, 0.5995808], [1.0, 0.5995808], [1.0, 0.5995808]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.5995909090909091]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.5995909], [1.0, 0.5995909], [1.0, 0.5995909], [1.0, 0.5995909], [1.0, 0.5995909], [1.0, 0.5995909], [1.0, 0.5995909], [1.0, 0.5995909], [1.0, 0.5995909], [1.0, 0.5995909] … [1.0, 0.5995909], [1.0, 0.5995909], [1.0, 0.5995909], [1.0, 0.5995909], [1.0, 0.5995909], [1.0, 0.5995909], [1.0, 0.5995909], [1.0, 0.5995909], [1.0, 0.5995909], [1.0, 0.5995909]]) … DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.6004090909090909]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.6004091], [1.0, 0.6004091], [1.0, 0.6004091], [1.0, 0.6004091], [1.0, 0.6004091], [1.0, 0.6004091], [1.0, 0.6004091], [1.0, 0.6004091], [1.0, 0.6004091], [1.0, 0.6004091] … [1.0, 0.6004091], [1.0, 0.6004091], [1.0, 0.6004091], [1.0, 0.6004091], [1.0, 0.6004091], [1.0, 0.6004091], [1.0, 0.6004091], [1.0, 0.6004091], [1.0, 0.6004091], [1.0, 0.6004091]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.6004191919191919]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.60041916], [1.0, 0.60041916], [1.0, 0.60041916], [1.0, 0.60041916], [1.0, 0.60041916], [1.0, 0.60041916], [1.0, 0.60041916], [1.0, 0.60041916], [1.0, 0.60041916], [1.0, 0.60041916] … [1.0, 0.60041916], [1.0, 0.60041916], [1.0, 0.60041916], [1.0, 0.60041916], [1.0, 0.60041916], [1.0, 0.60041916], [1.0, 0.60041916], [1.0, 0.60041916], [1.0, 0.60041916], [1.0, 0.60041916]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.6004292929292929]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.6004293], [1.0, 0.6004293], [1.0, 0.6004293], [1.0, 0.6004293], [1.0, 0.6004293], [1.0, 0.6004293], [1.0, 0.6004293], [1.0, 0.6004293], [1.0, 0.6004293], [1.0, 0.6004293] … [1.0, 0.6004293], [1.0, 0.6004293], [1.0, 0.6004293], [1.0, 0.6004293], [1.0, 0.6004293], [1.0, 0.6004293], [1.0, 0.6004293], [1.0, 0.6004293], [1.0, 0.6004293], [1.0, 0.6004293]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.6004393939393939]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.60043937], [1.0, 0.60043937], [1.0, 0.60043937], [1.0, 0.60043937], [1.0, 0.60043937], [1.0, 0.60043937], [1.0, 0.60043937], [1.0, 0.60043937], [1.0, 0.60043937], [1.0, 0.60043937] … [1.0, 0.60043937], [1.0, 0.60043937], [1.0, 0.60043937], [1.0, 0.60043937], [1.0, 0.60043937], [1.0, 0.60043937], [1.0, 0.60043937], [1.0, 0.60043937], [1.0, 0.60043937], [1.0, 0.60043937]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.600449494949495]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.6004495], [1.0, 0.6004495], [1.0, 0.6004495], [1.0, 0.6004495], [1.0, 0.6004495], [1.0, 0.6004495], [1.0, 0.6004495], [1.0, 0.6004495], [1.0, 0.6004495], [1.0, 0.6004495] … [1.0, 0.6004495], [1.0, 0.6004495], [1.0, 0.6004495], [1.0, 0.6004495], [1.0, 0.6004495], [1.0, 0.6004495], [1.0, 0.6004495], [1.0, 0.6004495], [1.0, 0.6004495], [1.0, 0.6004495]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.600459595959596]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.6004596], [1.0, 0.6004596], [1.0, 0.6004596], [1.0, 0.6004596], [1.0, 0.6004596], [1.0, 0.6004596], [1.0, 0.6004596], [1.0, 0.6004596], [1.0, 0.6004596], [1.0, 0.6004596] … [1.0, 0.6004596], [1.0, 0.6004596], [1.0, 0.6004596], [1.0, 0.6004596], [1.0, 0.6004596], [1.0, 0.6004596], [1.0, 0.6004596], [1.0, 0.6004596], [1.0, 0.6004596], [1.0, 0.6004596]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.600469696969697]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.6004697], [1.0, 0.6004697], [1.0, 0.6004697], [1.0, 0.6004697], [1.0, 0.6004697], [1.0, 0.6004697], [1.0, 0.6004697], [1.0, 0.6004697], [1.0, 0.6004697], [1.0, 0.6004697] … [1.0, 0.6004697], [1.0, 0.6004697], [1.0, 0.6004697], [1.0, 0.6004697], [1.0, 0.6004697], [1.0, 0.6004697], [1.0, 0.6004697], [1.0, 0.6004697], [1.0, 0.6004697], [1.0, 0.6004697]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.600479797979798]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.6004798], [1.0, 0.6004798], [1.0, 0.6004798], [1.0, 0.6004798], [1.0, 0.6004798], [1.0, 0.6004798], [1.0, 0.6004798], [1.0, 0.6004798], [1.0, 0.6004798], [1.0, 0.6004798] … [1.0, 0.6004798], [1.0, 0.6004798], [1.0, 0.6004798], [1.0, 0.6004798], [1.0, 0.6004798], [1.0, 0.6004798], [1.0, 0.6004798], [1.0, 0.6004798], [1.0, 0.6004798], [1.0, 0.6004798]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.600489898989899]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.6004899], [1.0, 0.6004899], [1.0, 0.6004899], [1.0, 0.6004899], [1.0, 0.6004899], [1.0, 0.6004899], [1.0, 0.6004899], [1.0, 0.6004899], [1.0, 0.6004899], [1.0, 0.6004899] … [1.0, 0.6004899], [1.0, 0.6004899], [1.0, 0.6004899], [1.0, 0.6004899], [1.0, 0.6004899], [1.0, 0.6004899], [1.0, 0.6004899], [1.0, 0.6004899], [1.0, 0.6004899], [1.0, 0.6004899]]), DynamicalBilliardsVisualizations.ParticleHelper{Particle{Float64}, Float64}(Particle{Float64}
position: [1.0, 0.6004999999999999]
velocity: [1.0, 0.0], 0.0, 0.0, Point{2, Float32}[[1.0, 0.6005], [1.0, 0.6005], [1.0, 0.6005], [1.0, 0.6005], [1.0, 0.6005], [1.0, 0.6005], [1.0, 0.6005], [1.0, 0.6005], [1.0, 0.6005], [1.0, 0.6005] … [1.0, 0.6005], [1.0, 0.6005], [1.0, 0.6005], [1.0, 0.6005], [1.0, 0.6005], [1.0, 0.6005], [1.0, 0.6005], [1.0, 0.6005], [1.0, 0.6005], [1.0, 0.6005]])]), Observable(DynamicalBilliardsVisualizations.CollisionHelper{Float64}[DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.4899997448978928, 0, [0.8855668664872378, -0.19900000000000007]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.4899976936649391, 0, [0.8855771736673416, -0.19902020202020193]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.48999564221517133, 0, [0.8855874808905959, -0.19904040404040396]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.4899935905485867, 0, [0.8855977881570055, -0.19906060606060602]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.4899915386651826, 0, [0.8856080954665754, -0.19908080808080802]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.48998948656495617, 0, [0.8856184028193104, -0.19910101010101006]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.4899874342479048, 0, [0.8856287102152155, -0.19912121212121198]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.48998538171402567, 0, [0.8856390176542955, -0.19914141414141412]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.48998332896331614, 0, [0.8856493251365555, -0.1991616161616161]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.4899812759957734, 0, [0.8856596326620003, -0.19918181818181818]) … DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.48981426527083705, 0, [0.886494686092584, -0.20081818181818178]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.4898121945136692, 0, [0.8865049971759564, -0.20083838383838376]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.4898101235394408, 0, [0.8865153083029229, -0.2008585858585859]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.48980805234814917, 0, [0.8865256194734884, -0.20087878787878788]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.4898059809397915, 0, [0.8865359306876579, -0.20089898989898997]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.489803909314365, 0, [0.8865462419454363, -0.20091919191919203]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.48980183747186706, 0, [0.8865565532468285, -0.20093939393939397]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.48979976541229475, 0, [0.8865668645918394, -0.20095959595959606]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.4897976931356454, 0, [0.8865771759804741, -0.2009797979797981]), DynamicalBilliardsVisualizations.CollisionHelper{Float64}(2, 0.48979562064191634, 0, [0.8865874874127374, -0.2009999999999999])]))Then, we add some axis
layout = fig[2,1] = GridLayout()
axd = Axis(layout[1,1]; ylabel = "log(⟨d⟩)", alignmode = Outside())
axs = Axis(layout[2,1]; ylabel = "std", xlabel = "time", alignmode = Outside())
hidexdecorations!(axd; grid = false)
rowsize!(fig.layout, 1, Auto(2))
fig
Our next step is to create new observables to plot in the new axis, by lifting phs, chs. Let's plot the distance between two particles and the std of the particle y position.
using Statistics: std
# Define observables
d_p(phs) = log(sum(sqrt(sum(phs[1].p.pos .- phs[j].p.pos).^2) for j in 2:N)/N)
std_p(phs) = std(p.p.pos[1] for p in phs)
t = Observable([0.0]) # Time axis
d = Observable([d_p(phs[])])
s = Observable([std_p(phs[])])
# Trigger observable updates
on(phs) do phs
push!(t[], phs[1].T)
push!(d[], d_p(phs))
push!(s[], std_p(phs))
notify.((t, d))
autolimits!(axd); autolimits!(axs)
end
# Plot observables
lines!(axd, t, d; color = Cycled(1))
lines!(axs, t, s; color = Cycled(2))
nothingThe figure hasn't changed yet of course, but after we step the animation, it does:
dt = 0.001
for j in 1:1000
for i in 1:9
bdplot_animstep!(phs, chs, bd, dt; update = false)
end
bdplot_animstep!(phs, chs, bd, dt; update = true)
end
fig
Of course, you can produce a video of this using Makie's record function.
Video output
DynamicalBilliards.bdplot_video — Functionbdplot_video(file::String, bd::Billiard, ps::Vector{<:AbstractParticle}; kwargs...)Create an animation of ps evolving in bd and save it into file. This function shares all visualization-related keywords with bdplot_interactive. Other keywords are:
steps = 10: How manydt-steps are taken between producing a new frame.frames = 1000: How many frames to produce in total.framerate = 60.
Here is an example that changes plotting defaults to make an animation in the style of 3Blue1Brown.
using DynamicalBilliards, CairoMakie
BLUE = "#7BC3DC"
BROWN = "#8D6238"
colors = [BLUE, BROWN]
# Overwrite default color of obstacles to white (to fit with black background)
bd = billiard_stadium(1, 1)
ps = particlebeam(1.0, 0.6, 0, 200, 0.01)
# Notice that keyword `color = :white` is propagated to billiard plot
bdplot_video(
"3b1billiard.mp4", bd, ps;
frames = 120, colors, dt = 0.01, tail_length = 100,
figure = (backgroundcolor = :black,), framerate = 10, color = :white,
)