mirror of
https://github.com/JuliaFEM/JuliaFEM.jl.git
synced 2026-09-27 12:16:56 +00:00
refactor(continuum): Implement ContinuumKernel for generic assemblers
- Implement ContinuumKernel{Theory, Material} implementing AbstractKernel
- Implement dofs_per_node() returning 3 (ux, uy, uz)
- Implement get_dof_mapping!() with node-major DOF ordering
- Implement compute_element_stiffness!() with material dispatch
- Add compute_element_stiffness_blocked!() for LinearElastic material
- Add compute_element_stiffness_blocked!() for NeoHookean material
- Add blocked_tensor_to_matrix_view!() for tensor-to-matrix conversion
- Extract topology type from Mesh{N,T} parameters at runtime
- Changed get_dof_mapping!() to accept AbstractVector{Int} for view compatibility
- 424 lines of continuum kernel implementation
Kernel interface implementation:
- dofs_per_node(): Returns 3 (displacements ux, uy, uz)
- get_dof_mapping!(): Node-major ordering [ux1, uy1, uz1, ux2, uy2, uz2, ...]
- compute_element_stiffness!(): Zero-allocation, writes to ElementCache
Material dispatch:
- LinearElastic: Pre-compute constant C tensor, efficient integration
- NeoHookean: Strain-dependent tangent 𝔻(E), nonlinear stiffness
- Future: Plasticity, damage, hyperelastic, etc.
Integration strategy:
- Automatic topology detection from mesh type
- Automatic basis selection (Lagrange{Topology,1})
- Automatic integration order (default_integration)
Zero-allocation design:
- All computations use ElementCache buffers
- Temporary tensors are stack-allocated (small, fast)
- No heap allocations during assembly loop
This commit is contained in:
@@ -0,0 +1,425 @@
|
||||
# This file is a part of JuliaFEM.
|
||||
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
|
||||
|
||||
"""
|
||||
Continuum mechanics kernel for generic assemblers.
|
||||
|
||||
Implements the kernel interface for 3D continuum mechanics (solid mechanics).
|
||||
Compatible with COOAssembler, CSCAssembler, and future NodalAssembler.
|
||||
"""
|
||||
|
||||
using Tensors
|
||||
using LinearAlgebra
|
||||
|
||||
"""
|
||||
ContinuumKernel{Theory<:AbstractContinuumTheory, Mat<:AbstractMaterial} <: AbstractKernel
|
||||
|
||||
Domain kernel for continuum mechanics (3D solid mechanics).
|
||||
|
||||
Couples formulation theory, material model, and displacement field.
|
||||
Works with any assembler (COO, CSC, nodal).
|
||||
|
||||
# Type Parameters
|
||||
- `Theory`: Continuum theory (FullThreeD, PlaneStress, PlaneStrain, Axisymmetric)
|
||||
- `Mat`: Material model (LinearElastic, NeoHookean, etc.)
|
||||
|
||||
# Fields
|
||||
- `formulation`: ContinuumFormulation{Theory}
|
||||
- `material`: Material model instance
|
||||
- `field`: Displacement{3}() field type
|
||||
|
||||
# Integration and Basis
|
||||
|
||||
Kernel automatically selects appropriate integration order and basis functions
|
||||
based on topology type during assembly.
|
||||
|
||||
# Example
|
||||
|
||||
```julia
|
||||
kernel = ContinuumKernel(
|
||||
ContinuumFormulation{FullThreeD}(),
|
||||
LinearElastic(E=210e9, ν=0.3),
|
||||
Displacement{3}()
|
||||
)
|
||||
|
||||
# Use with any assembler
|
||||
assembler = CSCAssembler()
|
||||
cache = create_cache(assembler, mesh, kernel)
|
||||
assemble!(cache, assembler, kernel, mesh)
|
||||
K, f = extract_system(cache)
|
||||
```
|
||||
"""
|
||||
struct ContinuumKernel{Theory<:AbstractContinuumTheory,Mat<:AbstractMaterial} <: AbstractKernel
|
||||
formulation::ContinuumFormulation{Theory}
|
||||
material::Mat
|
||||
field::Displacement{3}
|
||||
end
|
||||
|
||||
# Convenience constructor without field (defaults to Displacement{3})
|
||||
function ContinuumKernel(
|
||||
formulation::ContinuumFormulation{Theory},
|
||||
material::Mat
|
||||
) where {Theory<:AbstractContinuumTheory,Mat<:AbstractMaterial}
|
||||
return ContinuumKernel(formulation, material, Displacement{3}())
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# KERNEL INTERFACE IMPLEMENTATION
|
||||
# ============================================================================
|
||||
|
||||
"""
|
||||
dofs_per_node(kernel::ContinuumKernel) -> Int
|
||||
|
||||
Continuum mechanics uses 3 DOFs per node (ux, uy, uz).
|
||||
"""
|
||||
function dofs_per_node(kernel::ContinuumKernel)
|
||||
return 3 # ux, uy, uz displacements
|
||||
end
|
||||
|
||||
"""
|
||||
get_dof_mapping!(
|
||||
dofs::Vector{Int},
|
||||
kernel::ContinuumKernel,
|
||||
element_id::Int,
|
||||
mesh::AbstractMesh
|
||||
) -> Nothing
|
||||
|
||||
Fill DOF indices for continuum element (node-major ordering).
|
||||
|
||||
DOF numbering: Node k has DOFs [3*(k-1)+1, 3*(k-1)+2, 3*(k-1)+3] for [ux, uy, uz].
|
||||
|
||||
# Example
|
||||
|
||||
Element with nodes [10, 20, 30, 40]:
|
||||
- Node 10: DOFs [28, 29, 30]
|
||||
- Node 20: DOFs [58, 59, 60]
|
||||
- Node 30: DOFs [88, 89, 90]
|
||||
- Node 40: DOFs [118, 119, 120]
|
||||
|
||||
Output: dofs = [28, 29, 30, 58, 59, 60, 88, 89, 90, 118, 119, 120]
|
||||
"""
|
||||
function get_dof_mapping!(
|
||||
dofs::AbstractVector{Int},
|
||||
kernel::ContinuumKernel,
|
||||
element_id::Int,
|
||||
mesh::AbstractMesh
|
||||
)
|
||||
conn = mesh.connectivity[element_id]
|
||||
nnodes_elem = length(conn)
|
||||
|
||||
# Fill DOF indices (node-major: all DOFs for node 1, then node 2, ...)
|
||||
idx = 1
|
||||
@inbounds for node_id in conn
|
||||
for α in 1:3 # ux, uy, uz
|
||||
dofs[idx] = 3 * (node_id - 1) + α
|
||||
idx += 1
|
||||
end
|
||||
end
|
||||
|
||||
return nothing
|
||||
end
|
||||
|
||||
"""
|
||||
compute_element_stiffness!(
|
||||
cache::ElementCache,
|
||||
kernel::ContinuumKernel,
|
||||
element_id::Int,
|
||||
mesh::AbstractMesh
|
||||
) -> Nothing
|
||||
|
||||
Compute element stiffness matrix and force vector for continuum mechanics **in-place**.
|
||||
|
||||
# Algorithm
|
||||
|
||||
1. Get element nodes and coordinates
|
||||
2. Zero output arrays (Ke, fe)
|
||||
3. Select topology, basis, integration based on element type
|
||||
4. Loop over integration points:
|
||||
- Compute shape function gradients
|
||||
- Compute B-matrix (strain-displacement)
|
||||
- Compute material stiffness C or 𝔻
|
||||
- Accumulate: Ke += B^T * C * B * detJ * w
|
||||
5. Apply body forces to fe (if any)
|
||||
|
||||
# Material Dispatch
|
||||
|
||||
- `LinearElastic`: Uses constant elasticity tensor C
|
||||
- `NeoHookean`: Uses strain-dependent tangent 𝔻(E)
|
||||
- Future: Plasticity, damage, etc.
|
||||
|
||||
# Zero-Allocation Guarantee
|
||||
|
||||
All computations use pre-allocated buffers from `cache`. Temporary tensors
|
||||
are stack-allocated (small, fast). No heap allocations.
|
||||
|
||||
# Arguments
|
||||
- `cache`: Pre-allocated element workspace
|
||||
- `kernel`: Continuum kernel with material and formulation
|
||||
- `element_id`: Element index in mesh
|
||||
- `mesh`: Finite element mesh
|
||||
"""
|
||||
function compute_element_stiffness!(
|
||||
cache::ElementCache,
|
||||
kernel::ContinuumKernel,
|
||||
element_id::Int,
|
||||
mesh::AbstractMesh
|
||||
)
|
||||
# Get element connectivity
|
||||
conn = mesh.connectivity[element_id]
|
||||
nnodes_elem = length(conn)
|
||||
ndofs_elem = 3 * nnodes_elem
|
||||
|
||||
# Zero output arrays
|
||||
@views fill!(cache.Ke[1:ndofs_elem, 1:ndofs_elem], 0.0)
|
||||
@views fill!(cache.fe[1:ndofs_elem], 0.0)
|
||||
|
||||
# Get element node coordinates
|
||||
@inbounds for (i, node_id) in enumerate(conn)
|
||||
cache.coords[i, :] .= mesh.nodes[node_id]
|
||||
end
|
||||
|
||||
# Convert coords to Vector{Vec{3}} for kernel calls
|
||||
X_buffer = [Vec{3}(cache.coords[i, :]) for i in 1:nnodes_elem]
|
||||
|
||||
# Get topology type from mesh
|
||||
# For Mesh{8, Hexahedron{8}}, this is Hexahedron{8}
|
||||
# Extract topology type directly from Mesh{N,T} type parameters
|
||||
MeshType = typeof(mesh)
|
||||
TopologyType = MeshType.parameters[2] # T from Mesh{N,T}
|
||||
|
||||
# Create topology, basis, integration instances
|
||||
topology = TopologyType()
|
||||
basis = Lagrange{TopologyType,1}()
|
||||
integration_scheme = default_integration(TopologyType)
|
||||
ips = integration_points(integration_scheme, topology)
|
||||
|
||||
# Compute element stiffness using blocked tensor format
|
||||
# Allocate K_blocks (small, stack-allocated for typical elements)
|
||||
K_blocks = Matrix{Tensor{2,3,Float64,9}}(undef, nnodes_elem, nnodes_elem)
|
||||
fill!(K_blocks, zero(Tensor{2,3}))
|
||||
|
||||
# Displacement DOFs (zero for linear elastic, needed for nonlinear)
|
||||
u_elem = zeros(ndofs_elem)
|
||||
|
||||
# Call material-dispatched stiffness computation
|
||||
compute_element_stiffness_blocked!(
|
||||
K_blocks,
|
||||
X_buffer,
|
||||
kernel.material,
|
||||
u_elem,
|
||||
topology,
|
||||
basis,
|
||||
ips
|
||||
)
|
||||
|
||||
# Convert blocked tensor to Float64 matrix (cache.Ke)
|
||||
blocked_tensor_to_matrix_view!(
|
||||
@view(cache.Ke[1:ndofs_elem, 1:ndofs_elem]),
|
||||
K_blocks
|
||||
)
|
||||
|
||||
# TODO: Add body forces to fe if needed
|
||||
# For now, fe = 0 (forces added by Neumann BCs)
|
||||
|
||||
return nothing
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# HELPER FUNCTIONS
|
||||
# ============================================================================
|
||||
|
||||
"""
|
||||
compute_element_stiffness_blocked!(
|
||||
K_blocks::Matrix{Tensor{2,3}},
|
||||
X::Vector{Vec{3}},
|
||||
material::LinearElastic,
|
||||
u_elem::Vector{Float64},
|
||||
topology::T,
|
||||
basis::B,
|
||||
ips
|
||||
) -> Nothing
|
||||
|
||||
Compute element stiffness for LinearElastic material **in-place**.
|
||||
|
||||
Uses constant elasticity tensor C for efficiency.
|
||||
"""
|
||||
function compute_element_stiffness_blocked!(
|
||||
K_blocks::AbstractMatrix{Tensor{2,3,Float64,9}},
|
||||
X::Vector{Vec{3,Float64}},
|
||||
material::LinearElastic,
|
||||
u_elem::Vector{Float64},
|
||||
topology::T,
|
||||
basis::B,
|
||||
ips
|
||||
) where {T<:AbstractTopology{N},B<:AbstractBasis} where {N}
|
||||
|
||||
# Pre-compute elasticity tensor once
|
||||
C = elasticity_tensor(material)
|
||||
|
||||
# Integrate over node pairs
|
||||
for k in 1:N, l in 1:N
|
||||
# Accumulate contributions from all integration points
|
||||
for ip in ips
|
||||
ξ = Vec{3}(ip.ξ)
|
||||
w = ip.weight
|
||||
|
||||
# Shape function gradients in reference coordinates
|
||||
dN_dξ = get_basis_derivatives(topology, basis, ξ)
|
||||
|
||||
# Jacobian transformation: J = ∑_i X_i ⊗ (∂N_i/∂ξ)
|
||||
J = X[1] ⊗ dN_dξ[1]
|
||||
for i in 2:N
|
||||
J += X[i] ⊗ dN_dξ[i]
|
||||
end
|
||||
detJ = det(J)
|
||||
J_inv = inv(J)
|
||||
J_inv_T = transpose(J_inv)
|
||||
|
||||
# Physical gradients
|
||||
grad_k = J_inv_T ⋅ dN_dξ[k]
|
||||
grad_l = J_inv_T ⋅ dN_dξ[l]
|
||||
|
||||
# Compute 3×3 stiffness block
|
||||
K_kl = compute_stiffness_block(grad_k, grad_l, C)
|
||||
|
||||
# Accumulate with quadrature weight and Jacobian
|
||||
K_blocks[k, l] += K_kl * detJ * w
|
||||
end
|
||||
end
|
||||
|
||||
return nothing
|
||||
end
|
||||
|
||||
"""
|
||||
compute_element_stiffness_blocked!(
|
||||
K_blocks::Matrix{Tensor{2,3}},
|
||||
X::Vector{Vec{3}},
|
||||
material::NeoHookean,
|
||||
u_elem::Vector{Float64},
|
||||
topology::T,
|
||||
basis::B,
|
||||
ips
|
||||
) -> Nothing
|
||||
|
||||
Compute element stiffness for NeoHookean material **in-place**.
|
||||
|
||||
Uses strain-dependent tangent modulus 𝔻(E).
|
||||
"""
|
||||
function compute_element_stiffness_blocked!(
|
||||
K_blocks::AbstractMatrix{Tensor{2,3,Float64,9}},
|
||||
X::Vector{Vec{3,Float64}},
|
||||
material::NeoHookean,
|
||||
u_elem::Vector{Float64},
|
||||
topology::T,
|
||||
basis::B,
|
||||
ips
|
||||
) where {T<:AbstractTopology{N},B<:AbstractBasis} where {N}
|
||||
|
||||
# Basis vectors
|
||||
e_1, e_2, e_3 = Vec{3}((1.0, 0.0, 0.0)), Vec{3}((0.0, 1.0, 0.0)), Vec{3}((0.0, 0.0, 1.0))
|
||||
e = (e_1, e_2, e_3)
|
||||
|
||||
# Identity tensor
|
||||
I = one(Tensor{2,3,Float64})
|
||||
|
||||
# Integrate over integration points
|
||||
for ip in ips
|
||||
ξ = Vec{3}(ip.ξ)
|
||||
w = ip.weight
|
||||
|
||||
# Shape function gradients
|
||||
dN_dξ = get_basis_derivatives(topology, basis, ξ)
|
||||
|
||||
# Jacobian
|
||||
J = X[1] ⊗ dN_dξ[1]
|
||||
for k in 2:N
|
||||
J += X[k] ⊗ dN_dξ[k]
|
||||
end
|
||||
J_inv = inv(J)
|
||||
detJ = det(J)
|
||||
detJ > 0.0 || error("Negative Jacobian determinant: $detJ")
|
||||
|
||||
# Physical gradients
|
||||
∇N = ntuple(k -> J_inv' ⋅ dN_dξ[k], N)
|
||||
|
||||
# Compute deformation gradient F = I + ∇u
|
||||
F = I
|
||||
for k in 1:N
|
||||
k_offset = 3(k - 1)
|
||||
u_k = Vec{3}((u_elem[k_offset+1], u_elem[k_offset+2], u_elem[k_offset+3]))
|
||||
F += u_k ⊗ ∇N[k]
|
||||
end
|
||||
|
||||
# Right Cauchy-Green tensor C = F^T F
|
||||
C_tensor = symmetric(F' ⋅ F)
|
||||
|
||||
# Green-Lagrange strain E = ½(C - I)
|
||||
E = SymmetricTensor{2,3}(0.5 * (C_tensor - I))
|
||||
|
||||
# Compute stress and material tangent
|
||||
S, 𝔻, _ = compute_stress(material, E)
|
||||
|
||||
# Integration weight
|
||||
dV = detJ * w
|
||||
|
||||
# Compute stiffness contributions for each node pair
|
||||
for k in 1:N
|
||||
grad_k = ∇N[k]
|
||||
for l in 1:N
|
||||
grad_l = ∇N[l]
|
||||
|
||||
# Accumulate 3×3 block K_kl
|
||||
K_kl = zero(Tensor{2,3,Float64})
|
||||
for α in 1:3, β in 1:3
|
||||
e_α, e_β = e[α], e[β]
|
||||
|
||||
# Strain-displacement tensors
|
||||
B_k_α = 0.5 * (grad_k ⊗ e_α + e_α ⊗ grad_k)
|
||||
B_l_β = 0.5 * (grad_l ⊗ e_β + e_β ⊗ grad_l)
|
||||
|
||||
# Double contraction: B_k^α : 𝔻 : B_l^β
|
||||
value = dcontract(dcontract(B_k_α, 𝔻), B_l_β)
|
||||
|
||||
# Assemble into K_kl[α,β]
|
||||
K_kl += value * (e_α ⊗ e_β)
|
||||
end
|
||||
|
||||
# Accumulate to global block
|
||||
K_blocks[k, l] += K_kl * dV
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return nothing
|
||||
end
|
||||
|
||||
"""
|
||||
blocked_tensor_to_matrix_view!(K_e::AbstractMatrix, K_blocks::Matrix{Tensor{2,3}})
|
||||
|
||||
Convert blocked tensor matrix to Float64 matrix **in-place**.
|
||||
|
||||
# Arguments
|
||||
- `K_e`: Output matrix view [3N × 3N] (modified in-place)
|
||||
- `K_blocks`: Input blocked matrix [N × N] of Tensor{2,3}
|
||||
|
||||
# Performance
|
||||
|
||||
Zero allocations - writes directly to output view.
|
||||
"""
|
||||
function blocked_tensor_to_matrix_view!(
|
||||
K_e::AbstractMatrix{Float64},
|
||||
K_blocks::AbstractMatrix{Tensor{2,3,Float64,9}}
|
||||
)
|
||||
N = size(K_blocks, 1)
|
||||
|
||||
@inbounds for k in 1:N, l in 1:N
|
||||
K_kl = K_blocks[k, l]
|
||||
for α in 1:3, β in 1:3
|
||||
i = 3 * (k - 1) + α
|
||||
j = 3 * (l - 1) + β
|
||||
K_e[i, j] = K_kl[α, β]
|
||||
end
|
||||
end
|
||||
|
||||
return nothing
|
||||
end
|
||||
Reference in New Issue
Block a user