From 1dc067805f5eff87be8f7f31466c5f269d4223b1 Mon Sep 17 00:00:00 2001 From: "Craig M. Hamel" Date: Wed, 22 Apr 2026 04:03:09 -0400 Subject: [PATCH] sources now use a vector of functions rather than a namedtuple. --- Project.toml | 6 +- src/AppTools.jl | 33 ++++++++++- src/Parameters.jl | 5 +- src/bcs/NeumannBCs.jl | 9 --- src/bcs/Sources.jl | 126 ++++++++++++++++++++++++------------------ test/TestAppTools.jl | 7 ++- 6 files changed, 117 insertions(+), 69 deletions(-) diff --git a/Project.toml b/Project.toml index 2a337113..0581ffc0 100644 --- a/Project.toml +++ b/Project.toml @@ -41,10 +41,10 @@ MetisExt = "Metis" PartitionedArraysExt = "PartitionedArrays" [compat] +AMDGPU = "2" AbaqusReader = "0.2.7" AcceleratedKernels = "0.4" Adapt = "4" -AMDGPU = "2" Atomix = "1" BlockArrays = "1" CUDA = "5, 6" @@ -53,6 +53,7 @@ Exodus = "0.14" ForwardDiff = "1" GPUArrays = "11" Gmsh = "0.3" +JuliaC = "0.3" KernelAbstractions = "0.9" Krylov = "0.10" LinearAlgebra = "1" @@ -72,9 +73,10 @@ julia = "1" [extras] Gmsh = "705231aa-382f-11e9-3f0c-b7cb4346fdeb" +JuliaC = "acedd4c2-ced6-4a15-accc-2607eb759ba2" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" TestItemRunner = "f8b46487-2199-4994-9208-9a1283c18c0a" TestItems = "1c621080-faea-4a02-84b6-bbd5e436b8fe" [targets] -test = ["Gmsh", "Test", "TestItemRunner", "TestItems"] +test = ["Gmsh", "JuliaC", "Test", "TestItemRunner", "TestItems"] diff --git a/src/AppTools.jl b/src/AppTools.jl index 2f239bcd..82dc3616 100644 --- a/src/AppTools.jl +++ b/src/AppTools.jl @@ -5,6 +5,8 @@ trim mode for small binaries """ module AppTools +export App + import ..DirichletBC import ..Expressions.ExpressionFunction import ..ExodusMesh @@ -622,11 +624,15 @@ end ######################################################################### # tools to create new projects ######################################################################### +function build_app(; path::String = pwd()) + run(`julia --project=$path $(joinpath(path, "build.jl"))`) +end + function generate_app( name::String; backends::Vector{String} = ["cpu"], directory::String = pwd(), - trim::Bool = false + trim::Bool = true ) path = joinpath(directory, name) @info "Creating new FiniteElementContainers app at $path" @@ -686,9 +692,20 @@ function generate_app( # create basic src file open(joinpath(path, "src", "$(name).jl"), "w") do io src = """ + import FiniteElementContainers as FEC import FiniteElementContainers.AppTools as AT + using Exodus + using FiniteElementContainers + + function app_main(ARGS::Vector{String}) + app = AT.App(\"$(name)\") + AT.add_cli_arg(app, \"--backend\"; default = \"cpu\") + sim = AT.setup(app, ARGS) + println(sim.log_file.io, "Setup complete") + end function @main(ARGS::Vector{String}) + app_main(ARGS) return 0 end """ @@ -736,4 +753,18 @@ function generate_app( end end +function run_app( + args::Vector{String}; + exe_name::Union{Nothing, String} = nothing, + path::String = pwd() +) + # first figure out exe name + if exe_name === nothing + data = TOML.parsefile(joinpath(path, "Project.toml")) + exe_name = lowercase(data["name"]) + end + run_cmds = pushfirst!(args, joinpath(path, "build", "bin", exe_name)) + run(Cmd(run_cmds)) +end + end # module AppTools diff --git a/src/Parameters.jl b/src/Parameters.jl index b733853e..9bc53f39 100644 --- a/src/Parameters.jl +++ b/src/Parameters.jl @@ -14,11 +14,12 @@ struct Parameters{ RT <: Number, IV <: AbstractVector{<:Integer}, RV <: AbstractVector{RT}, + RM <: AbstractMatrix, ICFuncs <: AbstractVector, DBCFuncs <: AbstractVector, + SrcFuncs <: AbstractVector, NBCs, RBCs, - SRCs, Phys, Props, Coords <: AbstractField, @@ -28,7 +29,7 @@ struct Parameters{ dirichlet_bcs::DirichletBCs{DBCFuncs, IV, RV} neumann_bcs::NBCs robin_bcs::RBCs - sources::SRCs + sources::Sources{SrcFuncs, RM} times::TimeStepper{RT} physics::Phys properties::Props diff --git a/src/bcs/NeumannBCs.jl b/src/bcs/NeumannBCs.jl index 33398d2c..08102d3c 100644 --- a/src/bcs/NeumannBCs.jl +++ b/src/bcs/NeumannBCs.jl @@ -19,15 +19,6 @@ struct NeumannBC{F} <: AbstractBC{F} end end -# """ -# $(TYPEDEF) -# $(TYPEDSIGNATURES) -# $(TYPEDFIELDS) -# """ -# function NeumannBC(var_name::String, func::Function, sset_name::String) -# return NeumannBC(Symbol(var_name), func, Symbol(sset_name)) -# end - """ $(TYPEDEF) $(TYPEDSIGNATURES) diff --git a/src/bcs/Sources.jl b/src/bcs/Sources.jl index 689228ae..edda4178 100644 --- a/src/bcs/Sources.jl +++ b/src/bcs/Sources.jl @@ -33,13 +33,19 @@ volume quadrature points for one block. and have assemblers just ping the correct block """ struct SourceContainer{ - RV <: AbstractArray{<:Union{<:Number, <:SVector}, 2} + RM <: AbstractArray{<:Union{<:Number, <:SVector}, 2} } - vals::RV # size (NQ_cell, nelem) of SVector{ND} + vals::RM # size (NQ_cell, nelem) of SVector{ND} is_constant::Bool # skip re-evaluation after first call initialized::Base.RefValue{Bool} + + function SourceContainer(vals::RM, is_constant, initialized) where RM + new{RM}(vals, is_constant, initialized) + end end +_vals_type(::Vector{SourceContainer{RM}}) where RM = RM + function Adapt.adapt_structure(to, source::SourceContainer) SourceContainer( adapt(to, source.vals), @@ -61,6 +67,14 @@ function _update_source_values!(vals, func, ref_fe, conns, coffset, X, t) end end +struct SourceFunction{F} <: AbstractBCFunction{F} + func::F +end + +function (f::SourceFunction)(x, t) + return f.func(x, t) +end + """ $(TYPEDEF) $(TYPEDSIGNATURES) @@ -68,62 +82,62 @@ $(TYPEDFIELDS) Collection of body force containers, one per specified body force entry. """ struct Sources{ - RV <: AbstractArray{<:Union{<:Number, <:SVector}, 2}, - SourceFuncs <: NamedTuple + SourceFuncs, + RM <: AbstractArray{<:Union{<:Number, <:SVector}, 2}, } <: AbstractBCs{SourceFuncs} source_block_ids::Vector{Int} # note this is the id order in FEC not the id in exodus source_block_names::Vector{String} - source_caches::Vector{SourceContainer{RV}} + source_caches::Vector{SourceContainer{RM}} source_funcs::SourceFuncs -end -function Sources(mesh, dof::DofManager, sources::Vector{Source}) - if length(sources) == 0 - return Sources(Int[], String[], SourceContainer{Matrix{Float64}}[], NamedTuple()) + function Sources{SourceFuncs, RM}( + source_block_ids, source_block_names, source_caches, source_funcs + ) where {SourceFuncs, RM} + new{SourceFuncs, RM}(source_block_ids, source_block_names, source_caches, source_funcs) end - fspace = function_space(dof) - ND = length(dof.var) - caches = [] - funcs = Function[] - source_block_ids = Int[] - source_block_names = String[] - for source in sources - block_name = source.block_name - # block_id = indexin([block_name], collect(keys(fspace.ref_fes))) - # @assert length(block_id) == 1 - # block_id = block_id[1] - block_id = findfirst(x -> x == block_name, mesh.element_block_names) - push!(source_block_ids, block_id) - push!(source_block_names, block_name) - NQ, NE = block_quadrature_size(fspace, block_id) - # conns = Connectivity([conns_full]) - # if ND == 1 - # vals = zeros(Float64, NQ, nelem) - # else - vals = zeros(SVector{ND, Float64}, NQ, NE) - # end - - # Detect constant: the is_constant flag is set by the caller (Carina) - # via the Source struct. For now, default to false (re-evaluate every step). - is_constant = false - - # push!(caches, SourceContainer(conns, ref_fe, vals, is_constant, Ref(false))) - push!(caches, SourceContainer(vals, is_constant, Ref(false))) - push!(funcs, source.func) - end + # TODO should we really allow for scalar funcs for this in the case of scalar variables? + function Sources(mesh, dof::DofManager, sources::Vector{Source}, ::Type{RT} = Float64) where RT <: Number + ND = size(dof, 1) + caches = SourceContainer{Matrix{SVector{ND, RT}}}[] + funcs = SourceFunction[] + if length(sources) == 0 + return Sources{typeof(funcs), Matrix{SVector{ND, RT}}}(Int[], String[], SourceContainer{Matrix{RT}}[], funcs) + end - # what happens if they're not all the same type? - caches = convert(Vector{typeof(caches[1])}, caches) + fspace = function_space(dof) + ND = length(dof.var) + source_block_ids = Int[] + source_block_names = String[] + for source in sources + block_name = source.block_name + block_id = findfirst(x -> x == block_name, mesh.element_block_names) + push!(source_block_ids, block_id) + push!(source_block_names, block_name) + NQ, NE = block_quadrature_size(fspace, block_id) + # conns = Connectivity([conns_full]) + # if ND == 1 + # vals = zeros(Float64, NQ, nelem) + # else + vals = zeros(SVector{ND, Float64}, NQ, NE) + # end + + # Detect constant: the is_constant flag is set by the caller (Carina) + # via the Source struct. For now, default to false (re-evaluate every step). + is_constant = false + + push!(caches, SourceContainer(vals, is_constant, Ref(false))) + push!(funcs, SourceFunction(source.func)) + end - syms = tuple(map(x -> Symbol("source_$x"), 1:length(caches))...) - return Sources( - source_block_ids, source_block_names, caches, - NamedTuple{syms}(tuple(funcs...)), - ) + # return Sources(source_block_ids, source_block_names, caches, funcs) + return Sources{typeof(funcs), Matrix{SVector{ND, RT}}}( + source_block_ids, source_block_names, caches, funcs + ) + end end -function Adapt.adapt(to, sources::Sources) +function Adapt.adapt(to, sources::Sources{SourceFuncs, RM}) where {SourceFuncs, RM} # needed due to failures on 1.10 and 1.11 if length(sources.source_caches) == 0 caches = Vector{SourceContainer{Matrix{Float64}}}(undef, 0) @@ -131,11 +145,11 @@ function Adapt.adapt(to, sources::Sources) caches = map(x -> adapt(to, x), sources.source_caches) end - return Sources( + return Sources{SourceFuncs, _vals_type(caches)}( sources.source_block_ids, sources.source_block_names, caches, - adapt(to, sources.source_funcs) + sources.source_funcs ) end @@ -151,15 +165,19 @@ end function update_source_values!(sources::Sources, assembler, X, t) fspace = function_space(assembler) - for (block_id, source, func) in zip(sources.source_block_ids, sources.source_caches, sources.source_funcs) - if source.is_constant && source.initialized[] + for n in axes(sources.source_block_ids, 1) + block_id = sources.source_block_ids[n] + cache = sources.source_caches[n] + if cache.is_constant && cache.initialized[] continue end - ref_fe = fspace.ref_fes[block_id] + + func = sources.source_funcs[n] + ref_fe = block_reference_element(fspace, block_id) conns_data = fspace.elem_conns.data coffset = fspace.elem_conns.offsets[block_id] - _update_source_values!(source.vals, func, ref_fe, conns_data, coffset, X, t) - source.initialized[] = true + _update_source_values!(cache.vals, func, ref_fe, conns_data, coffset, X, t) + cache.initialized[] = true end return nothing end diff --git a/test/TestAppTools.jl b/test/TestAppTools.jl index 9ffe1cf2..8c0287c9 100644 --- a/test/TestAppTools.jl +++ b/test/TestAppTools.jl @@ -120,14 +120,19 @@ end # sim = AT.setup(app, args) end -@testitem "AppTools - generate app" begin +@testitem "AppTools - generate/build/run app" begin import FiniteElementContainers.AppTools as AT + if isdir("MyApp/") + rm("MyApp/"; force = true, recursive = true) + end AT.generate_app("MyApp") @test isdir("MyApp") @test isfile("MyApp/build.jl") @test isfile("MyApp/Project.toml") @test isdir("MyApp/src") @test isfile("MyApp/src/MyApp.jl") + # TODO re-enable after future release + # AT.build_app(; path = "MyApp") rm("MyApp"; force = true, recursive = true) mkdir("app_dir")