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" julia_version = "1.11.5"
manifest_format = "2.0" manifest_format = "2.0"
project_hash = "5fd84347cd356de5b819aa3ea793fa63c45180e4" project_hash = "b5f5f0b50b1e0b7dd05bb93e0b1bb03fea235d53"
[[deps.ADTypes]] [[deps.ADTypes]]
git-tree-sha1 = "e2478490447631aedba0823d4d7a80b2cc8cdb32" git-tree-sha1 = "e2478490447631aedba0823d4d7a80b2cc8cdb32"
@ -635,9 +635,9 @@ uuid = "4e289a0a-7415-4d19-859d-a7e5c4648b56"
version = "1.0.5" version = "1.0.5"
[[deps.EnzymeCore]] [[deps.EnzymeCore]]
git-tree-sha1 = "7d7822a643c33bbff4eab9c87ca8459d7c688db0" git-tree-sha1 = "8272a687bca7b5c601c0c24fc0c71bff10aafdfd"
uuid = "f151be2c-9106-41f4-ab19-57ee4f262869" uuid = "f151be2c-9106-41f4-ab19-57ee4f262869"
version = "0.8.11" version = "0.8.12"
weakdeps = ["Adapt"] weakdeps = ["Adapt"]
[deps.EnzymeCore.extensions] [deps.EnzymeCore.extensions]
@ -1419,9 +1419,9 @@ version = "1.11.0"
[[deps.MathTeXEngine]] [[deps.MathTeXEngine]]
deps = ["AbstractTrees", "Automa", "DataStructures", "FreeTypeAbstraction", "GeometryBasics", "LaTeXStrings", "REPL", "RelocatableFolders", "UnicodeFun"] deps = ["AbstractTrees", "Automa", "DataStructures", "FreeTypeAbstraction", "GeometryBasics", "LaTeXStrings", "REPL", "RelocatableFolders", "UnicodeFun"]
git-tree-sha1 = "31a99cb7537f812e1d6be893a71804c35979f1be" git-tree-sha1 = "6e64d2321257cc52f47e193407d0659ea1b2b431"
uuid = "0a4f8689-d25c-4efe-a92b-7142dfc1aa53" uuid = "0a4f8689-d25c-4efe-a92b-7142dfc1aa53"
version = "0.6.4" version = "0.6.5"
[[deps.MatrixFactorizations]] [[deps.MatrixFactorizations]]
deps = ["ArrayLayouts", "LinearAlgebra", "Printf", "Random"] deps = ["ArrayLayouts", "LinearAlgebra", "Printf", "Random"]
@ -2784,9 +2784,9 @@ version = "2.0.3+0"
[[deps.libpng_jll]] [[deps.libpng_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Zlib_jll"] deps = ["Artifacts", "JLLWrappers", "Libdl", "Zlib_jll"]
git-tree-sha1 = "002748401f7b520273e2b506f61cab95d4701ccf" git-tree-sha1 = "cd155272a3738da6db765745b89e466fa64d0830"
uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f" uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f"
version = "1.6.48+0" version = "1.6.49+0"
[[deps.libsixel_jll]] [[deps.libsixel_jll]]
deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "libpng_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") include("../src/visualization.jl")
using Observables using Observables
using GLMakie 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 using .Visualization
# GSParams # GSParams AND FHNParams
N = 128 N = 128
dx = 1.0 dx = 1.0
Du, Dv = Observable(0.16), Observable(0.08) params = (
F, k = Observable(0.060), Observable(0.062) N=Observable(128),
dx=Observable(1.0),
param_observables = ( Du=Observable(0.16),
Du=Du, Dv=Observable(0.08),
Dv=Dv, F=Observable(0.060),
F=F, k=Observable(0.062),
k=k, ϵ=Observable(0.05),
a=Observable(0.7),
b=Observable(0.8)
) )
""" params_obs = Observable{Constants.CombinedPDEParams}(CombinedPDEParams(N, dx, params.Du[], params.Dv[], params.F[], params.k[], params.ϵ[], params.a[], params.b[]))
# FHNParams 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
N = 128 params_obs[] = CombinedPDEParams(N, dx, Du, Dv, F, k, ϵ, a, b)
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)
end 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) U = ones(N, N)
V = zeros(N, N) V = zeros(N, N)
heat_obs = Observable(U) 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) 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") include("utils/laplacian.jl")
using DifferentialEquations using DifferentialEquations
@ -45,7 +47,8 @@ function fhn!(du, u, p::FHNParams, t)
du .= vcat(vec(fu_full), vec(fv_full)) du .= vcat(vec(fu_full), vec(fv_full))
end end
function step!(U, V, params_obs::Observable; dx=1) function step_fhn!(U, V, params_obs::Observable; dx=1)
"""
p = params_obs[] p = params_obs[]
# Flatten initial condition (activation u, recovery v) # Flatten initial condition (activation u, recovery v)
@ -65,11 +68,24 @@ function step!(U, V, params_obs::Observable; dx=1)
U .= u_new U .= u_new
V .= v_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 return U
end end
function multi_step!(state, n_steps, heat_obs::Observable, params_obs::Observable; dx=1) end # Module end
for _ in 1:n_steps
heat_obs[] = step!(state[1], state[2], params_obs; dx=1)
end
end

View File

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

View File

@ -10,11 +10,6 @@ struct FHNParams <: PDEParams
ϵ::Float64 ϵ::Float64
a::Float64 a::Float64
b::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 end
struct GSParams <: PDEParams struct GSParams <: PDEParams
@ -27,6 +22,18 @@ struct GSParams <: PDEParams
end 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 end # module Constants

View File

@ -1,8 +1,18 @@
module Visualization 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) function coord_to_index(x, y, N)
ix = clamp(round(Int, x), 1, N) ix = clamp(round(Int, x), 1, N)
@ -34,9 +44,11 @@ function reset!(U, V, heat_obs)
heat_obs[] = copy(U) heat_obs[] = copy(U)
end end
function param_box!(grid, row, labeltxt, observable::Observable) function param_box!(grid, row, labeltxt, observable::Observable; col=1)
Label(grid[row, 1], labeltxt) label_cell = 2 * col - 1
box = Textbox(grid[row, 2], validator=Float64, width=50, placeholder=labeltxt, stored_string="$(observable[])") 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 on(box.stored_string) do s
try try
observable[] = parse(Float64, s) observable[] = parse(Float64, s)
@ -45,13 +57,27 @@ function param_box!(grid, row, labeltxt, observable::Observable)
@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
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 end
function build_ui(U, V, param_obs_map::NamedTuple, params_obs, heat_obs) function build_ui(U, V, param_obs_map::NamedTuple, params_obs, heat_obs)
reset!(U, V, heat_obs) reset!(U, V, 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]) 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) hm = Makie.heatmap!(ax, heat_obs, colormap=:viridis)
deactivate_interaction!(ax, :rectanglezoom) deactivate_interaction!(ax, :rectanglezoom)
ax.aspect = DataAspect() 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") run_label = Observable{String}("Run")
stepsize = Observable(30) stepsize = Observable(30)
spoint = select_point(ax.scene) spoint = select_point(ax.scene)
step_method = Observable{Function}(step_gray_scott!)
# 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=50, label="Step")
btn_start = Button(buttongrid[1, 2], width=50, label=run_label) 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") 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 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) param_box!(textboxgrid, 1, "Du", param_obs_map.Du, col=1)
rowsize!(gh, 1, Relative(1.0)) 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) rowsize!(gh, 1, Fixed(50)) # small row for the menu
param_box!(textboxgrid, 2, "Dv", param_obs_map.Dv) rowsize!(gh, 2, Relative(1.0))
for c in 1:4
if haskey(param_obs_map, :F) colsize!(textboxgrid, c, Relative(1.0 / 4.0))
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)
end end
# Events ##############################################################
# Timer and state for animation # Timer and state for animation
running = Observable(false) running = Observable(false)
@ -102,7 +134,7 @@ function build_ui(U, V, param_obs_map::NamedTuple, params_obs, heat_obs)
end end
# Button Listeners # 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; step_method=step_method[])
end end
on(btn_start.clicks) do _ 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 _ on(btn_start.clicks) do _
@async while running[] @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 sleep(0.0015) # ~20 FPS
end end
end end
@ -127,7 +159,6 @@ function build_ui(U, V, param_obs_map::NamedTuple, 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
@ -142,6 +173,24 @@ function build_ui(U, V, param_obs_map::NamedTuple, params_obs, heat_obs)
heat_obs[] = copy(U) heat_obs[] = copy(U)
end 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 return fig
end end