split gray scott simulation into multiple files

pull/3/head
Nikola Sebastian Munder 2025-06-11 14:48:24 +02:00
parent 2819c48076
commit 32f3885e86
3 changed files with 189 additions and 3 deletions

28
scripts/main.jl 100644
View File

@ -0,0 +1,28 @@
include("../src/gray_scott_solver.jl")
include("../src/visualization.jl")
include("../src/constants.jl")
using Observables
using GLMakie
using .GrayScottSolver
using .Visualization
using .Constants
const N = 128
const dx = 1.0
Du, Dv = Observable(0.16), Observable(0.08)
F, k = Observable(0.060), Observable(0.062)
params_obs = Observable(GSParams(N, dx, Du[], Dv[], F[], k[]))
lift(Du, Dv, F, k) do u, v, f, ki
params_obs[] = GSParams(N, dx, u, v, f, ki)
end
U = ones(N, N)
V = zeros(N, N)
heat_obs = Observable(U)
fig = build_ui(U, V, Du, Dv, F, k, params_obs, heat_obs)
display(fig)

View File

@ -0,0 +1,55 @@
module GrayScottSolver
using Observables
include("constants.jl")
using .Constants
export laplacian5, step!, multi_step!
function laplacian5(f, dx)
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, params_obs::Observable; dx=1)
lap_u = laplacian5(U, dx)
lap_v = laplacian5(V, dx)
diff_u = params_obs[].Du
diff_v = params_obs[].Dv
feed_u = params_obs[].F
kill_v = params_obs[].k
u = U[2:end-1, 2:end-1]
v = V[2:end-1, 2:end-1]
uvv = u .* v .* v
u_new = u .+ (diff_u .* lap_u .- uvv .+ feed_u .* (1 .- u))
v_new = v .+ (diff_v .* lap_v .+ uvv .- (feed_u .+ kill_v) .* v)
# 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
return U
end
function multi_step!(state, n_steps, heat_obs::Observable, params_obs::Observable; dx=1)
for _ in 1:n_steps
heat_obs[] = step!(state[1], state[2], params_obs; dx=1)
end
end
end

View File

@ -1,6 +1,7 @@
module Visualization
using GLMakie
include("gray_scott_solver.jl")
using GLMakie, Observables, Makie
using .GrayScottSolver
"""
step_through_solution(sol::SolutionType, N::Int)
@ -18,18 +19,120 @@ function step_through_solution(sol, N::Int)
ax = Axis(fig[1, 1])
slider = Slider(fig[2, 1], range=1:length(sol), startvalue=1)
textbox = Textbox(fig[1, 2], placeholder="Feed Rate", validator=Float64)
F = Observable(0.060)
# Initialize heatmap with first time step
u0 = reshape(sol[1][1:N^2], N, N)
heat_obs = Observable(u0)
hmap = heatmap!(ax, heat_obs, colormap=:magma)
on(textbox.stored_string) do s
F[] = parse(Float64, s)
end
# Update heatmap on slider movement
on(slider.value) do i
u = reshape(sol[i][1:N^2], N, N)
heat_obs[] = u
end
display(fig)
end
function coord_to_index(x, y, N)
ix = clamp(round(Int, x), 1, N)
iy = clamp(round(Int, y), 1, N)
return ix, iy
end
function reset!(U, V, heat_obs)
U .= 1.0
V .= 0.0
center = size(U, 1) ÷ 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
heat_obs[] = copy(U)
end
function param_box!(grid, row, labeltxt, observable::Observable)
Label(grid[row, 1], labeltxt)
box = Textbox(grid[row, 2], validator=Float64, width=50, placeholder=labeltxt, stored_string="$(observable[])")
on(box.stored_string) do s
try
observable[] = parse(Float64, s)
println("changed $labeltxt to $s")
catch
@warn "Invalid input for $labeltxt: $s"
end
end
end
function build_ui(U, V, Du, Dv, F, k, params_obs, heat_obs)
reset!(U, V, heat_obs)
fig = Figure(size=(800, 800))
gh = GridLayout(fig[1, 1])
ax = Axis(gh[1, 1])
hm = Makie.heatmap!(ax, heat_obs, colormap=:viridis)
deactivate_interaction!(ax, :rectanglezoom)
ax.aspect = DataAspect()
run_label = Observable{String}("Run")
stepsize = Observable(30)
# # Controls
fig[2, 1] = buttongrid = GridLayout(ax.scene, tellwidth=false)
btn_step = Button(buttongrid[1, 1], width=50, label="Step")
btn_start = Button(buttongrid[1, 2], width=50, label=run_label)
btn_reset = Button(buttongrid[1, 3], width=50, label="Reset")
slidergrid = SliderGrid(fig[3, 1], (label="Speed", range=1:1:100, format="{}x", width=350, startvalue=stepsize[]))
speed_slider = slidergrid.sliders[1].value
gh[1, 2] = textboxgrid = GridLayout(ax.scene, tellwidth=false)
rowsize!(gh, 1, Relative(1.0))
param_box!(textboxgrid, 1, "Du", Du)
param_box!(textboxgrid, 2, "Dv", Dv)
param_box!(textboxgrid, 3, "Feed", F)
param_box!(textboxgrid, 4, "kill", k)
# Timer and state for animation
running = Observable(false)
on(running) do r
run_label[] = r ? "Pause" : "Run"
end
on(speed_slider) do s
try
stepsize[] = s
println("Changed stepsize to $s")
catch
@warn "Invalid input for $s"
end
end
# Button Listeners
on(btn_step.clicks) do _
multi_step!((U, V), stepsize[], heat_obs, params_obs)
end
on(btn_start.clicks) do _
running[] = !running[]
end
on(btn_start.clicks) do _
@async while running[]
multi_step!((U, V), stepsize[], heat_obs, params_obs)
sleep(0.0015) # ~20 FPS
end
end
on(btn_reset.clicks) do _
running[] = false
reset!(U, V, heat_obs)
end
return fig
end
export step_through_solution, build_ui
end