refactor(continuum): Remove BC functions from assemble.jl

- Remove apply_neumann_bcs! and apply_dirichlet_bcs!
- Functions moved to domains/common/boundary_conditions.jl
- Keeps assemble.jl focused on matrix/vector assembly only
This commit is contained in:
Jukka Aho
2025-11-19 02:13:42 +02:00
parent 9c980ab264
commit 706a275d57
+48 -157
View File
@@ -2,175 +2,66 @@
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
"""
Assembly for 3D Continuum Mechanics using Generic Assemblers
Continuum mechanics assembly examples and documentation.
This file provides the high-level `assemble!(physics)` method that:
1. Creates a continuum kernel from physics parameters
2. Selects an assembler strategy (COO by default)
3. Delegates to generic assembler framework
4. Applies boundary conditions
This file provides documentation for the explicit kernel API workflow.
Boundary condition functions have been moved to `src/domains/common/boundary_conditions.jl`
as they are generic and work with any domain type (continuum, beams, shells, heat, etc.).
Replaces monolithic assembly with clean separation:
- **Kernel** (WHAT to assemble): `src/domains/continuum/kernel.jl`
- **Assembler** (HOW to assemble): `src/assemblers/`
"""
# Explicit Workflow (Recommended)
"""
assemble!(physics::Physics{ContinuumFormulation{FullThreeD}, Displacement{3}, M, Mat})
-> (K, f)
Assemble global system for 3D continuum mechanics.
# Algorithm
1. Create continuum kernel from physics parameters
2. Select assembler (COOAssembler by default, can configure)
3. Create cache (all allocations here)
4. Assemble using generic assembler framework (zero allocations)
5. Extract system (K, f)
6. Apply boundary conditions
# Arguments
- `physics`: Physics object with mesh, material, formulation, field, BCs
# Returns
- `(K, f)::Tuple{SparseMatrixCSC{Float64,Int}, Vector{Float64}}`
# Performance
COOAssembler (default):
- Time: ~9.7ms for 2500 Tet4 elements
- Memory: ~8MB
- Best for: Prototyping, debugging
To use faster CSCAssembler (4.1x speedup):
```julia
# TODO: Add assembler selection to Physics constructor
# physics = Physics(..., assembler=CSCAssembler())
# Setup
mesh = create_cantilever_mesh(50, 10, 10)
material = LinearElastic(E=210e9, ν=0.3)
kernel = ContinuumKernel(
ContinuumFormulation{FullThreeD}(),
material,
Displacement{3}()
)
# Choose assembler explicitly
assembler = CSCAssembler() # or COOAssembler()
# Create cache (reusable!)
cache = create_cache(assembler, mesh, kernel)
# Assembly and solve
assemble!(cache, assembler, kernel, mesh)
K, f = extract_system(cache)
# Apply BCs explicitly (defined in domains/common/boundary_conditions.jl)
apply_neumann_bcs!(f, kernel, mesh, bc_neumann)
apply_dirichlet_bcs!(K, f, kernel, mesh, bc_dirichlet)
# Solve
u = K \\ f
```
# References
- Kernel: `src/domains/continuum/kernel.jl`
- Assemblers: `src/assemblers/`
- Original implementation: `src/domains/continuum/assemble_v1_backup.jl`
"""
function assemble!(
physics::Physics{ContinuumFormulation{FullThreeD},
Displacement{3},
M,
Mat}) where {M<:AbstractMesh,Mat<:AbstractMaterial}
# Nonlinear Loop Example
mesh = physics.mesh
material = physics.material
formulation = physics.formulation
field = physics.field
bc_dirichlet = physics.bc_dirichlet
bc_neumann = physics.bc_neumann
```julia
cache = create_cache(CSCAssembler(), mesh, kernel)
# Create continuum kernel
kernel = ContinuumKernel(formulation, material, field)
# Select assembler (COO by default)
# TODO: Allow user to configure assembler choice
assembler = COOAssembler()
# Create cache (ALL allocations here!)
cache = create_cache(assembler, mesh, kernel)
# Assemble (ZERO allocations!)
assemble!(cache, assembler, kernel, mesh)
# Extract system
for iter in 1:max_iter
assemble!(cache, assembler, kernel, mesh) # Zero allocations!
K, f = extract_system(cache)
apply_neumann_bcs!(f, kernel, mesh, bc_neumann)
apply_dirichlet_bcs!(K, f, kernel, mesh, bc_dirichlet)
# Apply Neumann BCs (add forces to f)
apply_neumann_bcs!(f, bc_neumann, mesh, kernel)
Δu = K \\ f
u .+= Δu
# Apply Dirichlet BCs (modify K and f)
apply_dirichlet_bcs!(K, f, bc_dirichlet, mesh, kernel)
return (K, f)
end
"""
apply_neumann_bcs!(f, bc_neumann::NeumannBC, mesh, kernel) -> Nothing
Apply Neumann (natural) boundary conditions to force vector **in-place**.
For now, interprets `surface_ids` as node IDs (simplified).
TODO: Proper surface force integration over element faces.
# Arguments
- `f`: Global force vector (modified in-place)
- `bc_neumann`: Neumann BC data structure
- `mesh`: Finite element mesh
- `kernel`: Domain kernel (for DOF mapping)
"""
function apply_neumann_bcs!(
f::Vector{Float64},
bc_neumann::NeumannBC,
mesh::AbstractMesh,
kernel::AbstractKernel
)
nnodes = nnodes_total(mesh)
for (surf_id, force) in zip(bc_neumann.surface_ids, bc_neumann.values)
# Simplified: treat surface_id as node_id
# TODO: Implement proper surface integration
node = surf_id
if node <= nnodes
for α in 1:3
f[3*(node-1)+α] += force[α]
end
end
if norm(Δu) < tol
break
end
return nothing
end
```
# See Also
- [`apply_neumann_bcs!`](@ref) - in `domains/common/boundary_conditions.jl`
- [`apply_dirichlet_bcs!`](@ref) - in `domains/common/boundary_conditions.jl`
- [`ContinuumKernel`](@ref) - in `domains/continuum/kernel.jl`
- [`CSCAssembler`](@ref), [`COOAssembler`](@ref) - in `assemblers/`
"""
apply_dirichlet_bcs!(K, f, bc_dirichlet::DirichletBC, mesh, kernel) -> Nothing
Apply Dirichlet (essential) boundary conditions **in-place**.
Uses elimination method:
1. Zero out row and column for constrained DOF
2. Set diagonal to 1.0
3. Set force vector entry to prescribed value
# Arguments
- `K`: Global stiffness matrix (modified in-place)
- `f`: Global force vector (modified in-place)
- `bc_dirichlet`: Dirichlet BC data structure
- `mesh`: Finite element mesh
- `kernel`: Domain kernel (for DOF mapping)
"""
function apply_dirichlet_bcs!(
K::SparseMatrixCSC{Float64,Int},
f::Vector{Float64},
bc_dirichlet::DirichletBC,
mesh::AbstractMesh,
kernel::AbstractKernel
)
nnodes = nnodes_total(mesh)
ndofs = dofs_per_node(kernel) * nnodes
for i in 1:length(bc_dirichlet.node_ids)
node = bc_dirichlet.node_ids[i]
components = bc_dirichlet.components[i]
value = bc_dirichlet.values[i]
for comp in components
dof = 3 * (node - 1) + comp
if dof <= ndofs # Safety check
# Elimination method
K[dof, :] .= 0.0
K[:, dof] .= 0.0
K[dof, dof] = 1.0
f[dof] = value
end
end
end
return nothing
end