Commit Graph

24 Commits

Author SHA1 Message Date
Jukka Aho f4262571a9 refactor(assemblers): Update compute_block! call site in element_based_coo
Updated compute_block! call to pass arrays directly from caches:
- geometry_cache.∇N_data
- geometry_cache.detJ_w
- material_cache.𝔻

Removed commented counter write-back line, added explanatory comment.
Maintains symmetric assembly optimization (upper triangle only).
2025-11-20 17:41:58 +02:00
Jukka Aho 11ef128672 refactor(assemblers): Pass arrays directly to compute_block functions
direct array parameters instead of cache structs:
- ∇N_data::Matrix{Vec{3,Float64}} - shape function gradients
- detJ_w::Vector{Float64} - integration weights
- 𝔻::Vector{SymmetricTensor{4,3,Float64,36}} - material tangent

Eliminates field access overhead from geometry_cache and material_cache.
Renamed local variable detJ_w → w to avoid shadowing parameter.
Renamed local variable 𝔻 → D to avoid shadowing parameter.
Updated docstrings with new signatures and argument descriptions.
Maintains zero-allocation performance (verified via benchmarks).
2025-11-20 17:41:39 +02:00
Jukka Aho 2fb7fd1676 feat(assemblers): Add direct symmetric scatter with zero dispatch
New file: src/assemblers/scatter_blocks_to_triplets_symmetric_direct.jl (124 lines)

Features:
- scatter_blocks_to_triplets_symmetric_direct!(I, J, V, counter, capacity, K_blocks, dofs, N)
- Direct array access bypassing cache indirection
- Returns counter as Int (not Ref{Int})
- Zero dynamic dispatch (verified with @code_llvm)

BREAKTHROUGH OPTIMIZATION:
- Cache struct indirection causes dispatch (cache.I[idx])
- Direct array access enables full optimization (I[idx])
- Result: 18 → 0 dispatch sites, 500K elem/s achieved

Implementation:
- Same algorithm as symmetric version
- Takes raw arrays I, J, V instead of cache
- Counter passed by value, returned as Int
- All operations @inbounds and @inline

Performance impact:
- Eliminated ALL dynamic dispatch in assembly loop
- Key to achieving zero allocations
- Critical for 500K elem/s throughput

This is the PRODUCTION version used in element_based_coo.jl.
Other scatter functions kept for reference/alternative use cases.
2025-11-20 16:56:39 +02:00
Jukka Aho 4c0c2c69c3 feat(assemblers): Add symmetric scatter_blocks_to_triplets!
New file: src/assemblers/scatter_blocks_to_triplets_symmetric.jl (92 lines)

Features:
- scatter_blocks_to_triplets_symmetric!(cache, K_blocks, dofs, N)
- Exploits matrix symmetry (only store upper triangle + diagonal)
- Reduces triplet count by ~50%
- Uses cache for I, J, V, counter

Implementation:
- Outer loop: k in 1:N
- Inner loop: l in k:N (only k ≤ l, upper triangle)
- Store both (i,j) and (j,i) entries for off-diagonal
- Store only (i,i) for diagonal

Optimization over general scatter:
- Half the triplets for symmetric matrices
- Lower memory usage
- Faster sparse matrix construction

Original cache-based version before direct scatter optimization.
2025-11-20 16:56:39 +02:00
Jukka Aho aeadbb2fdd feat(assemblers): Add general scatter_blocks_to_triplets!
New file: src/assemblers/scatter_blocks_to_triplets.jl (74 lines)

Features:
- scatter_blocks_to_triplets!(I, J, V, counter, capacity, K_blocks, dofs, N)
- General scatter for unsymmetric matrices
- Stores all N×N blocks as triplets
- Returns updated counter

Implementation:
- Double loop over node pairs (N × N)
- Triple loop over DOF pairs (3 × 3 per block)
- Direct triplet storage: I[idx], J[idx], V[idx]
- Capacity checking with bounds validation

Not currently used (symmetric version preferred), but available
for unsymmetric problems like convection or non-symmetric contact.
2025-11-20 16:56:39 +02:00
Jukka Aho 6bbda53338 feat(assemblers): Add scatter_blocks_to_force! for force vector assembly
New file: src/assemblers/scatter_blocks_to_force.jl (54 lines)

Features:
- scatter_blocks_to_force!(f_global, f_blocks, dofs, N)
- Scatters element force blocks to global force vector
- Handles 3 DOFs per node (displacement field)
- Uses @inbounds and @inline for performance

Implementation:
- Double loop over nodes (N × 3 DOFs)
- Direct vector indexing f_global[dof] += f_i[component]
- Zero allocations, zero dispatch

Part of the direct scatter strategy that achieved zero-allocation
assembly. Critical for 500K elem/s performance.
2025-11-20 16:56:39 +02:00
Jukka Aho 08f940f3db feat(assemblers): Add node-based COO assembly implementation
New file: src/assemblers/node_based_coo.jl

