SCJ_Projekt/scripts/run_simulation.jl

277 lines
6.6 KiB
Julia

using GLMakie, Observables
include("../src/constants.jl")
using .Constants
include("../src/visualization.jl")
using .Visualization
# # Gray Scott Model Parameters
# Parameters and initial conditions
N = 128
dx = 1.0
# diffusion rates of substance 'u' and 'v'
Du, Dv = Observable(0.142), Observable(0.078)
# feed rate of 'u' and kill rate of 'v'
F, k = Observable(0.0617), Observable(0.062)
dt = 1.0
params_obs = Observable(GSParams(N, dx, Du[], Dv[], F[], k[]))
# # GUI Parameters
run_label = Observable{String}("Run")
stepsize = Observable{Int}(30)
function update_params!(params_obs::Observable, u, v, feed, kill)
old = params_obs[]
params_obs[] = GSParams(old.N, old.dx, u, v, feed, kill)
current_params()
end
# Whenever a value gets changed via the textbox, update params object to reflect changes in simulation
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
# set a cube in the center with starting concentrations for 'u' and 'v'
jitter_amount = 5 # maximale Verschiebung in Pixeln
margin = radius + 2 # Abstand zum Rand
# Manuell definierte gleichverteilte Positionen (6 Punkte)
positions = [
(1/4, 1/4),
(3/4, 1/4),
(1/2, 1/2),
(1/4, 3/4),
(3/4, 3/4),
(1/2, 1/8)
]
centers = []
for (px, py) in positions
# Position relativ zu Feldgröße + Jitter
cx = clamp(round(Int, px * N) + rand(-jitter_amount:jitter_amount), margin, N - margin)
cy = clamp(round(Int, py * N) + rand(-jitter_amount:jitter_amount), margin, N - margin)
push!(centers, (cx, cy))
for x in (cx - radius):(cx + radius)
for y in (cy - radius):(cy + radius)
if x > 0 && x N && y > 0 && y N
if sqrt((x - cx)^2 + (y - cy)^2) radius
U[x, y] = 0.5
V[x, y] = 0.25
end
end
end
end
end
# 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, params_obs::Observable)
lap_u = laplacian5(U)
lap_v = laplacian5(V)
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)
for _ in 1:n_steps
heat_obs[] = step!(state[1], state[2], params_obs)
end
end
# Build GUI
fig = Figure(size=(600, 600))
gh = GridLayout(fig[1, 1])
ax = Axis(gh[1, 1])
hm = Makie.heatmap!(ax, heat_obs, colormap=:viridis)
Makie.deactivate_interaction!(ax, :rectanglezoom)
ax.aspect = DataAspect()
spoint = select_point(ax.scene)
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
r = 5
on(spoint) do pt
if pt === nothing
return
end
x, y = pt
i, j = coord_to_index(x, y, N)
# get corners of square that will get filled with concentration
imin = max(i - r, 1)
imax = min(i + r, N)
jmin = max(j - r, 1)
jmax = min(j + r, N)
# set disbalanced concentration of U and V
U[imin:imax, jmin:jmax] .= 0.5
V[imin:imax, jmin:jmax] .= 0.25
heat_obs[] = copy(U)
end
# # 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
on(speed_slider) do s
try
stepsize[] = s
println("Changed stepsize to $s")
catch
@warn "Invalid input for $s"
end
end
gh[1, 2] = textboxgrid = GridLayout(ax.scene, tellwidth=false)
rowsize!(gh, 1, Relative(1.0))
function param_box!(row, labeltxt, observable)
Label(textboxgrid[row, 1], labeltxt)
box = Textbox(textboxgrid[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
param_box!(1, "Du", Du)
param_box!(2, "Dv", Dv)
param_box!(3, "Feed", F)
param_box!(4, "kill", k)
# Timer and state for animation
running = Observable(false)
function reset!(U, V, heat_obs)
current_params()
U .= 1.0
V .= 0.0
center = size(U, 1) ÷ 2
radius = 10
num_spots = 6
jitter_amount = 5 # maximale Verschiebung in Pixeln
margin = radius + 2 # Abstand zum Rand
# Manuell definierte gleichverteilte Positionen (6 Punkte)
positions = [
(1/4, 1/4),
(3/4, 1/4),
(1/2, 1/2),
(1/4, 3/4),
(3/4, 3/4),
(1/2, 1/8)
]
centers = []
for (px, py) in positions
# Position relativ zu Feldgröße + Jitter
cx = clamp(round(Int, px * N) + rand(-jitter_amount:jitter_amount), margin, N - margin)
cy = clamp(round(Int, py * N) + rand(-jitter_amount:jitter_amount), margin, N - margin)
push!(centers, (cx, cy))
for x in (cx - radius):(cx + radius)
for y in (cy - radius):(cy + radius)
if x > 0 && x N && y > 0 && y N
if sqrt((x - cx)^2 + (y - cy)^2) radius
U[x, y] = 0.5
V[x, y] = 0.25
end
end
end
end
end
heat_obs[] = copy(U)
end
on(running) do r
run_label[] = r ? "Pause" : "Run"
end
# Button Listeners
on(btn_step.clicks) do _
multi_step!((U, V), stepsize[])
end
on(btn_start.clicks) do _
running[] = !running[]
end
on(btn_start.clicks) do _
@async while running[]
multi_step!((U, V), stepsize[])
sleep(0.05) # ~20 FPS
end
end
on(btn_reset.clicks) do _
running[] = false
reset!(U, V, heat_obs)
end
display(fig)