chore: remove obsolete and legacy files

Major cleanup: remove obsolete files that have been replaced or
moved to new locations. Files removed include legacy assembly
implementations, old API files, and deprecated test files.

Removed files:

Assemblers and assembly:
- src/assemblers/nodal_based.jl
- src/assemblers/nodal_cache.jl
- src/assemblers/node_based_coo.jl
- src/assembly/assembly.jl
- src/assembly/element_structures.jl
- src/assembly/framework.jl
- src/assembly/nodal_structures.jl
- src/assembly/problems.jl
- src/element_assembly_structures.jl
- src/nodal_assembly_structures.jl

Legacy API and structure files:
- src/beams/api.jl
- src/formulations/api.jl
- src/gpu_elasticity.jl
- src/io.jl
- src/materials_plasticity.jl
- src/postprocess_utils.jl
- src/preprocess.jl
- src/quadrature.jl
- src/readers.jl
- src/shells/api.jl
- src/trusses/api.jl

Elements and domains:
- src/domains/continuum/assemble_v2.jl
- src/elements/integrate.jl

Quadrature legacy files:
- src/quadrature/gauss_points.jl
- src/quadrature/integration.jl

Test files:
- test/runtests_new.jl
- test/runtests.jl.old
- test/test_problems_elasticity_assemble_3d_seg3.jl
This commit is contained in:
Jukka Aho
2025-12-12 22:45:49 +02:00
parent cba8d19c1c
commit ff3c26d2c3
28 changed files with 0 additions and 6419 deletions
-177
View File
@@ -1,177 +0,0 @@
# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
"""
Nodal-based assembly (future implementation).
Node-by-node assembly using inverse connectivity (node-to-elements map).
Natural for GPU parallelization (one thread per node).
**Performance**: Expected 2-10x speedup on GPU for large problems (> 100k nodes).
**Best for**: GPU acceleration, very large problems.
**Status**: Planned for future implementation.
"""
using SparseArrays
"""
assemble!(
cache::NodalCache,
assembler::NodalAssembler,
kernel::AbstractKernel,
mesh::AbstractMesh
) -> Nothing
Assemble global system using nodal traversal **in-place, zero allocations**.
**Status**: Not yet implemented. Placeholder for future GPU-based assembly.
# Algorithm (Planned)
1. Reset cache
2. Loop over nodes (parallelizable on GPU):
a. Get all elements touching this node
b. For each touching element:
- Compute full element stiffness (or use cached value)
- Extract only rows/columns for this node
- Accumulate to global system (atomic add on GPU)
# GPU Parallelization
Each node processed by one GPU thread:
```cuda
__global__ void assemble_nodal(nodes, elements, K, f) {
int node_id = blockIdx.x * blockDim.x + threadIdx.x;
if (node_id >= nnodes) return;
// Get touching elements
for (elem in touching_elements[node_id]) {
// Compute element contribution for this node
// Atomic add to K, f
}
}
```
# Arguments
- `cache`: Pre-allocated nodal cache
- `assembler`: Nodal assembler
- `kernel`: Domain kernel
- `mesh`: Finite element mesh
# Zero-Allocation Guarantee
All arrays pre-allocated. Atomic operations on GPU ensure thread-safety.
"""
function assemble!(
cache::NodalCache,
assembler::NodalAssembler,
kernel::AbstractKernel,
mesh::AbstractMesh
)
error("NodalAssembler not yet implemented. Use COOAssembler or CSCAssembler.")
# Planned implementation:
# 1. Reset cache
# 2. Loop over nodes
# 3. For each node, get touching elements from cache.node_to_elements
# 4. Accumulate contributions from all touching elements
# 5. Write to global K, f (atomic on GPU)
return nothing
end
# ============================================================================
# HELPER FUNCTIONS (for future implementation)
# ============================================================================
"""
create_cache(assembler::NodalAssembler, mesh::AbstractMesh, kernel::AbstractKernel) -> NodalCache
Create pre-allocated cache for nodal assembly.
Builds node-to-elements map (inverse connectivity) for efficient nodal traversal.
# Arguments
- `assembler`: Nodal assembler
- `mesh`: Finite element mesh
- `kernel`: Domain kernel
# Returns
- Pre-allocated nodal cache with inverse connectivity
"""
function create_cache(assembler::NodalAssembler, mesh::AbstractMesh, kernel::AbstractKernel)
return NodalCache(mesh, kernel)
end
"""
compute_node_contributions!(
node_cache::NodeCache,
element_cache::ElementCache,
node_id::Int,
kernel::AbstractKernel,
mesh::AbstractMesh,
node_to_elements::NodeToElementsMap
) -> Nothing
Compute contributions to this node from all touching elements **in-place**.
# Algorithm
1. Get all elements touching this node
2. For each element:
a. Compute full element stiffness (using `compute_element_stiffness!`)
b. Find local node index within element
c. Extract rows/columns corresponding to this node's DOFs
d. Accumulate to node contributions
# Arguments
- `node_cache`: Node workspace (output)
- `element_cache`: Element workspace (for kernel calls)
- `node_id`: Node index
- `kernel`: Domain kernel
- `mesh`: Finite element mesh
- `node_to_elements`: Inverse connectivity
# Returns
Nothing - writes to `node_cache` in-place.
"""
function compute_node_contributions!(
node_cache::NodeCache,
element_cache::ElementCache,
node_id::Int,
kernel::AbstractKernel,
mesh::AbstractMesh,
node_to_elements::NodeToElementsMap
)
error("compute_node_contributions! not yet implemented")
# Planned implementation:
# 1. Get touching elements: get_node_spider(node_to_elements, node_id)
# 2. For each element:
# - Compute element stiffness: compute_element_stiffness!(element_cache, ...)
# - Find local node index in element
# - Extract node DOF rows/columns from Ke, fe
# - Accumulate to node_cache
# 3. Return node_cache (contains all contributions for this node)
return nothing
end
# ============================================================================
# GPU KERNEL STUBS (for future CUDA implementation)
# ============================================================================
# """
# assemble_nodal_gpu!(K, f, mesh, kernel, node_to_elements)
#
# GPU kernel for nodal assembly.
#
# Launches one thread per node. Each thread:
# 1. Gets touching elements for its node
# 2. Computes contributions from all elements
# 3. Atomically adds to global K, f
#
# Requires CUDA.jl or similar GPU framework.
# """
# function assemble_nodal_gpu! end
-125
View File
@@ -1,125 +0,0 @@
# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
"""
Nodal cache for node-based assembly.
Nodal assembly iterates over nodes rather than elements. Each node assembles
contributions from all touching elements. This approach has advantages for:
- Contact mechanics (contact is inherently nodal)
- Domain decomposition (clear node ownership)
- Matrix-free operations (natural node-based matvec)
- Adaptive refinement (local node operations)
# Structure
Pre-builds node-to-elements map (inverse connectivity) once. During assembly,
each node visits all touching elements and accumulates contributions.
# Performance
Similar to CSC for standard problems. Better locality for nodal operations
like contact and matrix-free solvers.
# Use case
Best for problems with nodal phenomena (contact, nodal plasticity) or
matrix-free iterative solvers.
"""
using SparseArrays
"""
NodalCache <: AbstractAssemblerCache
Cache for nodal-based assembly.
Pre-allocates sparse matrix, force vector, and node-to-elements map.
Each node assembles contributions from all touching elements.
# Fields
- `K::SparseMatrixCSC{Float64,Int}`: Sparse matrix
- `f::Vector{Float64}`: Global force vector
- `node_cache::NodeCache`: Per-node workspace
- `element_cache::ElementCache`: Per-element workspace (for kernel calls)
- `node_to_elements::NodeToElementsMap`: Inverse connectivity
# Zero-Allocation Usage
```julia
cache = NodalCache(mesh, kernel)
fill!(cache)
assemble!(cache, assembler, kernel, mesh) # No allocations
K, f = extract_system(cache)
```
"""
mutable struct NodalCache <: AbstractAssemblerCache
K::SparseMatrixCSC{Float64,Int} # Sparse matrix
f::Vector{Float64} # Force vector
node_cache::NodeCache # Node workspace
element_cache::ElementCache # Element workspace
node_to_elements::NodeToElementsMap # Inverse connectivity
end
"""
NodalCache(mesh::AbstractMesh, kernel::AbstractKernel) -> NodalCache
Create pre-allocated nodal cache.
Builds node-to-elements map (inverse connectivity) for efficient nodal traversal.
# Arguments
- `mesh`: Finite element mesh
- `kernel`: Domain kernel defining DOF structure
# Returns
- `NodalCache` with pre-allocated workspace and inverse connectivity
"""
function NodalCache(mesh::AbstractMesh, kernel::AbstractKernel)
ndofs_per_node = dofs_per_node(kernel)
nnodes_mesh = nnodes_total(mesh)
ndofs = nnodes_mesh * ndofs_per_node
# Build sparsity pattern (same as CSC)
K = build_sparsity_pattern(mesh, kernel)
f = zeros(Float64, ndofs)
# Create caches
node_cache = create_node_cache(mesh, kernel)
element_cache = create_element_cache(mesh, kernel)
# Build inverse connectivity
node_to_elements = NodeToElementsMap(mesh)
return NodalCache(K, f, node_cache, element_cache, node_to_elements)
end
"""
reset!(cache::NodalCache)
Reset nodal cache for new assembly.
Zeros out matrix values and force vector.
**Zero allocations** - reuses existing arrays.
"""
function reset!(cache::NodalCache)
fill!(cache.K.nzval, 0.0)
fill!(cache.f, 0.0)
return nothing
end
"""
extract_system(cache::NodalCache) -> (K, f)
Extract global system from nodal cache.
Returns references to sparse matrix and force vector.
**Zero allocations** - no copying.
# Arguments
- `cache`: Nodal cache after assembly
# Returns
- `K`: Sparse matrix (reference, no copy)
- `f`: Force vector (reference, no copy)
"""
function extract_system(cache::NodalCache)
return cache.K, cache.f
end
-484
View File
@@ -1,484 +0,0 @@
# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
"""
Node-based COO assembly using block integration.
**NODAL ASSEMBLY PARADIGM**: Loop over nodes, not elements!
Each node:
1. Finds all elements touching it (via inverse connectivity)
2. For each touching element:
- Prepares element geometry once (PreparedElement)
- Computes only needed 3×3 blocks (compute_block!)
3. Scatters blocks to COO triplets
# Key Differences from Element-Based Assembly
**Element-Based (traditional):**
```julia
for element in elements
K_e = compute_element_stiffness(element) # Full N×N matrix of 3×3 blocks
scatter(K_e) # Scatter all entries
end
```
**Node-Based (this file):**
```julia
for node_i in nodes
for element in elements_touching(node_i)
prepared = prepare_element(element) # Geometry preprocessing
for node_j in element.nodes
K_ij = compute_block!(prepared, i, j) # Single 3×3 block
scatter(K_ij, i, j) # Scatter one block
end
end
end
```
# Advantages
1. **GPU-friendly**: One thread per node, no race conditions
2. **Contact-ready**: Contact is naturally node-based
3. **Matrix-free ready**: Can compute K*v without forming K
4. **Cache-friendly**: Reuses PreparedElement for multiple blocks
5. **Adaptive-ready**: Easy to refine/coarsen at node level
# Performance Expectations
- **CPU Single-thread**: ~1.5-2x slower than element-based (more kernel calls)
- **CPU Multi-thread**: ~1.5-2x faster (better parallelization)
- **GPU**: ~10-50x faster (massive parallelization, no atomics needed)
# References
- Golden standard: `docs/src/book/multigpu_nodal_assembly.md`
- PreparedElement: `src/domains/continuum/integration.jl`
- Block kernel: `src/domains/continuum/kernel.jl`
# Example
```julia
# Setup
mesh = create_cantilever_mesh(50, 10, 10)
material = LinearElastic(E=210e9, ν=0.3)
kernel = ContinuumKernel(
ContinuumFormulation{FullThreeD}(),
material,
Displacement{3}()
)
# Create node-based assembler and cache
assembler = NodeBasedCOOAssembler()
cache = create_cache(assembler, mesh, kernel)
# Assemble (zero allocations after warmup!)
assemble!(cache, assembler, kernel, mesh)
# Extract system
K, f = extract_system(cache)
# Solve
apply_dirichlet_bcs!(K, f, kernel, mesh, bc_dirichlet)
u = K \\ f
```
"""
using SparseArrays
using Tensors
"""
NodeBasedCOOCache
Pre-allocated cache for node-based COO assembly.
Similar to COOCache but includes inverse connectivity mapping.
# Fields
- `I::Vector{Int}`: Row indices (COO format)
- `J::Vector{Int}`: Column indices (COO format)
- `V::Vector{Float64}`: Values (COO format)
- `f::Vector{Float64}`: Global force vector
- `counter::Ref{Int}`: Current triplet count
- `capacity::Int`: Maximum triplet capacity
- `node_to_elements::NodeToElementsMap`: Inverse connectivity
- `element_cache::ElementCache`: Cache for element operations
- `ndofs::Int`: Total DOFs in system
"""
struct NodeBasedCOOCache{T<:AbstractTopology,B<:AbstractBasis,IPS}
I::Vector{Int}
J::Vector{Int}
V::Vector{Float64}
f::Vector{Float64}
counter::Ref{Int}
capacity::Int
node_to_elements::NodeToElementsMap
element_cache::ElementCache{T,B,IPS}
ndofs::Int
end
"""
NodeBasedCOOCache(mesh::AbstractMesh, kernel::ContinuumKernel)
Create cache for node-based assembly.
Builds inverse connectivity and allocates buffers.
# Arguments
- `mesh`: Finite element mesh
- `kernel`: Continuum kernel
# Returns
- Pre-allocated node-based COO cache
"""
function NodeBasedCOOCache(mesh::AbstractMesh, kernel::ContinuumKernel)
# Build inverse connectivity
node_to_elements = NodeToElementsMap(mesh.connectivity)
# Estimate triplet count (same as element-based)
ndofs_per_node = dofs_per_node(kernel)
nnodes = length(mesh.nodes)
ndofs = ndofs_per_node * nnodes
# Estimate: For each node, sum over touching elements
# Each element contributes N blocks (N = nodes per element)
# Each block = 3×3 = 9 triplets
avg_elements_per_node = node_to_elements.nelements / nnodes
N = length(first(mesh.connectivity)) # Nodes per element
estimated_triplets = Int(ceil(1.2 * nnodes * avg_elements_per_node * N * 9))
# Allocate triplet arrays
I = Vector{Int}(undef, estimated_triplets)
J = Vector{Int}(undef, estimated_triplets)
V = Vector{Float64}(undef, estimated_triplets)
f = zeros(Float64, ndofs)
counter = Ref(0)
# Create element cache (for prepare_element! and compute_block!)
element_cache = ElementCache(mesh, kernel)
return NodeBasedCOOCache(I, J, V, f, counter, estimated_triplets,
node_to_elements, element_cache, ndofs)
end
"""
reset!(cache::NodeBasedCOOCache)
Reset cache for new assembly (zero force vector, reset counter).
Does NOT clear inverse connectivity (that's permanent structure).
"""
function reset!(cache::NodeBasedCOOCache)
fill!(cache.f, 0.0)
cache.counter[] = 0
return nothing
end
"""
assemble!(
cache::NodeBasedCOOCache,
assembler::NodeBasedCOOAssembler,
kernel::ContinuumKernel,
mesh::AbstractMesh
) -> Nothing
Assemble global system using **node-based traversal**.
# Algorithm
```julia
for node_i in 1:nnodes
# Get all elements touching this node
for elem_info in node_to_elements[node_i]
element_id = elem_info.element_id
local_i = elem_info.local_node_idx
# Prepare element geometry ONCE
prepared = prepare_element!(cache.element_cache, kernel, element_id, mesh)
# Compute blocks for all nodes in this element
for local_j in 1:N
global_j = connectivity[element_id][local_j]
# Compute single 3×3 block
K_ij = compute_block!(prepared, kernel.material, local_i, local_j)
# Scatter to triplets
scatter_block_to_triplets!(cache, K_ij, node_i, global_j)
end
end
end
```
# Key Operations
1. **prepare_element!** - Precompute geometry (Jacobian, gradients) once per element
2. **compute_block!** - Compute single 3×3 stiffness block using prepared geometry
3. **scatter_block_to_triplets!** - Add 9 triplets (i,j,value) for 3×3 block
# Zero-Allocation (After Warmup)
All arrays pre-allocated. Element preparation reuses cache buffers.
# Arguments
- `cache`: Pre-allocated node-based COO cache
- `assembler`: Node-based COO assembler
- `kernel`: Continuum kernel
- `mesh`: Finite element mesh
"""
function assemble!(
cache::NodeBasedCOOCache,
assembler::NodeBasedCOOAssembler,
kernel::ContinuumKernel,
mesh::AbstractMesh
)
# Reset cache
reset!(cache)
nnodes = length(mesh.nodes)
ndofs_per_node = dofs_per_node(kernel)
# NODAL LOOP: One iteration per node (GPU: one thread per node!)
for node_i in 1:nnodes
# Get all elements touching this node
touching_elements = cache.node_to_elements.node_to_elements[node_i]
# Loop over touching elements
for elem_info in touching_elements
element_id = elem_info.element_id
local_i = elem_info.local_node_idx # Position of node_i in element
# Prepare element geometry ONCE (reuses cache.element_cache)
prepared = prepare_element!(cache.element_cache, kernel, element_id, mesh)
# Get element connectivity
conn = mesh.connectivity[element_id]
N = length(conn) # Nodes per element
# Compute blocks for all nodes j in this element
for local_j in 1:N
global_j = conn[local_j]
# Compute single 3×3 block K[i,j]
# This is THE KEY OPERATION: block-based integration
K_ij = compute_block!(
prepared,
kernel.material,
local_i,
local_j
)
# Scatter 3×3 block to triplets (adds 9 entries)
scatter_block_to_triplets!(
cache,
K_ij,
node_i,
global_j,
ndofs_per_node
)
end
end
end
return nothing
end
"""
scatter_block_to_triplets!(
cache::NodeBasedCOOCache,
K_block::Tensor{2,3},
node_i::Int,
node_j::Int,
ndofs_per_node::Int
)
Scatter single 3×3 block to COO triplets **in-place**.
Maps block[α,β] → triplet at DOF indices:
- Row: 3*(node_i-1) + α
- Col: 3*(node_j-1) + β
- Val: K_block[α,β]
# Arguments
- `cache`: Node-based COO cache
- `K_block`: 3×3 stiffness block (Tensor{2,3})
- `node_i`: Global row node index
- `node_j`: Global column node index
- `ndofs_per_node`: DOFs per node (typically 3)
# Zero-Allocation
Writes to pre-allocated triplet arrays, updates counter.
"""
function scatter_block_to_triplets!(
cache::NodeBasedCOOCache,
K_block::Tensor{2,3,Float64},
node_i::Int,
node_j::Int,
ndofs_per_node::Int
)
counter = cache.counter[]
# Check capacity
new_triplets = ndofs_per_node * ndofs_per_node # 3×3 = 9
if counter + new_triplets > cache.capacity
error("Node-based COO cache overflow: need $(counter + new_triplets) triplets, " *
"capacity is $(cache.capacity). Increase cache size.")
end
# DOF offsets for nodes i and j
row_offset = ndofs_per_node * (node_i - 1)
col_offset = ndofs_per_node * (node_j - 1)
# Scatter 3×3 block to triplets
for β in 1:ndofs_per_node # Column (node j DOF)
j_global = col_offset + β
for α in 1:ndofs_per_node # Row (node i DOF)
i_global = row_offset + α
counter += 1
cache.I[counter] = i_global
cache.J[counter] = j_global
cache.V[counter] = K_block[α, β]
end
end
cache.counter[] = counter
return nothing
end
"""
extract_system(cache::NodeBasedCOOCache) -> (K, f)
Build sparse matrix from triplets and return system.
Calls `sparse(I, J, V)` to build CSC matrix. Duplicates are summed automatically.
# Arguments
- `cache`: Assembled node-based COO cache
# Returns
- `K::SparseMatrixCSC`: Global stiffness matrix
- `f::Vector`: Global force vector
# Allocation
Allocates sparse matrix structure (CSC format). This is the only allocation
outside cache construction.
"""
function extract_system(cache::NodeBasedCOOCache)
ntriplets = cache.counter[]
# Build sparse matrix (duplicates are summed automatically)
I_used = @view cache.I[1:ntriplets]
J_used = @view cache.J[1:ntriplets]
V_used = @view cache.V[1:ntriplets]
K = sparse(I_used, J_used, V_used, cache.ndofs, cache.ndofs)
return K, cache.f
end
# ============================================================================
# HELPER FUNCTIONS
# ============================================================================
"""
create_cache(
assembler::NodeBasedCOOAssembler,
mesh::AbstractMesh,
kernel::ContinuumKernel
) -> NodeBasedCOOCache
Create pre-allocated cache for node-based COO assembly.
Convenience function that wraps `NodeBasedCOOCache(mesh, kernel)`.
# Example
```julia
assembler = NodeBasedCOOAssembler()
cache = create_cache(assembler, mesh, kernel)
assemble!(cache, assembler, kernel, mesh)
K, f = extract_system(cache)
```
"""
function create_cache(
assembler::NodeBasedCOOAssembler,
mesh::AbstractMesh,
kernel::ContinuumKernel
)
return NodeBasedCOOCache(mesh, kernel)
end
"""
dofs_per_node(kernel::ContinuumKernel) -> Int
Return DOFs per node for continuum kernel (always 3 for displacement).
Dispatches on kernel field dimension.
"""
function dofs_per_node(kernel::ContinuumKernel{Theory,Mat}) where {Theory,Mat}
field = kernel.field
return field.dim # Displacement{3} → 3
end
# ============================================================================
# PERFORMANCE NOTES
# ============================================================================
#=
# CPU Performance Comparison (Estimated)
**Element-Based Assembly:**
- Elements: 1000 Tet4
- Operations: 1000 elements × 4×4 blocks × 3×3 entries = 48,000 block computations
- Time: ~5ms (baseline)
**Node-Based Assembly:**
- Nodes: 500 nodes
- Operations: 500 nodes × 8 elements/node × 4 blocks/element = 16,000 block computations
- But: 3× more kernel calls due to overlaps
- Time: ~7-10ms (1.5-2× slower single-threaded)
**Why slower on CPU?**
- Each block computed once in element assembly
- Each block computed 2× on average in nodal assembly (shared between 2 elements)
- More function call overhead
**Why faster on GPU?**
- Element assembly: Sequential (can't parallelize over elements efficiently)
- Nodal assembly: Massive parallelism (one thread per node)
- GPU speedup: ~10-50× depending on problem size
**Multi-threaded CPU (Threads.@threads):**
- Can parallelize outer node loop
- Expected speedup: 1.5-2× over element-based
- No race conditions (each node writes different triplets)
# Memory Comparison
**Element-Based:**
- Triplet storage: ~50 KB per 1000 elements
- Element cache: ~2 KB per thread
**Node-Based:**
- Triplet storage: Same (~50 KB)
- Element cache: ~2 KB per thread
- Inverse connectivity: ~10-20 KB (one-time)
Nearly identical memory usage!
# When to Use Node-Based Assembly
**Use when:**
- GPU acceleration needed
- Contact mechanics (naturally nodal)
- Matrix-free methods (K*v without forming K)
- Adaptive refinement (local node operations)
- Multi-threading on CPU
**Don't use when:**
- Single-threaded CPU only
- Simple problems (< 1000 nodes)
- Prototyping/debugging (element-based is clearer)
=#