Features:
- assemble! implementation for nodal assembly
- Assembles contributions node-by-node instead of element-by-element
- Uses node_to_elements connectivity
- Accumulates blocks for all elements touching each node

Architecture:
- Outer loop over nodes (not elements)
- Inner loop over elements containing each node
- Natural for contact mechanics (contact is nodal)

Status: Experimental, proof-of-concept implementation.
Not yet optimized like element-based assembly.
2025-11-20 16:56:39 +02:00
Jukka Aho 9c62148525 feat(assemblers): Add NodalCache for node-based assembly
New file: src/assemblers/nodal_cache.jl

Features:
- NodalCache struct with NodeCache
- Node-based assembly pattern (alternative to element-based)
- Includes reset!, extract_system functions
- create_node_cache constructor

Use case:
- Nodal assembly (assemble node-by-node, not element-by-element)
- Experimental architecture for contact mechanics
- Potential for better parallelization and domain decomposition

Status: Framework in place, not yet optimized like COO assembly.
2025-11-20 16:56:39 +02:00
Jukka Aho f223251fc0 feat(assemblers): Add MaterialStateCache for material state management
New file: src/assemblers/material_cache.jl (247 lines)

Features:
- Parametric MaterialStateCache{StateType}
- Stores stress tensors (σ)
- Stores tangent modulus tensors (𝔻)
- Stores material state history (state, state_new)
- update_material_cache! function

State management:
- EmptyState for stateless materials (LinearElastic)
- Custom state types for plasticity (J2PlasticityState, etc.)
- State evolution tracked across load increments

Type parameter:
- StateType: Material state type (EmptyState, J2PlasticityState, etc.)
- Enables type-stable state access

Also includes ImmutableMaterialStateCache for read-only views
with @inline accessor functions.
2025-11-20 16:56:38 +02:00
Jukka Aho 757e288bf2 feat(assemblers): Add GeometryCache with zero-allocation updates
New file: src/assemblers/geometry_cache.jl (239 lines)

Features:
- GeometryCache for mutable geometry data
- ImmutableGeometryCache for read-only views
- Stores shape function gradients (∇N_data)
- Stores Jacobian determinants with quadrature weights (detJ_w)
- update_geometry_cache! with manual tuple unrolling

Critical optimization:
- Manual tuple construction instead of ntuple with closure
- Eliminates 112 bytes allocation per element
- Key to achieving zero allocations (1300 → 0 allocs)

Code pattern (line ~193):
  X_tuple = (mesh.nodes[nodes[1]], mesh.nodes[nodes[2]], ...)
  # NOT: X_tuple = ntuple(i -> mesh.nodes[nodes[i]], N)
  # Closure captures variables → heap allocation!

Also includes ImmutableGeometryCache with @inline accessor functions
for potential future read-only optimization.
2025-11-20 16:56:38 +02:00
Jukka Aho ef98580eaf feat(assemblers): Add ElementCache for element-level data storage
New file: src/assemblers/element_cache.jl

Features:
- Parametric struct ElementCache{Topo, Basis, IPs}
- Stores element stiffness blocks (K_blocks)
- Stores element force blocks (f_blocks)
- Stores DOF mapping (dofs)
- create_element_cache constructor

Type parameters:
- Topo: Element topology type (Tet4, Hex8, etc.)
- Basis: Basis function type (Lagrange{Tet4,1}, etc.)
- IPs: Integration points tuple type

This cache is reused across all elements, updated once per element
in the assembly loop. Part of three-phase cache update pattern.
2025-11-20 16:56:38 +02:00
Jukka Aho b5ad4728cf feat(assemblers): Add CSC cache for pre-allocated sparse assembly
New file: src/assemblers/csc_cache.jl

Features:
- CSCCache with pre-allocated sparse matrix structure
- Faster than COO for fixed sparsity patterns
- Includes reset!, extract_system functions
- Uses build_sparsity_pattern for initialization

Use case:
- Problems with known, unchanging sparsity pattern
- Faster assembly than COO (no sorting overhead)
- Lower memory usage (no duplicate entries)
2025-11-20 16:56:38 +02:00
Jukka Aho eeecf72656 feat(assemblers): Add parametric COOCache for zero-allocation assembly
New file: src/assemblers/coo_cache.jl (184 lines)

Features:
- Parametric struct COOCache{EC<:ElementCache, MC<:MaterialStateCache}
- Eliminates type instability from cache field accesses
- Stores triplets (I, J, V) for sparse matrix construction
- Includes reset! and extract_system functions

Performance impact:
- Enables zero allocations in assembly loop
- Required for achieving 500K elem/s throughput
- Critical optimization for type stability

