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
bdplot
andbdplot_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 stepsdt
to 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_length
positions 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 toFigure
creation.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 eachdt
step: the particle, time elapsed since last collision, total time ellapsed, tail (positions in the lasttail_length
dt
-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))
nothing
The 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,
)