Switch zwischen FHN und GrayScott einbauen #5

Merged
2211567 merged 14 commits from feat/common_ui into main 2025-06-17 09:56:58 +02:00
6 changed files with 135 additions and 88 deletions

View File

@ -2,7 +2,7 @@
julia_version = "1.11.5"
manifest_format = "2.0"
project_hash = "5fd84347cd356de5b819aa3ea793fa63c45180e4"
project_hash = "b5f5f0b50b1e0b7dd05bb93e0b1bb03fea235d53"
[[deps.ADTypes]]
git-tree-sha1 = "e2478490447631aedba0823d4d7a80b2cc8cdb32"
@ -635,9 +635,9 @@ uuid = "4e289a0a-7415-4d19-859d-a7e5c4648b56"
version = "1.0.5"
[[deps.EnzymeCore]]
git-tree-sha1 = "7d7822a643c33bbff4eab9c87ca8459d7c688db0"
git-tree-sha1 = "8272a687bca7b5c601c0c24fc0c71bff10aafdfd"
uuid = "f151be2c-9106-41f4-ab19-57ee4f262869"
version = "0.8.11"
version = "0.8.12"
weakdeps = ["Adapt"]
[deps.EnzymeCore.extensions]
@ -1419,9 +1419,9 @@ version = "1.11.0"
[[deps.MathTeXEngine]]
deps = ["AbstractTrees", "Automa", "DataStructures", "FreeTypeAbstraction", "GeometryBasics", "LaTeXStrings", "REPL", "RelocatableFolders", "UnicodeFun"]
git-tree-sha1 = "31a99cb7537f812e1d6be893a71804c35979f1be"
git-tree-sha1 = "6e64d2321257cc52f47e193407d0659ea1b2b431"
uuid = "0a4f8689-d25c-4efe-a92b-7142dfc1aa53"
version = "0.6.4"
version = "0.6.5"
[[deps.MatrixFactorizations]]
deps = ["ArrayLayouts", "LinearAlgebra", "Printf", "Random"]
@ -2784,9 +2784,9 @@ version = "2.0.3+0"
[[deps.libpng_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Zlib_jll"]
git-tree-sha1 = "002748401f7b520273e2b506f61cab95d4701ccf"
git-tree-sha1 = "cd155272a3738da6db765745b89e466fa64d0830"
uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f"
version = "1.6.48+0"
version = "1.6.49+0"
[[deps.libsixel_jll]]
deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "libpng_jll"]

View File

@ -1,58 +1,34 @@
include("../src/utils/constants.jl")
#include("../src/fhn_solver.jl")
include("../src/gray_scott_solver.jl")
include("../src/visualization.jl")
using Observables
using GLMakie
using .Constants
using .Visualization.Constants

wieso hast du hier Visualization vor das Constants geschrieben? Macht es nicht mehr Sinn Constants selbst zu importieren? Oder bei Namenskonflikten explizit das Constants Modul anzugeben. Mit dem Doppelnamen sieht es nicht so schön aus finde ich

wieso hast du hier Visualization vor das Constants geschrieben? Macht es nicht mehr Sinn Constants selbst zu importieren? Oder bei Namenskonflikten explizit das Constants Modul anzugeben. Mit dem Doppelnamen sieht es nicht so schön aus finde ich

Weil Julia leider unterscheidet zwischen Main.Constants und Visualization.Constant

Weil Julia leider unterscheidet zwischen Main.Constants und Visualization.Constant
using .Visualization
# GSParams
# GSParams AND FHNParams
N = 128
dx = 1.0
Du, Dv = Observable(0.16), Observable(0.08)
F, k = Observable(0.060), Observable(0.062)
param_observables = (
Du=Du,
Dv=Dv,
F=F,
k=k,
params = (
N=Observable(128),
dx=Observable(1.0),
Du=Observable(0.16),
Dv=Observable(0.08),
F=Observable(0.060),
k=Observable(0.062),
ϵ=Observable(0.05),
a=Observable(0.7),
b=Observable(0.8)
)
"""
# FHNParams
N = 128
dx = 1.0
Du, Dv = Observable(0.016), Observable(0.1)
ϵ, a, b = Observable(0.1), Observable(0.5), Observable(0.9)
param_observables = (
Du=Du,
Dv=Dv,
ϵ=ϵ,
a=a,
b=b
)
"""
params_obs = Observable(Constants.GSParams(N, dx, Du[], Dv[], F[], k[]))
#params_obs = Observable(FHNParams(N=N, dx=dx, Du=Du[], Dv=Dv[], ϵ=ϵ[], a=a[], b=b[]))
lift(Du, Dv, F, k) do u, v, f, ki
params_obs[] = GSParams(N, dx, u, v, f, ki)
params_obs = Observable{Constants.CombinedPDEParams}(CombinedPDEParams(N, dx, params.Du[], params.Dv[], params.F[], params.k[], params.ϵ[], params.a[], params.b[]))
lift(params.N, params.dx, params.Du, params.Dv, params.F, params.k, params.ϵ, params.a, params.b) do N, dx, Du, Dv, F, k, ϵ, a, b
params_obs[] = CombinedPDEParams(N, dx, Du, Dv, F, k, ϵ, a, b)
end
"""
lift(Du, Dv, ϵ, a, b) do d_u, d_v, eps, aa, bb
params_obs[] = FHNParams(N=N, dx=dx, Du=d_u, Dv=d_v, ϵ=eps, a=aa, b=bb)
end
"""
U = ones(N, N)
V = zeros(N, N)
heat_obs = Observable(U)
fig = build_ui(U, V, param_observables, params_obs, heat_obs)
fig = build_ui(U, V, params, params_obs, heat_obs)
display(fig)

View File

@ -1,3 +1,5 @@
module FHNSolver

Für die beiden Module FHN und GrayScott, fänd ichs eig nicer, wenn wir multi dispatching implementieren könnten. Also dass wir einfach die step! Methode. Und je nachdem was für ein params_obs man reingibt, verhält sie sich anders. Also wenn es ein GSParams ist oder ein FHNParams.
step!(p::GSParams...)
step!(p::FHNParams...)
Wenn das aber zu schwierig ist, weil es diesen switch gibt, dann lassen wir es so. Wäre halt der more juliane way I guess

Für die beiden Module FHN und GrayScott, fänd ichs eig nicer, wenn wir multi dispatching implementieren könnten. Also dass wir einfach die step! Methode. Und je nachdem was für ein params_obs man reingibt, verhält sie sich anders. Also wenn es ein GSParams ist oder ein FHNParams. step!(p::GSParams...) step!(p::FHNParams...) Wenn das aber zu schwierig ist, weil es diesen switch gibt, dann lassen wir es so. Wäre halt der more juliane way I guess

Ich setz mich mal dran

Ich setz mich mal dran
include("utils/laplacian.jl")
using DifferentialEquations
@ -45,7 +47,8 @@ function fhn!(du, u, p::FHNParams, t)
du .= vcat(vec(fu_full), vec(fv_full))
end
function step!(U, V, params_obs::Observable; dx=1)
function step_fhn!(U, V, params_obs::Observable; dx=1)
"""
p = params_obs[]
# Flatten initial condition (activation u, recovery v)
@ -65,11 +68,24 @@ function step!(U, V, params_obs::Observable; dx=1)
U .= u_new
V .= v_new
return U
"""
params = params_obs[]
Δu = laplacian(U, params.dx)
Δv = laplacian(V, params.dx)
u = U[2:end-1, 2:end-1]
v = V[2:end-1, 2:end-1]
fu = params.Du .* Δu .+ u .- (u .^ 3) ./ 3 .- v
fv = params.Dv .* Δv .+ params.ϵ .* (u .+ params.a .- params.b .* v)
U[2:end-1, 2:end-1] .+= 0.1 .* fu
V[2:end-1, 2:end-1] .+= 0.1 .* fv
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 # Module end

View File

@ -1,3 +1,5 @@
module GrayScottSolver
include("utils/constants.jl")
include("utils/laplacian.jl")
@ -6,7 +8,7 @@ using Observables
using .Constants
using .Laplacian
function step!(U, V, params_obs::Observable; dx=1)
function step_gray_scott!(U, V, params_obs::Observable; dx=1)
# Extract parameters
p = params_obs[]
Du, Dv = p.Du, p.Dv
@ -42,8 +44,5 @@ function step!(U, V, params_obs::Observable; dx=1)
return U # for visualization
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 # Module end

View File

@ -10,11 +10,6 @@ struct FHNParams <: PDEParams
ϵ::Float64
a::Float64
b::Float64
# Inner constructor that takes keyword arguments
# The semicolon ';' separates positional arguments from keyword arguments
FHNParams(; N::Int, dx::Float64, Du::Float64, Dv::Float64, ϵ::Float64, a::Float64, b::Float64) =
new(N, dx, Du, Dv, ϵ, a, b)
end
struct GSParams <: PDEParams
@ -27,6 +22,18 @@ struct GSParams <: PDEParams
end
export PDEParams, FHNParams, GSParams
struct CombinedPDEParams <: PDEParams
N::Int
dx::Float64
Du::Float64
Dv::Float64
F::Float64
k::Float64
ϵ::Float64
a::Float64
b::Float64
end
export PDEParams, FHNParams, GSParams, CombinedPDEParams
end # module Constants

View File

@ -1,8 +1,18 @@
module Visualization
include("gray_scott_solver.jl")
#include("fhn_solver.jl")
using GLMakie, Observables, Makie
include("../src/utils/constants.jl")
include("gray_scott_solver.jl")
include("fhn_solver.jl")
using Observables, Makie, GLMakie
using .Constants
using .GrayScottSolver: step_gray_scott!
using .FHNSolver: step_fhn!
const STEP_FUNCTIONS = Dict(
:gray_scott => GrayScottSolver.step_gray_scott!,
:fhn => FHNSolver.step_fhn!
)
function coord_to_index(x, y, N)
ix = clamp(round(Int, x), 1, N)
@ -34,9 +44,11 @@ function reset!(U, V, heat_obs)
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[])")
function param_box!(grid, row, labeltxt, observable::Observable; col=1)
label_cell = 2 * col - 1
textbox_cell = 2 * col
Label(grid[row, label_cell], labeltxt)
box = Textbox(grid[row, textbox_cell], validator=Float64, width=50, placeholder=labeltxt, stored_string="$(observable[])")
on(box.stored_string) do s
try
observable[] = parse(Float64, s)
@ -45,13 +57,27 @@ function param_box!(grid, row, labeltxt, observable::Observable)
@warn "Invalid input for $labeltxt: $s"
end
end
on(observable) do val
box.displayed_string[] = string(val)
end
end
function multi_step!(state, n_steps, heat_obs::Observable, params_obs::Observable; step_method=step_gray_scott!, dx=1)
for _ in 1:n_steps
heat_obs[] = step_method(state[1], state[2], params_obs; dx=1)
end
end
function build_ui(U, V, param_obs_map::NamedTuple, params_obs, heat_obs)
reset!(U, V, heat_obs)
fig = Figure(size=(800, 800))
gh = GridLayout(fig[1, 1])
ax = Axis(gh[1, 1])
dropdown = Menu(fig, options=collect(zip(["Gray-Scott", "FHN"], [:gray_scott, :fhn])))
gh[1, 1] = dropdown
ax = Axis(gh[2, 1])
hm = Makie.heatmap!(ax, heat_obs, colormap=:viridis)
deactivate_interaction!(ax, :rectanglezoom)
ax.aspect = DataAspect()
@ -59,33 +85,39 @@ function build_ui(U, V, param_obs_map::NamedTuple, params_obs, heat_obs)
run_label = Observable{String}("Run")
stepsize = Observable(30)
spoint = select_point(ax.scene)
step_method = Observable{Function}(step_gray_scott!)
# 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)
2211567 marked this conversation as resolved
slidergrid = SliderGrid(fig[3, 1], (label="Speed", range=1:1:100, format="{}x", width=350, startvalue=stepsize[]), tellwidth=false)

in Zeile 92 tellwidth=false dazu

slidergrid = SliderGrid(fig[3, 1], (label="Speed", range=1:1:100, format="{}x", width=350, startvalue=stepsize[]), tellwidth=false) in Zeile 92 tellwidth=false dazu
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[]))
slidergrid = SliderGrid(fig[3, 1], (label="Speed", range=1:1:100, format="{}x", width=350, startvalue=stepsize[]), tellwidth=false)
speed_slider = slidergrid.sliders[1].value
# place all the parameter boxes
gh[2, 2] = textboxgrid = GridLayout(ax.scene, tellwidth=false)
gh[1, 2] = textboxgrid = GridLayout(ax.scene, tellwidth=false)
rowsize!(gh, 1, Relative(1.0))
param_box!(textboxgrid, 1, "Du", param_obs_map.Du, col=1)
param_box!(textboxgrid, 2, "Dv", param_obs_map.Dv, col=1)
param_box!(textboxgrid, 3, "Feed", param_obs_map.F, col=1)
param_box!(textboxgrid, 4, "Kill", param_obs_map.k, col=1)
# FHN column (col 2)
param_box!(textboxgrid, 1, "Du", param_obs_map.Du, col=2)
param_box!(textboxgrid, 2, "Dv", param_obs_map.Dv, col=2)
param_box!(textboxgrid, 3, "ϵ", param_obs_map.ϵ, col=2)
param_box!(textboxgrid, 4, "a", param_obs_map.a, col=2)
param_box!(textboxgrid, 5, "b", param_obs_map.b, col=2)
param_box!(textboxgrid, 1, "Du", param_obs_map.Du)
param_box!(textboxgrid, 2, "Dv", param_obs_map.Dv)
if haskey(param_obs_map, :F)
param_box!(textboxgrid, 3, "Feed", param_obs_map.F)
param_box!(textboxgrid, 4, "kill", param_obs_map.k)
elseif haskey(param_obs_map, )
param_box!(textboxgrid, 3, "ϵ", param_obs_map.ϵ)
param_box!(textboxgrid, 4, "a", param_obs_map.a)
param_box!(textboxgrid, 5, "b", param_obs_map.b)
rowsize!(gh, 1, Fixed(50)) # small row for the menu
rowsize!(gh, 2, Relative(1.0))
for c in 1:4
colsize!(textboxgrid, c, Relative(1.0 / 4.0))
end
# Events ##############################################################
# Timer and state for animation
running = Observable(false)
@ -102,7 +134,7 @@ function build_ui(U, V, param_obs_map::NamedTuple, params_obs, heat_obs)
end
# Button Listeners
on(btn_step.clicks) do _
multi_step!((U, V), stepsize[], heat_obs, params_obs)
multi_step!((U, V), stepsize[], heat_obs, params_obs; step_method=step_method[])
end
on(btn_start.clicks) do _
@ -111,7 +143,7 @@ function build_ui(U, V, param_obs_map::NamedTuple, params_obs, heat_obs)
on(btn_start.clicks) do _
@async while running[]
multi_step!((U, V), stepsize[], heat_obs, params_obs)
multi_step!((U, V), stepsize[] * 5, heat_obs, params_obs; step_method=step_method[])
sleep(0.0015) # ~20 FPS
end
end
@ -127,7 +159,6 @@ function build_ui(U, V, param_obs_map::NamedTuple, params_obs, heat_obs)
return
end
x, y = pt
println("params_obs[].N, ", params_obs[].N)
i, j = coord_to_index(x, y, params_obs[].N)
# get corners of square that will get filled with concentration
@ -142,6 +173,24 @@ function build_ui(U, V, param_obs_map::NamedTuple, params_obs, heat_obs)
heat_obs[] = copy(U)
end
on(dropdown.selection) do sel
if sel isa Tuple
label, selected_model = sel
else
selected_model = sel
label = string(sel)
end
@info "Selected model: $label$selected_model"
if haskey(STEP_FUNCTIONS, selected_model)
step_method[] = STEP_FUNCTIONS[selected_model]
else
@warn "Unknown model selected: $selected_model"
end
end
return fig
end