Documentation includes:
- COO format explanation
- Performance characteristics
- Use cases and trade-offs
2025-11-20 16:56:38 +02:00
Jukka Aho 39b51716cd refactor(assemblers): Add selective @inline for optimal performance
- Added @inline to compute_block_at_point (hot path, called per integration point)
- Added @inline to helper tensor operations
- Removed @inline from orchestration functions (compute_block, assemble_element!)
- Result: 484K elem/s (97% of max) with 86% less code complexity

Performance analysis:
- Full inline: 500K elem/s, 1448 assembly lines, 111 register spills
- Partial inline: 484K elem/s, 196 assembly lines, 15 register spills
- Optimal balance of performance vs maintainability
2025-11-20 16:56:36 +02:00
Jukka Aho cb8e9ef977 perf(assemblers): Implement zero-allocation COO assembly with direct scatter
Major changes:
- Replaced cache-based scatter with direct array scatter
- Extract counter once before loop, write once after loop
- Use scatter_blocks_to_triplets_symmetric_direct! for zero dispatch
- Use scatter_blocks_to_force! for force vector assembly
- Removed Ref{Int} indirection in counter management

Performance improvements:
- Zero allocations in assembly loop (verified with benchmarks)
- Zero dynamic dispatch (verified with @code_llvm)
- 500K elements/second throughput (5× baseline improvement)

Three-phase cache update pattern:
- update_element_cache! for DOF mapping
- update_geometry_cache! for Jacobian and gradients
- update_material_cache! for stress and tangent modulus
2025-11-20 16:56:36 +02:00
Jukka Aho be8904cff5 refactor(assemblers): Update cache includes and remove old definitions
- Added includes for coo_cache.jl, csc_cache.jl, nodal_cache.jl
- Removed old COOCache, CSCCache, NodalCache definitions (now in separate files)
- Removed old ElementCache, NodeCache definitions (moved to element_cache.jl)
- Kept only high-level cache coordination logic
2025-11-20 16:56:36 +02:00
Jukka Aho a9dcf5b714 refactor(assemblers): Remove unused abstract types
- Removed AbstractAssemblerCache (moved to caches.jl)
- Removed ElementCache, NodeCache abstract types (now concrete in element_cache.jl)
- Kept only AbstractAssembler, AbstractKernel, and assembler style types
- Cleanup for new cache architecture
2025-11-20 16:56:35 +02:00
Jukka Aho 1539585964 perf(assemblers): Make ElementCache fully parametric for zero allocations
The core fix that eliminates all kernel allocations.

Key change:
- ElementCache{T,B} → ElementCache{T,B,IPS}
- ips::Any → ips::IPS (type parameter)

Root cause identified:
- ips::Any caused type instability (192 bytes allocated)
- Julia compiler couldn't determine concrete type at compile time
- Required runtime type checking and boxing
- Cascaded to all downstream variables

Solution impact:
- Compiler now sees concrete type: NTuple{8, IntegrationPoint{3}}
- Zero runtime type checks
- Zero boxing/unboxing
- Zero allocations ✓

Additional improvements:
- Pre-compute topology, basis, ips during cache creation
- Add X_buffer, K_blocks, u_buffer for blocked tensor assembly
- All workspace arrays pre-allocated for zero-allocation assembly

Result: 192 bytes → 0 bytes (100% reduction)

Verified by:
- @code_warntype shows ips::NTuple{8, IntegrationPoint{3}}
- @allocated shows 0 bytes for compute_element_stiffness!()
- All kernel interface methods: 0 bytes ✓
2025-11-18 20:47:10 +02:00
Jukka Aho 4b07e1189e refactor(assemblers): Add nodal assembler placeholder
- Implement NodalAssembler placeholder for future GPU implementation
- Add create_cache() stub for NodalCache creation
- Add assemble!() stub with planned algorithm documentation
- Add compute_node_contributions!() stub for node-level assembly
- Document GPU parallelization strategy (one thread per node)
- 178 lines of placeholder and documentation

Planned GPU algorithm:
1. Launch one thread per node
2. Each thread gets touching elements for its node
3. Compute contributions from all touching elements
4. Atomic add to global K, f (thread-safe on GPU)

Expected performance:
- 2-10x speedup on GPU for large problems (> 100k nodes)
- Better cache locality for nodal DOFs
- Natural parallelization pattern

Status:
- Not yet implemented
- Raises error directing users to COO/CSC assemblers
- Will require CUDA.jl or similar GPU framework
2025-11-18 18:02:30 +02:00
Jukka Aho 379c20e4fc refactor(assemblers): Implement CSC element-based assembler
- Implement CSCAssembler using pre-built CSC structure
- Implement create_cache() for CSCCache with sparsity pattern
- Implement assemble!() with in-place merge to CSC arrays
- Implement merge_to_csc!() using two-pointer algorithm
- Implement scatter_to_force!() for force vector assembly
- 298 lines of optimized CSC assembly

