Compare commits

...

8 Commits

4 changed files with 148 additions and 42 deletions

View File

@ -19,7 +19,6 @@ lift(Du, Dv, F, k) do u, v, f, ki
params_obs[] = GSParams(N, dx, u, v, f, ki) params_obs[] = GSParams(N, dx, u, v, f, ki)
end end
U = ones(N, N) U = ones(N, N)
V = zeros(N, N) V = zeros(N, N)
heat_obs = Observable(U) heat_obs = Observable(U)

View File

@ -9,9 +9,9 @@ using .Visualization
N = 128 N = 128
dx = 1.0 dx = 1.0
# diffusion rates of substance 'u' and 'v' # diffusion rates of substance 'u' and 'v'
Du, Dv = Observable(0.16), Observable(0.08) Du, Dv = Observable(0.142), Observable(0.078)
# feed rate of 'u' and kill rate of 'v' # feed rate of 'u' and kill rate of 'v'
F, k = Observable(0.060), Observable(0.062) F, k = Observable(0.0617), Observable(0.062)
dt = 1.0 dt = 1.0
params_obs = Observable(GSParams(N, dx, Du[], Dv[], F[], k[])) params_obs = Observable(GSParams(N, dx, Du[], Dv[], F[], k[]))
@ -24,6 +24,7 @@ stepsize = Observable{Int}(30)
function update_params!(params_obs::Observable, u, v, feed, kill) function update_params!(params_obs::Observable, u, v, feed, kill)
old = params_obs[] old = params_obs[]
params_obs[] = GSParams(old.N, old.dx, u, v, feed, kill) params_obs[] = GSParams(old.N, old.dx, u, v, feed, kill)
current_params()
end end
# Whenever a value gets changed via the textbox, update params object to reflect changes in simulation # Whenever a value gets changed via the textbox, update params object to reflect changes in simulation
@ -35,8 +36,40 @@ V = zeros(N, N)
center = N ÷ 2 center = N ÷ 2
radius = 10 radius = 10
# set a cube in the center with starting concentrations for 'u' and 'v' # set a cube in the center with starting concentrations for 'u' and 'v'
U[center-radius:center+radius, center-radius:center+radius] .= 0.50
V[center-radius:center+radius, center-radius:center+radius] .= 0.25 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 # Observable holding current U for heatmap
heat_obs = Observable(U) heat_obs = Observable(U)
@ -170,12 +203,47 @@ param_box!(4, "kill", k)
# Timer and state for animation # Timer and state for animation
running = Observable(false) running = Observable(false)
function reset!(U, V, heat_obs) function reset!(U, V, heat_obs)
current_params()
U .= 1.0 U .= 1.0
V .= 0.0 V .= 0.0
center = size(U, 1) ÷ 2 center = size(U, 1) ÷ 2
radius = 10 radius = 10
U[center-radius:center+radius, center-radius:center+radius] .= 0.50
V[center-radius:center+radius, center-radius:center+radius] .= 0.25 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) heat_obs[] = copy(U)
end end

View File

@ -16,7 +16,7 @@ struct FHNParams
end end
struct GSParams struct GSParams
N::Int # grid size N::Int64 # grid size
dx::Float64 # grid spacing dx::Float64 # grid spacing
Du::Float64 # diffusion rate U Du::Float64 # diffusion rate U
Dv::Float64 # diffusion rate V Dv::Float64 # diffusion rate V

View File

