diff --git a/scripts/run_simulation.jl b/scripts/run_simulation.jl index bb923e3..6eb584e 100644 --- a/scripts/run_simulation.jl +++ b/scripts/run_simulation.jl @@ -1,31 +1,131 @@ -include("../src/AnimalFurFHN.jl") # this loads the module code +using GLMakie, Observables using .AnimalFurFHN -# include optional visualizer only if needed: -include("../src/visualization.jl") -using .Visualization - -N = 256 -# tspan = (0.0, 1000.0) - -# sol = AnimalFurFHN.run_simulation(tspan, N) -# Visualization.step_through_solution(sol, N) +# Parameters and initial conditions +N = 128 +dx = 1.0 +Du, Dv = Observable(0.16), Observable(0.08) +F, k = Observable(0.060), Observable(0.062) +dt = 1.0 +params_obs = Observable(AnimalFurFHN.GSParams(N, dx, 0.16, 0.08, 0.08, 0.06)) -using Plots - -function animate_gray_scott(sol, N; var=:U) - anim = @animate for t in 1:length(sol.t) - u = reshape(sol[t][1:N^2], N, N) - v = reshape(sol[t][N^2+1:end], N, N) - data = var == :U ? u : v - Plots.heatmap(data, c=:magma, title="$var at t=$(Int(sol.t[t]))", clims=(0, 1)) - end - gif(anim, "gif/gray_scott.gif", fps=15) +function update_params!(params_obs, u, v, feed, kill) + old = params_obs[] + params_obs[] = AnimalFurFHN.GSParams(old.N, old.dx, u, v, feed, kill) end -# set time to more than 1000 -sol = AnimalFurFHN.run_simulationG((0.0, 10000.0), 256) +lift(Du, Dv, F, k) do u, v, F, k + update_params!(params_obs, u, v, F, k) +end +U = ones(N, N) +V = zeros(N, N) +center = N รท 2 +radius = 10 +U[center-radius:center+radius, center-radius:center+radius] .= 0.50 +V[center-radius:center+radius, center-radius:center+radius] .= 0.25 -Visualization.step_through_solution(sol, N) -# animate_gray_scott(sol, 256, var=:U) +# Observable holding current U for heatmap +heat_obs = Observable(U) + +function laplacian5(f) + h2 = dx^2 + left = f[2:end-1, 1:end-2] + right = f[2:end-1, 3:end] + down = f[3:end, 2:end-1] + up = f[1:end-2, 2:end-1] + center = f[2:end-1, 2:end-1] + return (left .+ right .+ down .+ up .- 4 .* center) ./ h2 +end + +function step!(U, V) + lap_u = laplacian5(U) + lap_v = laplacian5(V) + + u = U[2:end-1, 2:end-1] + v = V[2:end-1, 2:end-1] + + uvv = u .* v .* v + u_new = u .+ (Du[] .* lap_u .- uvv .+ F[] .* (1 .- u)) .* dt + v_new = v .+ (Dv[] .* lap_v .+ uvv .- (F[] .+ k[]) .* v) .* dt + + # Update with new values + U[2:end-1, 2:end-1] .= u_new + V[2:end-1, 2:end-1] .= v_new + + # Periodic boundary conditions + U[1, :] .= U[end-1, :] + U[end, :] .= U[2, :] + U[:, 1] .= U[:, end-1] + U[:, end] .= U[:, 2] + + V[1, :] .= V[end-1, :] + V[end, :] .= V[2, :] + V[:, 1] .= V[:, end-1] + V[:, end] .= V[:, 2] + + # Update heatmap observable + heat_obs[] = copy(U) +end +function multi_step!(state, n_steps) + for _ in 1:n_steps + step!(state[1], state[2]) + end +end + +# Build GUI +fig = Figure(size=(600, 600)) +ax = Axis(fig[1, 1]) + + + +hm = Makie.heatmap!(ax, heat_obs, colormap=:magma) +ax.aspect = DataAspect() + +# # Controls + +fig[2, 1] = buttongrid = GridLayout(tellwidth=false) +btn_step = Button(buttongrid[1, 1], label="Step") +btn_start = Button(buttongrid[1, 2], label="Start") +btn_stop = Button(buttongrid[1, 3], label="Stop") + + +fig[3, 1] = textboxgrid = GridLayout(tellwidth=false) +box_u = Textbox(textboxgrid[1, 1], validator=Float64, placeholder="word") +# box_v = Textbox(textboxgrid[1, 2], validator=Float64, placeholder="word") +# box_feed = Textbox(textboxgrid[1, 3], validator=Float64, placeholder="word") +# box_kill = Textbox(textboxgrid[1, 4], validator=Float64, placeholder="word") +# Timer and state for animation +running = Observable(false) + +function animation_loop() + while running[] + # step!(U, V) + multi_step!((U, V), 30) + sleep(0.05) # ~20 FPS + end +end +on(box_u.stored_string) do s + try + Du[] = parse(Float64, s) + catch + @warn "Invalid input for Du: $s" + end +end + +on(btn_step.clicks) do _ + multi_step!((U, V), 30) +end + +on(btn_start.clicks) do _ + if !running[] + running[] = true + @async animation_loop() + end +end + +on(btn_stop.clicks) do _ + running[] = false +end + +display(fig)