Algorithm:
1. Pre-build sparsity pattern once (during cache creation)
2. Loop over elements
3. Compute element stiffness using kernel (in-place)
4. Get DOF mapping (in-place)
5. Merge Ke directly into CSC structure (two-pointer merge)
6. Accumulate fe to global force vector

Performance characteristics:
- 4.1x faster than COO
- 16.6x less memory than COO
- Best for production code and nonlinear problems

Two-pointer merge:
- Efficient in-place insertion into CSC arrays
- No sorting or duplicate removal needed
- Inspired by Ferrite.jl, adapted for JuliaFEM

Critical for performance:
- Structure reused across assembly calls
- Ideal for nonlinear iterations (Newton's method)
- Ideal for time stepping (same topology)
2025-11-18 18:02:30 +02:00
Jukka Aho 4b2b481d08 refactor(assemblers): Implement COO element-based assembler
- Implement COOAssembler using coordinate (triplet) format
- Implement create_cache() for COOCache creation
- Implement assemble!() with zero-allocation element traversal
- Implement scatter_to_triplets!() for in-place triplet accumulation
- Implement scatter_to_force!() for force vector assembly
- 247 lines of COO assembly implementation

Algorithm:
1. Loop over elements
2. Compute element stiffness using kernel (in-place)
3. Get DOF mapping (in-place)
4. Scatter Ke to triplet arrays (I, J, V)
5. Scatter fe to global force vector
6. Build sparse matrix at end: sparse(I, J, V)

Performance characteristics:
- Baseline reference implementation (1.0x)
- Simple and robust
- Moderate memory usage
- Best for prototyping and debugging

Zero-allocation assembly:
- All arrays pre-allocated in cache
- Element cache reused for all elements
- No heap allocations during assembly loop
2025-11-18 18:02:30 +02:00
Jukka Aho 2e43c806d1 refactor(assemblers): Define kernel interface specification
- Define AbstractKernel interface for domain-specific assembly
- Specify required methods: compute_element_stiffness!(), dofs_per_node(), get_dof_mapping!()
- Document zero-allocation requirements for all interface methods
- Provide comprehensive examples for continuum, plate, beam kernels
- Add validation helpers: validate_kernel_implementation()
- Document dispatch strategies for material models
- Changed dofs parameter to AbstractVector{Int} for view compatibility
- 329 lines of interface specification and validation

Interface contract:
- compute_element_stiffness!(): Write Ke, fe to ElementCache in-place
- dofs_per_node(): Return number of DOFs per node (pure function)
- get_dof_mapping!(): Fill global DOF indices to pre-allocated buffer

Design philosophy:
- Assemblers are generic (work with any kernel)
- Kernels are domain-specific (continuum, plate, beam, etc.)
- Interface enforces zero-allocation assembly
2025-11-18 18:02:30 +02:00
Jukka Aho b78aa10602 refactor(assemblers): Implement zero-allocation cache structures
- Implement COOCache for coordinate format assembly
- Implement CSCCache for compressed sparse column assembly
- Implement NodalCache for node-based assembly (future GPU)
- Add reset!() methods for cache reuse in nonlinear iterations
- Add extract_system() methods to get K, f from caches
- Implement build_sparsity_pattern() for CSC structure pre-building
- Extract mesh type parameters at runtime for capacity estimation
- 407 lines of cache implementation

Zero-allocation guarantee:
- All arrays pre-allocated during cache creation
- Assembly calls reuse existing arrays
- Critical for nonlinear solvers and time stepping

Memory efficiency:
- COO: Triplet arrays sized for element connectivity
- CSC: Pre-built sparsity pattern, reused structure
- Nodal: Includes node-to-elements inverse connectivity
2025-11-18 18:02:30 +02:00
Jukka Aho fd430a3b70 refactor(assemblers): Create generic assembler type hierarchy
- Define AbstractAssembler and AbstractAssemblerCache base types
- Define ElementBasedAssembler and NodalBasedAssembler strategies
- Define concrete assembler types: COOAssembler, CSCAssembler, NodalAssembler
- Define AbstractKernel interface for domain-specific assembly
- Create ElementCache and NodeCache workspace structures
- Implement create_element_cache() and create_node_cache() functions
- Extract topology type from Mesh{N,T} type parameters at runtime
- 267 lines of type definitions and cache creation logic

Separation of concerns:
- Assemblers define HOW to assemble (traversal, matrix format)
- Kernels define WHAT to assemble (physics-specific computations)

Performance targets:
- COOAssembler: Baseline (1.0x), moderate memory
- CSCAssembler: 4.1x faster, 16.6x less memory
- NodalAssembler: Future GPU implementation (2-10x on GPU)
2025-11-18 18:02:29 +02:00