|
| 1 | +using LinearAlgebra |
| 2 | +using TimeseriesTools |
| 3 | +backgroundcolor = :transparent |
| 4 | +try # * Use GLMakie if available, much faster |
| 5 | + using GLMakie # * e.g. you could have it installed in the base project |
| 6 | + @info "Using GLMakie" |
| 7 | + global backgroundcolor = :white |
| 8 | +catch |
| 9 | + using CairoMakie |
| 10 | +end |
| 11 | +using Random |
| 12 | + |
| 13 | +f = Figure(; size=(400, 600), backgroundcolor) |
| 14 | +ax = Axis3(f[1, 1]; aspect=:data, backgroundcolor) |
| 15 | +μ_ext = (-50, 20) |
| 16 | +xscale = 13 |
| 17 | +x_ext = (-15, 15) .* xscale |
| 18 | +radius = 5 |
| 19 | +hidedecorations!(ax) |
| 20 | + |
| 21 | +# * Draw a series of potential functions |
| 22 | +V(x, μ) = -μ .* (x ./ xscale) .^ 2 / 2 + (x ./ xscale) .^ 4 ./ 4 |
| 23 | +colormap = cgrad(:RdYlBu) |> reverse # , [0, μ_ext[2] / (μ_ext[2] - μ_ext[1]), 1] |
| 24 | +μs = range(μ_ext..., step=0.1) |
| 25 | +xs = range(x_ext..., step=0.1) |
| 26 | +surf = V.([xs], μs) |
| 27 | +surf = stack(surf) |
| 28 | +cmat = repeat(colormap[eachindex(μs)./(length(μs))]', length(xs)) |
| 29 | +surface!(ax, xs, μs, surf, color=cmat) |
| 30 | + |
| 31 | +μs = range(μ_ext..., step=10) |
| 32 | +for (i, μ) in enumerate(μs) |
| 33 | + y = (V.(xs, μ)) |
| 34 | + lines!(ax, xs, fill(μ, length(y)), y, color=(colormap[i./(length(μs))], 0.9), linewidth=4, joinstyle=:round, linecap=:round) |
| 35 | +end |
| 36 | + |
| 37 | +# * Add lines to basin minima |
| 38 | +begin |
| 39 | + μs = range(μ_ext..., step=0.0005) |
| 40 | + x1 = similar(μs) |
| 41 | + x1 .= NaN |
| 42 | + x2 = deepcopy(x1) |
| 43 | + x3 = deepcopy(x1) |
| 44 | + x1[μs.≤0] .= 0 |
| 45 | + x2[μs.>0] .= sqrt.(μs[μs.>0]) .* xscale |
| 46 | + x3[μs.>0] .= -sqrt.(μs[μs.>0]) .* xscale |
| 47 | + lines!(ax, x1, μs, V.(x1, μs), color=:black, linewidth=2, alpha=0.2, joinstyle=:round, linecap=:round) |
| 48 | + lines!(ax, x2, μs, V.(x2, μs), color=:black, linewidth=2, alpha=0.2, joinstyle=:round, linecap=:round) |
| 49 | + lines!(ax, x3, μs, V.(x3, μs), color=:black, linewidth=2, alpha=0.2, joinstyle=:round, linecap=:round) |
| 50 | +end |
| 51 | + |
| 52 | +function centeroffset(μ, x, z; radius=radius, x_scale=xscale) |
| 53 | + f_x(x, μ) = -μ * x / (x_scale^2) + x^3 / (x_scale^4) |
| 54 | + f_μ(x, μ) = -0.5 * (x / x_scale)^2 |
| 55 | + g = [f_x(x, μ), f_μ(x, μ), -1] |
| 56 | + g = g ./ norm(g) |
| 57 | + p = (x, μ, z) .- radius .* g |
| 58 | + return p |
| 59 | +end |
| 60 | +function centeroffset(μ::AbstractVector, x::AbstractVector, z::AbstractVector; kwargs...) |
| 61 | + ps = centeroffset.(μ, x, z; kwargs...) |
| 62 | + x = first.(ps) |
| 63 | + μ = getindex.(ps, 2) |
| 64 | + z = last.(ps) |
| 65 | + return x, μ, z |
| 66 | +end |
| 67 | +# * Add ball and trajectory |
| 68 | +function ball!(ax, μs, ηs; color=:black, μmax=last(μs)) |
| 69 | + function traj(μs, ηs; x0=0.0) |
| 70 | + if ηs isa Number |
| 71 | + ηs = fill(ηs, length(μs)) |
| 72 | + end |
| 73 | + dt = 1e-4 |
| 74 | + x = similar(μs) |
| 75 | + x[1] = x0 |
| 76 | + for i in 2:length(μs) |
| 77 | + r = x[i-1] |
| 78 | + x[i] = r + (μs[i] * (r) / xscale^2 - (r)^3 / xscale^4) * dt + xscale * ηs[i] * sqrt(dt) * randn() # * Fix for xscale |
| 79 | + end |
| 80 | + return x |
| 81 | + end |
| 82 | + if μs isa Number |
| 83 | + μs = range(μ_ext[1], μmax, step=0.01) |
| 84 | + end |
| 85 | + xs = traj(μs, ηs) |
| 86 | + xs = .-xs |
| 87 | + zs = V.(xs, μs) |
| 88 | + xs, μs, zs = centeroffset(μs, xs, zs) |
| 89 | + trajectory = Point3f.(xs, μs, zs) |
| 90 | + |
| 91 | + idxs = μs .≤ μmax |
| 92 | + xs = xs[idxs] |
| 93 | + μs = μs[idxs] |
| 94 | + zs = zs[idxs] |
| 95 | + line = Point3f.(xs, μs, zs) |> Observable |
| 96 | + lines!(ax, line, color=(color, 0.8), linewidth=2, joinstyle=:round, linecap=:round) |
| 97 | + |
| 98 | + x = xs[end] |
| 99 | + μ = μs[end] |
| 100 | + z = zs[end] |
| 101 | + |
| 102 | + ball = Point3f(x, μ, z) |> Observable |
| 103 | + |
| 104 | + meshscatter!(ax, ball, markersize=radius, color=color, shininess=10.0f0, specular=1) |
| 105 | + return (; ball, line, trajectory) |
| 106 | +end |
| 107 | + |
| 108 | +Random.seed!(83) |
| 109 | +cmap = cgrad(:inferno) |
| 110 | +nballs = 4 |
| 111 | +μmax = μ_ext[2] - 2 |
| 112 | +balls = [ball!(ax, 5, 1; color=cmap[1/(nballs+1)], μmax), |
| 113 | + ball!(ax, -15, 2; color=cmap[2/(nballs+1)], μmax), |
| 114 | + ball!(ax, -25, 3; color=cmap[3/(nballs+1)], μmax), |
| 115 | + ball!(ax, μ_ext[2] - 2, 14 / 4; color=cmap[4/(nballs+1)], μmax)] |
| 116 | +ax.azimuth = π / 1.7 |
| 117 | +ax.elevation = π / 3 |
| 118 | +limits!(ax, (-100, 100), (nothing, nothing), (-50, 100)) |
| 119 | +hidespines!(ax) |
| 120 | +tightlimits!(ax) |
| 121 | +save(joinpath(@__DIR__, "CoverImage_potential.png"), f; px_per_unit=10) |
| 122 | +f |
| 123 | + |
| 124 | + |
| 125 | +begin # * Now an animated version |
| 126 | + map(balls) do ball # ? Reset positions |
| 127 | + ball[:ball][] = ball[:trajectory][1] |
| 128 | + ball[:line][] = ball[:trajectory][[1]] |
| 129 | + end |
| 130 | + ts = map(enumerate(balls)) do (i, b) |
| 131 | + t = eachindex(b[:trajectory]) |
| 132 | + zip(t, fill(i, length(t))) |> collect |
| 133 | + end |> Iterators.flatten |> collect |
| 134 | + ts = ts[1:50:end] |
| 135 | + aztrack = range(π / 2 - π / 4, π / 2 + π / 4, length=ceil(Int, length(ts) / 2)) |
| 136 | + aztrack = vcat(aztrack, reverse(aztrack)) |
| 137 | + aztrack = aztrack[1:length(ts)] |
| 138 | + |
| 139 | + |
| 140 | + record(f, joinpath(@__DIR__, "CoverImage_potential.mp4"), enumerate(ts); |
| 141 | + framerate=32) do (i, (t, b)) |
| 142 | + ball = balls[b] |
| 143 | + ball[:ball][] = ball[:trajectory][t] .+ Point3f(0, 0, 0.001) |
| 144 | + ball[:line][] = ball[:trajectory][1:t] .+ Point3f(0, 0, 0.001) |
| 145 | + ax.azimuth[] = aztrack[i] |
| 146 | + end |
| 147 | +end |
0 commit comments