@ -1,18 +1,20 @@
module Visualization module Visualization
include("gray_scott_solver.jl") include("gray_scott_solver.jl")
using GLMakie, Observables, Makie using GLMakie, Observables, Makie
using Random
using .GrayScottSolver using .GrayScottSolver
""" """
step_through_solution(sol::SolutionType, N::Int) step_through_solution(sol::SolutionType, N::Int)
Function for visualization for the output of run_simulation Function for visualization for the output of run_simulation
# Arguments # Arguments
- `sol`: computed differential equation by run_simulation - sol: computed differential equation by run_simulation
- `N`: size of the N×N grid - N: size of the N×N grid
# Returns # Returns
- ``: Displays created figure - Displays created figure
""" """
function step_through_solution(sol, N::Int) function step_through_solution(sol, N::Int)
fig = Figure(resolution=(600, 600)) fig = Figure(resolution=(600, 600))
@ -24,45 +26,79 @@ function step_through_solution(sol, N::Int)
# Initialize heatmap with first time step # Initialize heatmap with first time step
u0 = reshape(sol[1][1:N^2], N, N) u0 = reshape(sol[1][1:N^2], N, N)
heat_obs = Observable(u0) heat_obs = Observable(u0)
hmap = heatmap!(ax, heat_obs, colormap=:magma) heatmap!(ax, heat_obs, colormap=:magma)
on(textbox.stored_string) do s on(textbox.stored_string) do s
F[] = parse(Float64, s) F[] = parse(Float64, s)
end end
# Update heatmap on slider movement # Update heatmap on slider movement
on(slider.value) do i on(slider.value) do i
u = reshape(sol[i][1:N^2], N, N) u = reshape(sol[i][1:N^2], N, N)
heat_obs[] = u heat_obs[] = u
end end
display(fig) display(fig)
end end
function coord_to_index(x, y, N) function coord_to_index(x, y, N)
ix = clamp(round(Int, x), 1, N) ix = clamp(round(Int, x), 1, N)
iy = clamp(round(Int, y), 1, N) iy = clamp(round(Int, y), 1, N)
return ix, iy return ix, iy
end end
function reset!(U, V, heat_obs) function reset!(U, V, heat_obs)
U .= 1.0 U .= 1.0
V .= 0.0 V .= 0.0
# Default starting point for Run
default_start!(U, V)
heat_obs[] = copy(U)
end
function default_start!(U, V)
center = size(U, 1) ÷ 2 center = size(U, 1) ÷ 2
radius = 10 radius = 10
U[center-radius:center+radius, center-radius:center+radius] .= 0.50 U[center-radius:center+radius, center-radius:center+radius] .= 0.50
V[center-radius:center+radius, center-radius:center+radius] .= 0.25 V[center-radius:center+radius, center-radius:center+radius] .= 0.25
heat_obs[] = copy(U)
end end
function starting_points!(U, V)
N = size(U, 1)
radius = 10
jitter = 5
margin = radius + 2
rels = [
(1 / 4, 1 / 4), (3 / 4, 1 / 4), (1 / 2, 1 / 2),
(1 / 4, 3 / 4), (3 / 4, 3 / 4), (1 / 2, 1 / 8)
]
for (px, py) in rels
cx = clamp(round(Int, px * N) + rand(-jitter:jitter), margin, N - margin)
cy = clamp(round(Int, py * N) + rand(-jitter:jitter), margin, N - margin)
for x in (cx-radius):(cx+radius), y in (cy-radius):(cy+radius)
if 1 x N && 1 y N && hypot(x - cx, y - cy) radius
U[x, y] = 0.50
V[x, y] = 0.25
end
end
end
end
function param_box!(grid, row, labeltxt, observable::Observable) function param_box!(grid, row, labeltxt, observable::Observable)
Label(grid[row, 1], labeltxt) Label(grid[row, 1], labeltxt)
box = Textbox(grid[row, 2], validator=Float64, width=50, placeholder=labeltxt, stored_string="$(observable[])") box = Textbox(grid[row, 2], validator=Float64, width=50,
placeholder=labeltxt, stored_string="$(observable[])")
on(box.stored_string) do s on(box.stored_string) do s
try try
observable[] = parse(Float64, s) observable[] = parse(Float64, s)
println("changed $labeltxt to $s") box.displayed_string[] = s
catch catch
@warn "Invalid input for $labeltxt: $s" @warn "Invalid input for $labeltxt: $s"
end end
end end
on(observable) do val
box.displayed_string[] = string(val)
end
return box
end end
function build_ui(U, V, Du, Dv, F, k, params_obs, heat_obs) function build_ui(U, V, Du, Dv, F, k, params_obs, heat_obs)
@ -70,67 +106,72 @@ function build_ui(U, V, Du, Dv, F, k, params_obs, heat_obs)
fig = Figure(size=(800, 800)) fig = Figure(size=(800, 800))
gh = GridLayout(fig[1, 1]) gh = GridLayout(fig[1, 1])
ax = Axis(gh[1, 1]) ax = Axis(gh[1, 1])
hm = Makie.heatmap!(ax, heat_obs, colormap=:viridis) heatmap!(ax, heat_obs, colormap=:viridis)
deactivate_interaction!(ax, :rectanglezoom) deactivate_interaction!(ax, :rectanglezoom)
ax.aspect = DataAspect() ax.aspect = DataAspect()
run_label = Observable{String}("Run") run_label = Observable("Run")
stepsize = Observable(30) stepsize = Observable(30)
spoint = select_point(ax.scene) spoint = select_point(ax.scene)
# # Controls # # Controls
fig[2, 1] = buttongrid = GridLayout(ax.scene, tellwidth=false) fig[2, 1] = buttongrid = GridLayout(ax.scene, tellwidth=false)
btn_step = Button(buttongrid[1, 1], width=50, label="Step") btn_step = Button(buttongrid[1, 1], width=60, label="Step")
btn_start = Button(buttongrid[1, 2], width=50, label=run_label) btn_start = Button(buttongrid[1, 2], width=60, label=run_label)
btn_reset = Button(buttongrid[1, 3], width=50, label="Reset") btn_reset = Button(buttongrid[1, 3], width=60, label="Reset")
slidergrid = SliderGrid(fig[3, 1], (label="Speed", range=1:1:100, format="{}x", width=350, startvalue=stepsize[])) btn_Jaguar = Button(buttongrid[1, 4], width=60, label="Jaguar")
slidergrid = SliderGrid(fig[3, 1], (label="Speed", range=1:100,
format="{}x", width=350,
startvalue=stepsize[]))
speed_slider = slidergrid.sliders[1].value speed_slider = slidergrid.sliders[1].value
gh[1, 2] = textboxgrid = GridLayout(ax.scene, tellwidth=false) gh[1, 2] = textboxgrid = GridLayout(ax.scene, tellwidth=false)
rowsize!(gh, 1, Relative(1.0)) rowsize!(gh, 1, Relative(1.0))
param_box!(textboxgrid, 1, "Du", Du) param_box!(textboxgrid, 1, "Du", Du)
param_box!(textboxgrid, 2, "Dv", Dv) param_box!(textboxgrid, 2, "Dv", Dv)
param_box!(textboxgrid, 3, "Feed", F) param_box!(textboxgrid, 3, "Feed", F)
param_box!(textboxgrid, 4, "kill", k) param_box!(textboxgrid, 4, "kill", k)
# Timer and state for animation
running = Observable(false)
running = Observable(false)
on(running) do r on(running) do r
run_label[] = r ? "Pause" : "Run" run_label[] = r ? "Pause" : "Run"
end end
on(speed_slider) do s on(speed_slider) do s
try stepsize[] = s
stepsize[] = s println("Changed stepsize to $s")
println("Changed stepsize to $s")
catch
@warn "Invalid input for $s"
end
end end
# Button Listeners
on(btn_step.clicks) do _ on(btn_step.clicks) do _
multi_step!((U, V), stepsize[], heat_obs, params_obs) multi_step!((U, V), stepsize[], heat_obs, params_obs)
end end
on(btn_start.clicks) do _ on(btn_start.clicks) do _
running[] = !running[] running[] = !running[]
if running[]
@async while running[]
multi_step!((U, V), stepsize[], heat_obs, params_obs)
sleep(0.0015)
end
end
end end
on(btn_start.clicks) do _ on(btn_Jaguar.clicks) do _
@async while running[] Du[] = 0.142
multi_step!((U, V), stepsize[], heat_obs, params_obs) Dv[] = 0.078
sleep(0.0015) # ~20 FPS F[] = 0.0617
end k[] = 0.062
U .= 1.0
V .= 0.0
starting_points!(U, V)
heat_obs[] = copy(U)
end end
on(btn_reset.clicks) do _ on(btn_reset.clicks) do _
running[] = false running[] = false
reset!(U, V, heat_obs) reset!(U, V, heat_obs)
end end
on(spoint) do pt on(spoint) do pt
r = 5 r = 5
@ -138,7 +179,6 @@ function build_ui(U, V, Du, Dv, F, k, params_obs, heat_obs)
return return
end end
x, y = pt x, y = pt
println("params_obs[].N, ", params_obs[].N)
i, j = coord_to_index(x, y, params_obs[].N) i, j = coord_to_index(x, y, params_obs[].N)
# get corners of square that will get filled with concentration # get corners of square that will get filled with concentration
@ -156,6 +196,5 @@ function build_ui(U, V, Du, Dv, F, k, params_obs, heat_obs)
return fig return fig
end end
export step_through_solution, build_ui export step_through_solution, build_ui
end end