mirror of
https://github.com/JuliaFEM/JuliaFEM.jl.git
synced 2026-08-06 04:21:33 +00:00
feat(continuum): DOF-based Pass~1 hooks and mixed kernel updates
Add dof_based_pass1.jl prepare_dof_based_material_workspace! overrides; dim-2 geometry cache branch; update Hu–Washizu, Hellinger–Reissner, Stokes, mixed-up kernels and material cache wiring.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
# This file is a part of JuliaFEM.
|
||||
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
|
||||
# SPDX-FileCopyrightText: 2015-2026 Jukka Aho
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
Abstract types for continuum mechanics domain.
|
||||
@@ -24,7 +24,7 @@ DESIGN PHILOSOPHY: Formulations are DOMAIN-AGNOSTIC dimensionality concepts.
|
||||
Formulation (domain-agnostic):
|
||||
- Describes DIMENSIONALITY and geometric simplifications
|
||||
- Used by multiple physics domains
|
||||
- Examples: FullThreeD, Axisymmetric
|
||||
- Examples: ThreeDimensional, Axisymmetric
|
||||
- Can be reused across continuum, heat, acoustics, etc.
|
||||
|
||||
Theory (domain-specific):
|
||||
@@ -34,8 +34,8 @@ Theory (domain-specific):
|
||||
|
||||
# Why Separate Them?
|
||||
|
||||
Problem: Heat transfer needs FullThreeD and Axisymmetric, just like continuum!
|
||||
If FullThreeD is defined in domains/continuum/, heat can't use it without duplication.
|
||||
Problem: Heat transfer needs ThreeDimensional and Axisymmetric, just like continuum!
|
||||
If ThreeDimensional is defined in domains/continuum/, heat can't use it without duplication.
|
||||
|
||||
Solution: Formulations are dimensionality (shared), theories are physics (domain-specific).
|
||||
|
||||
@@ -43,7 +43,7 @@ Solution: Formulations are dimensionality (shared), theories are physics (domain
|
||||
|
||||
```julia
|
||||
# Domain-agnostic formulations
|
||||
FullThreeD() # Used by: continuum, heat, poisson, acoustics
|
||||
ThreeDimensional() # Used by: continuum, heat, poisson, acoustics
|
||||
Axisymmetric() # Used by: continuum, heat, etc.
|
||||
|
||||
# Domain-specific theories (in domains/*/types.jl)
|
||||
@@ -75,7 +75,7 @@ PlaneStrain (ε_xx, ε_yy, ε_xy, ε_zz = 0):
|
||||
- Out-of-plane strain ε_zz = 0
|
||||
- Examples: Dams, tunnels, retaining walls, long cylinders
|
||||
|
||||
FullThreeD:
|
||||
ThreeDimensional:
|
||||
- No simplifications, all six stress/strain components
|
||||
- Most accurate but most expensive
|
||||
|
||||
@@ -97,6 +97,6 @@ Plane Strain (thick section):
|
||||
- Constitutive: 3×3 reduced stiffness matrix (different from plane stress!)
|
||||
|
||||
# See Also
|
||||
- Concrete theories: `continuum/types.jl` (FullThreeD, PlaneStress, PlaneStrain, Axisymmetric)
|
||||
- Concrete theories: `continuum/types.jl` (ThreeDimensional, PlaneStress, PlaneStrain, Axisymmetric)
|
||||
"""
|
||||
abstract type AbstractContinuumTheory end
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
# SPDX-FileCopyrightText: 2015-2026 Jukka Aho
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
Pass~1 helpers for DOF-based assembly with [`ContinuumKernel`](@ref):
|
||||
vertex displacement scatter and material-workspace preparation.
|
||||
"""
|
||||
|
||||
using Tensors
|
||||
using Tensors: basevec, symmetric, Tensor
|
||||
|
||||
using ..JuliaFEM: AbstractMaterial, compute_stress, continuum_kinematics,
|
||||
GreenLagrangeKinematics, SmallStrainKinematics
|
||||
|
||||
"""
|
||||
scatter_vertex_displacements_from_global!(
|
||||
u_buffer, dofs_storage, configuration, ::Type{E},
|
||||
) -> Nothing
|
||||
|
||||
Scatter global displacement DOFs from `configuration` into
|
||||
`u_buffer` (vertex-major `Vec{3}` per topology node) using compile-time
|
||||
[`local_dof_layout`](@ref)`(E)` and the element's global DOF list in
|
||||
`dofs_storage`. Only entries with `1 ≤ component ≤ 3` and valid
|
||||
`entity_local` vertex indices participate.
|
||||
|
||||
No heap allocation in the loop.
|
||||
"""
|
||||
@inline function scatter_vertex_displacements_from_global!(
|
||||
u_buffer::Vector{Vec{3,Float64}},
|
||||
dofs_storage::Vector{Int},
|
||||
configuration::AbstractVector{Float64},
|
||||
::Type{E},
|
||||
) where {E<:AbstractElement}
|
||||
layout = local_dof_layout(E)
|
||||
nbuf = length(u_buffer)
|
||||
@inbounds for v in 1:nbuf
|
||||
u_buffer[v] = zero(Vec{3,Float64})
|
||||
end
|
||||
@inbounds for li in eachindex(layout)
|
||||
ent = entity_local(layout[li])
|
||||
(1 ≤ ent ≤ nbuf) || continue
|
||||
comp = component(layout[li])
|
||||
(1 ≤ comp ≤ 3) || continue
|
||||
d = dofs_storage[li]
|
||||
u_buffer[ent] = u_buffer[ent] + basevec(Vec{3,Float64}, comp) * configuration[d]
|
||||
end
|
||||
return nothing
|
||||
end
|
||||
|
||||
"""
|
||||
_continuum_fill_workspace_stress_from_displacement!(
|
||||
material_workspace, geometry_cache, element_cache, material, Δt, empty_state,
|
||||
) -> Nothing
|
||||
|
||||
Recompute `(σ, 𝔻)` at every IP from nodal displacements in `element_cache.u_buffer`
|
||||
using [`continuum_kinematics`](@ref)`(material)` (small strain or Green–Lagrange).
|
||||
|
||||
Used when `material_behavior(material) isa StatelessConstantTangent` but a global
|
||||
`configuration` is supplied so Cauchy stress tracks the current displacement while
|
||||
the tangent remains the constitutive tangent returned by [`compute_stress`](@ref).
|
||||
|
||||
Zero-allocation in the IP loop.
|
||||
"""
|
||||
@inline function _continuum_fill_workspace_stress_from_displacement!(
|
||||
material_workspace::AssemblyMaterialWorkspace{FieldType, StateType},
|
||||
geometry_cache::GeometryCache,
|
||||
element_cache::ElementCache,
|
||||
material::AbstractMaterial,
|
||||
Δt::Float64,
|
||||
empty_state::NamedTuple,
|
||||
) where {FieldType, StateType}
|
||||
fields_mw = getfield(material_workspace, 1)
|
||||
states_mw = getfield(material_workspace, 2)
|
||||
nips = length(element_cache.ips)
|
||||
nnodes = length(geometry_cache.X)
|
||||
kin = continuum_kinematics(material)
|
||||
I = one(Tensor{2,3,Float64,9})
|
||||
@inbounds for q in 1:nips
|
||||
strain_measure = if kin isa SmallStrainKinematics
|
||||
ε = zero(SymmetricTensor{2,3,Float64,6})
|
||||
for k in 1:nnodes
|
||||
u_k = element_cache.u_buffer[k]
|
||||
∇N_k_q = geometry_cache.∇N_data[q, k]
|
||||
ε += symmetric(u_k ⊗ ∇N_k_q)
|
||||
end
|
||||
ε
|
||||
elseif kin isa GreenLagrangeKinematics
|
||||
F = I
|
||||
for k in 1:nnodes
|
||||
u_k = element_cache.u_buffer[k]
|
||||
∇N_k_q = geometry_cache.∇N_data[q, k]
|
||||
F += u_k ⊗ ∇N_k_q
|
||||
end
|
||||
C_tensor = symmetric(F' ⋅ F)
|
||||
SymmetricTensor{2,3}(0.5 * (C_tensor - I))
|
||||
else
|
||||
throw(ArgumentError("unknown continuum kinematics $(typeof(kin))"))
|
||||
end
|
||||
σ, 𝔻, _ = compute_stress(material, strain_measure, NamedTuple(), Δt)
|
||||
fields_mw[q] = (σ=σ, 𝔻=𝔻)
|
||||
states_mw[q] = empty_state
|
||||
end
|
||||
return nothing
|
||||
end
|
||||
|
||||
@inline function prepare_dof_based_material_workspace!(
|
||||
k_e::ContinuumKernel,
|
||||
material_workspace::AssemblyMaterialWorkspace,
|
||||
geometry_cache::GeometryCache,
|
||||
element_cache::ElementCache,
|
||||
eid::Int,
|
||||
configuration::Union{Nothing,AbstractVector{Float64}},
|
||||
global_material_cache::Union{Nothing,GlobalMaterialCache},
|
||||
Δt::Float64,
|
||||
::Type{E},
|
||||
) where {E<:AbstractElement}
|
||||
mat = k_e.material
|
||||
beh = material_behavior(mat)
|
||||
fields_mw = getfield(material_workspace, 1)
|
||||
states_mw = getfield(material_workspace, 2)
|
||||
ips_ec = getfield(element_cache, :ips)
|
||||
nips = length(ips_ec)
|
||||
if beh isa StatelessConstantTangent
|
||||
fields_ref_e, empty_state_e = reference_fields(k_e)
|
||||
if configuration !== nothing
|
||||
scatter_vertex_displacements_from_global!(
|
||||
element_cache.u_buffer, element_cache.dofs, configuration, E,
|
||||
)
|
||||
_continuum_fill_workspace_stress_from_displacement!(
|
||||
material_workspace, geometry_cache, element_cache, mat, Δt, empty_state_e,
|
||||
)
|
||||
else
|
||||
@inbounds for q in 1:nips
|
||||
fields_mw[q] = fields_ref_e
|
||||
states_mw[q] = empty_state_e
|
||||
end
|
||||
end
|
||||
elseif beh isa StatelessStrainDependent
|
||||
if configuration !== nothing
|
||||
scatter_vertex_displacements_from_global!(
|
||||
element_cache.u_buffer, element_cache.dofs, configuration, E,
|
||||
)
|
||||
end
|
||||
update_material_cache_stateless_strain!(
|
||||
material_workspace, geometry_cache, mat, element_cache, Δt,
|
||||
)
|
||||
elseif beh isa StatefulStrainDependent
|
||||
global_material_cache === nothing && throw(ArgumentError(
|
||||
"DOF-based Pass 1: StatefulStrainDependent material requires keyword " *
|
||||
"`global_material_cache=create_global_material_cache(mat; n_ips, n_elems)`",
|
||||
))
|
||||
if configuration !== nothing
|
||||
scatter_vertex_displacements_from_global!(
|
||||
element_cache.u_buffer, element_cache.dofs, configuration, E,
|
||||
)
|
||||
end
|
||||
update_material_cache!(
|
||||
material_workspace,
|
||||
geometry_cache,
|
||||
mat,
|
||||
element_cache,
|
||||
global_material_cache,
|
||||
eid,
|
||||
Δt,
|
||||
)
|
||||
else
|
||||
throw(ArgumentError(
|
||||
"unsupported material behavior $(typeof(beh)) for ContinuumKernel in DOF-based Pass 1",
|
||||
))
|
||||
end
|
||||
return nothing
|
||||
end
|
||||
@@ -78,7 +78,7 @@ the discrete `σ`–`σ` block uses `G M⁻¹ G` in the Voigt component basis.
|
||||
```julia
|
||||
S = @DOFSet{u::DOF{Displacement{3}, Vertex}, σ::DOF{SymmetricTensor{2,3}, Cell}}
|
||||
kernel = HellingerReissnerKernel(
|
||||
ContinuumFormulation{FullThreeD}(),
|
||||
ContinuumFormulation{ThreeDimensional}(),
|
||||
LinearElastic(E = 210e9, ν = 0.3),
|
||||
)
|
||||
```
|
||||
|
||||
@@ -101,7 +101,7 @@ S = @DOFSet{
|
||||
sig::DOF{SymmetricTensor{2,3}, Cell},
|
||||
}
|
||||
kernel = HuWashizuKernel(
|
||||
ContinuumFormulation{FullThreeD}(),
|
||||
ContinuumFormulation{ThreeDimensional}(),
|
||||
LinearElastic(E = 210e9, ν = 0.3),
|
||||
)
|
||||
```
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# This file is a part of JuliaFEM.
|
||||
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
|
||||
# SPDX-FileCopyrightText: 2015-2026 Jukka Aho
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
Continuum mechanics kernel - defines the weak form only.
|
||||
@@ -10,9 +10,10 @@ This module defines:
|
||||
3. Block builder: compute_stiffness_block (builds D×D blocks)
|
||||
|
||||
The DOF-based / matrix-free assembler microkernel surface
|
||||
(`qpoint_buffer_eltype`, `update_qpoint_buffer!`, `evaluate_entry`,
|
||||
`evaluate_mass_entry`, `reference_fields`) is implemented further
|
||||
down in this file. Everything else (geometry preprocessing,
|
||||
(`qpoint_buffer_eltype`, `prepare_dof_based_material_workspace!`,
|
||||
`update_qpoint_buffer!`, `evaluate_entry`, `evaluate_mass_entry`,
|
||||
`reference_fields`) is implemented in this file and in
|
||||
`dof_based_pass1.jl`. Everything else (geometry preprocessing,
|
||||
integration, assembly, DOF mapping) belongs elsewhere.
|
||||
"""
|
||||
|
||||
@@ -29,7 +30,7 @@ optional density (carried on the kernel rather than the material so
|
||||
existing material structs stay untouched).
|
||||
|
||||
# Type Parameters
|
||||
- `Theory`: Continuum theory (FullThreeD, PlaneStress, PlaneStrain, Axisymmetric)
|
||||
- `Theory`: Continuum theory (ThreeDimensional, PlaneStress, PlaneStrain, Axisymmetric)
|
||||
- `Mat`: Material model (LinearElastic, NeoHookean, etc.)
|
||||
|
||||
# Fields
|
||||
@@ -45,7 +46,7 @@ existing material structs stay untouched).
|
||||
|
||||
```julia
|
||||
kernel = ContinuumKernel(
|
||||
ContinuumFormulation{FullThreeD}(),
|
||||
ContinuumFormulation{ThreeDimensional}(),
|
||||
LinearElastic(E=210e9, ν=0.3),
|
||||
Displacement{3}();
|
||||
density = 7850.0, # for mass matrix; omit for static-only
|
||||
@@ -223,6 +224,35 @@ keeps the entire chain inside the symmetric-tensor methods of
|
||||
return dcontract(B_k_α, dcontract(C, B_l_β))
|
||||
end
|
||||
|
||||
"""
|
||||
compute_internal_force_value(grad_i::Vec{3,F}, σ::SymmetricTensor{2,3,F}, α::Int) where {F}
|
||||
|
||||
Scalar factor for the Galerkin internal-force row of a displacement test function
|
||||
associated with shape function ``N_i`` (gradient ``\\nabla N_i``) and Cartesian
|
||||
component ``\\alpha``:
|
||||
|
||||
``\\sigma_{j\\alpha} \\, \\partial N_i / \\partial x_j``
|
||||
|
||||
(sum over ``j = 1\\ldots 3``). The caller multiplies by ``\\det J \\cdot w`` per
|
||||
quadrature point and accumulates over IPs and elements.
|
||||
|
||||
Cauchy stress ``\\sigma`` is the value stored in the material workspace at the IP
|
||||
(small-strain or finite-strain model, depending on the constitutive update).
|
||||
|
||||
Zero allocation.
|
||||
"""
|
||||
@inline function compute_internal_force_value(
|
||||
grad_i::Vec{3,F},
|
||||
σ::SymmetricTensor{2,3,F},
|
||||
α::Int,
|
||||
) where {F<:AbstractFloat}
|
||||
s = zero(F)
|
||||
@inbounds for j in 1:3
|
||||
s += grad_i[j] * σ[j, α]
|
||||
end
|
||||
return s
|
||||
end
|
||||
|
||||
"""
|
||||
compute_stiffness_block(
|
||||
grad_k::Vec{D},
|
||||
|
||||
@@ -101,7 +101,7 @@ function hex8_symmetric_uniaxial_eliminated_dirichlet(
|
||||
end
|
||||
|
||||
"""
|
||||
material_lab_linear_elastic_uniaxial_solve(mesh, handler, elements, E, ν, δx; formulation = ContinuumFormulation{FullThreeD}())
|
||||
material_lab_linear_elastic_uniaxial_solve(mesh, handler, elements, E, ν, δx; formulation = ContinuumFormulation{ThreeDimensional}())
|
||||
|
||||
Assemble `K`, apply [`hex8_symmetric_uniaxial_eliminated_dirichlet`](@ref), solve `K u = 0`
|
||||
with elimination lift, and return `u`.
|
||||
@@ -115,7 +115,7 @@ function material_lab_linear_elastic_uniaxial_solve(
|
||||
E::Float64,
|
||||
ν::Float64,
|
||||
δx::Float64;
|
||||
formulation = ContinuumFormulation{FullThreeD}(),
|
||||
formulation = ContinuumFormulation{ThreeDimensional}(),
|
||||
)
|
||||
material = LinearElastic(E = E, ν = ν)
|
||||
kernel = ContinuumKernel(formulation, material, Displacement{3}())
|
||||
|
||||
@@ -47,7 +47,7 @@ This is the first mixed kernel: `evaluate_entry` dispatches on
|
||||
using Tensors
|
||||
|
||||
using ..JuliaFEM: AbstractKernel, AbstractFormulation
|
||||
using ..JuliaFEM: ContinuumFormulation, FullThreeD, AbstractContinuumTheory
|
||||
using ..JuliaFEM: ContinuumFormulation, ThreeDimensional, AbstractContinuumTheory
|
||||
using ..JuliaFEM: AbstractMaterial, Displacement
|
||||
using ..JuliaFEM: AssemblyMaterialWorkspace, compute_stress
|
||||
import ..JuliaFEM: qpoint_buffer_eltype, update_qpoint_buffer!, evaluate_entry,
|
||||
@@ -63,7 +63,7 @@ Mixed `u`–`p` kernel: 3D vertex displacement (field 1) + scalar cell pressure
|
||||
(field 2). See the file-level docstring for the weak form.
|
||||
|
||||
# Fields
|
||||
- `formulation::ContinuumFormulation{Theory}` — geometric driver (`FullThreeD`, …)
|
||||
- `formulation::ContinuumFormulation{Theory}` — geometric driver (`ThreeDimensional`, …)
|
||||
- `material::Mat` — mechanical material (`LinearElastic`, …)
|
||||
- `inv_bulk::Float64` — `1/κ` for the `−κ⁻¹ ∫ p q dΩ` term (`0` = incompressible limit)
|
||||
|
||||
@@ -72,7 +72,7 @@ Mixed `u`–`p` kernel: 3D vertex displacement (field 1) + scalar cell pressure
|
||||
```julia
|
||||
S = @DOFSet{u::DOF{Displacement{3}, Vertex}, p::DOF{Float64, Cell}}
|
||||
kernel = MixedUPKernel(
|
||||
ContinuumFormulation{FullThreeD}(),
|
||||
ContinuumFormulation{ThreeDimensional}(),
|
||||
LinearElastic(E = 210e9, ν = 0.3),
|
||||
inv_bulk = 1.0 / (210e9 / 3), # order-of-magnitude compressible term
|
||||
)
|
||||
|
||||
@@ -42,7 +42,7 @@ Newtonian Stokes mixed kernel: `u` (vertex, three components) + scalar
|
||||
`p` on `Cell`. See the file-level docstring for the weak form.
|
||||
|
||||
# Fields
|
||||
- `formulation::ContinuumFormulation{Theory}` — geometric driver (`FullThreeD`, …)
|
||||
- `formulation::ContinuumFormulation{Theory}` — geometric driver (`ThreeDimensional`, …)
|
||||
- `μ::Float64` — dynamic viscosity (Stokes: `σ = 2μ ε(u)` with symmetric gradient `ε`)
|
||||
- `inv_bulk::Float64` — `1/κ` for the `−κ⁻¹ ∫ p q dΩ` term (`0` = incompressible)
|
||||
|
||||
@@ -50,7 +50,7 @@ Newtonian Stokes mixed kernel: `u` (vertex, three components) + scalar
|
||||
|
||||
```julia
|
||||
S = @DOFSet{u::DOF{Displacement{3}, Vertex}, p::DOF{Float64, Cell}}
|
||||
kernel = StokesMixedKernel(ContinuumFormulation{FullThreeD}(); μ = 1.0e-3, inv_bulk = 0.0)
|
||||
kernel = StokesMixedKernel(ContinuumFormulation{ThreeDimensional}(); μ = 1.0e-3, inv_bulk = 0.0)
|
||||
```
|
||||
"""
|
||||
struct StokesMixedKernel{Theory<:AbstractContinuumTheory} <: AbstractKernel
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
# This file is a part of JuliaFEM.
|
||||
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
|
||||
# SPDX-FileCopyrightText: 2015-2026 Jukka Aho
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
Concrete types for continuum mechanics formulations and theories.
|
||||
|
||||
Abstract types are in abstract.jl, implementations are in formulations.jl.
|
||||
Abstract types are in `abstract.jl`; concrete theory structs and
|
||||
`ContinuumFormulation` live in this file.
|
||||
|
||||
Must be included after abstract.jl.
|
||||
Must be included after `abstract.jl`.
|
||||
"""
|
||||
|
||||
# ============================================================================
|
||||
@@ -14,13 +15,15 @@ Must be included after abstract.jl.
|
||||
# ============================================================================
|
||||
|
||||
"""
|
||||
FullThreeD <: AbstractContinuumTheory
|
||||
ThreeDimensional <: AbstractContinuumTheory
|
||||
|
||||
Full 3D analysis with no simplifications. All six stress / flux components
|
||||
are carried; no geometric simplifications. Domain-agnostic — used by both
|
||||
`ContinuumKernel` (solid mechanics) and `HeatKernel` (heat conduction).
|
||||
Bulk three-dimensional model: no in-plane or axisymmetric reduction. All
|
||||
independent tensor components are retained at the quadrature point (six for
|
||||
symmetric mechanical stress / strain; three for isotropic flux, etc.).
|
||||
Domain-agnostic tag shared by `ContinuumKernel`, `HeatKernel`, Darcy-style
|
||||
kernels, and other `ContinuumFormulation{…}` drivers on 3D meshes.
|
||||
"""
|
||||
struct FullThreeD <: AbstractContinuumTheory end
|
||||
struct ThreeDimensional <: AbstractContinuumTheory end
|
||||
|
||||
"""
|
||||
PlaneStress <: AbstractContinuumTheory
|
||||
@@ -60,7 +63,7 @@ Used as a type tag inside `ContinuumKernel{Theory, Material, Field}`.
|
||||
# Examples
|
||||
|
||||
```julia
|
||||
ContinuumFormulation{FullThreeD}()
|
||||
ContinuumFormulation{ThreeDimensional}()
|
||||
ContinuumFormulation{PlaneStress}()
|
||||
ContinuumFormulation{PlaneStrain}()
|
||||
ContinuumFormulation{Axisymmetric}()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# This file is a part of JuliaFEM.
|
||||
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
|
||||
# SPDX-FileCopyrightText: 2015-2026 Jukka Aho
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
Geometry cache update functions for continuum elements.
|
||||
@@ -17,14 +17,24 @@ fields written are:
|
||||
|
||||
- `geometry_cache.X` — node coordinates (one entry per element node)
|
||||
- `geometry_cache.N_data[ip, k]` — basis values
|
||||
- `geometry_cache.∇N_data[ip, k]` — physical gradients `∇N`
|
||||
- `geometry_cache.detJ_w[ip]` — `det(J) * w` for integration
|
||||
- `geometry_cache.∇N_data[ip, k]` — physical gradients `∇N` as `Vec{3}` (tangent to the
|
||||
embedded surface for `dim(topology)==2`; all three components may be nonzero)
|
||||
- `geometry_cache.detJ_w[ip]` — `det(J) * w` for `D==3`, or `√(det G) · w` for `D==2`
|
||||
|
||||
The function is allocation-free; it reads topology, basis, and the
|
||||
integration points from `element_cache` and writes back into the
|
||||
pre-allocated `geometry_cache` arrays. For each integration point the
|
||||
Jacobian `J = X ⊗ ∇_ξ N` is built on the fly, then physical gradients
|
||||
are obtained via `J^{-T} · ∇_ξ N`.
|
||||
pre-allocated `geometry_cache` arrays.
|
||||
|
||||
For **`dim(topology) == 3`**, the Jacobian `J = Σ_k X_k ⊗ ∇_ξ N_k` is a `Tensor{2,3}`,
|
||||
inverted in the usual way, and `∇N` uses the full `Vec{3}` chain rule.
|
||||
|
||||
For **`dim(topology) == 2`**, node coordinates are `Vec{3}` but the isoparametric map
|
||||
`x(ξ, η) ∈ ℝ³` is only two-parameter. Let `v_α = ∂x/∂ξ^α = Σ_k X_k ∂N_k/∂ξ^α` for
|
||||
`α ∈ {1,2}` (columns of the `3 × 2` Jacobian), `G_{αβ} = v_α · v_β` (Gram matrix),
|
||||
`detJ_w = √(det G) · w` (surface area measure on the embedded patch), and
|
||||
`∇N_k = v_1 (G^{-1} ∂_ξ N_k)_1 + v_2 (G^{-1} ∂_ξ N_k)_2`. This reduces to the former
|
||||
`(x, y)` / `J_2^{-T}` formula when the element lies in the global `xy` plane.
|
||||
Degenerate `det(G) ≤ 0` throws `ArgumentError`.
|
||||
"""
|
||||
@inline function update_geometry_cache!(
|
||||
geometry_cache::GeometryCache,
|
||||
@@ -35,8 +45,6 @@ are obtained via `J^{-T} · ∇_ξ N`.
|
||||
conn = mesh.connectivity[elem_id]
|
||||
nnodes = length(conn)
|
||||
|
||||
# Extract node coordinates (mesh.nodes already contains Vec{3}).
|
||||
# Indexed loop avoids the iterator allocation that `enumerate` introduces.
|
||||
@inbounds for i in 1:nnodes
|
||||
node = conn[i]
|
||||
geometry_cache.X[i] = mesh.nodes[node]
|
||||
@@ -44,7 +52,9 @@ are obtained via `J^{-T} · ∇_ξ N`.
|
||||
|
||||
ips = element_cache.ips
|
||||
nips = length(ips)
|
||||
D = dim(element_cache.topology)
|
||||
|
||||
if D == 3
|
||||
@inbounds for ip_idx in 1:nips
|
||||
ip = ips[ip_idx]
|
||||
ξ = ip.coords
|
||||
@@ -52,7 +62,6 @@ are obtained via `J^{-T} · ∇_ξ N`.
|
||||
N_vals = get_basis_functions( element_cache.topology, element_cache.basis, ξ)
|
||||
dN_dξ = get_basis_derivatives(element_cache.topology, element_cache.basis, ξ)
|
||||
|
||||
# Jacobian J = X ⊗ ∇_ξ N
|
||||
J = geometry_cache.X[1] ⊗ dN_dξ[1]
|
||||
for i in 2:nnodes
|
||||
J += geometry_cache.X[i] ⊗ dN_dξ[i]
|
||||
@@ -67,6 +76,52 @@ are obtained via `J^{-T} · ∇_ξ N`.
|
||||
|
||||
geometry_cache.detJ_w[ip_idx] = det(J) * ip.weight
|
||||
end
|
||||
elseif D == 2
|
||||
F = geometry_eltype(geometry_cache)
|
||||
@inbounds for ip_idx in 1:nips
|
||||
ip = ips[ip_idx]
|
||||
ξ = ip.coords
|
||||
|
||||
N_vals = get_basis_functions( element_cache.topology, element_cache.basis, ξ)
|
||||
dN_dξ = get_basis_derivatives(element_cache.topology, element_cache.basis, ξ)
|
||||
|
||||
v1 = zero(Vec{3,F})
|
||||
v2 = zero(Vec{3,F})
|
||||
for i in 1:nnodes
|
||||
xk = geometry_cache.X[i]
|
||||
di = dN_dξ[i]
|
||||
v1 += xk * di[1]
|
||||
v2 += xk * di[2]
|
||||
end
|
||||
G11 = v1 ⋅ v1
|
||||
G12 = v1 ⋅ v2
|
||||
G22 = v2 ⋅ v2
|
||||
G = Tensor{2,2,F,4}((G11, G12, G12, G22))
|
||||
detG = det(G)
|
||||
if !(detG > zero(F))
|
||||
throw(ArgumentError(
|
||||
"update_geometry_cache!: singular or non-right-handed 2D element " *
|
||||
"(det(G) = $detG); check node ordering and geometry",
|
||||
))
|
||||
end
|
||||
Ginv = inv(G)
|
||||
detJ_w = sqrt(detG) * ip.weight
|
||||
|
||||
for k in 1:nnodes
|
||||
geometry_cache.N_data[ip_idx, k] = N_vals[k]
|
||||
h = Ginv ⋅ dN_dξ[k]
|
||||
g = v1 * h[1] + v2 * h[2]
|
||||
geometry_cache.∇N_data[ip_idx, k] = Vec{3,F}((g[1], g[2], g[3]))
|
||||
end
|
||||
|
||||
geometry_cache.detJ_w[ip_idx] = detJ_w
|
||||
end
|
||||
else
|
||||
throw(ArgumentError(
|
||||
"update_geometry_cache!: topology spatial dimension $D is not supported " *
|
||||
"(expected 2 or 3 for continuum assembly)",
|
||||
))
|
||||
end
|
||||
|
||||
return nothing
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# This file is a part of JuliaFEM.
|
||||
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
|
||||
# SPDX-FileCopyrightText: 2015-2026 Jukka Aho
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
Material cache update functions for continuum elements.
|
||||
@@ -10,6 +10,7 @@ Computes stress, tangent modulus, and internal state at integration points.
|
||||
using Tensors
|
||||
using ..JuliaFEM: GlobalMaterialCache, get_old_state, set_state!
|
||||
using ..JuliaFEM: continuum_kinematics, SmallStrainKinematics, GreenLagrangeKinematics
|
||||
using ..JuliaFEM: material_behavior, StatelessStrainDependent
|
||||
|
||||
# ============================================================================
|
||||
# GLOBAL MATERIAL CACHE — behavior-dispatched implementations
|
||||
@@ -126,6 +127,55 @@ end
|
||||
return nothing
|
||||
end
|
||||
|
||||
"""
|
||||
update_material_cache_stateless_strain!(
|
||||
material_workspace, geometry_cache, material, element_cache, Δt,
|
||||
) -> Nothing
|
||||
|
||||
Finite-strain / hyperelastic branch with **no** persistent integration-point
|
||||
state in [`GlobalMaterialCache`](@ref). Requires
|
||||
`material_behavior(material) isa StatelessStrainDependent`.
|
||||
|
||||
`element_cache.u_buffer` must hold the current nodal displacements (vertex
|
||||
ordering matching `geometry_cache.∇N_data`). Used by the DOF-based Pass 1
|
||||
when the configuration vector is supplied (or zero displacement when it
|
||||
is not).
|
||||
|
||||
Zero-allocation in the integration loop.
|
||||
"""
|
||||
@inline function update_material_cache_stateless_strain!(
|
||||
material_workspace::AssemblyMaterialWorkspace,
|
||||
geometry_cache::GeometryCache,
|
||||
material::AbstractMaterial,
|
||||
element_cache::ElementCache,
|
||||
Δt::Float64,
|
||||
)
|
||||
material_behavior(material) isa StatelessStrainDependent ||
|
||||
throw(ArgumentError("update_material_cache_stateless_strain! requires StatelessStrainDependent material"))
|
||||
nips = length(element_cache.ips)
|
||||
nnodes = length(geometry_cache.X)
|
||||
I = one(Tensor{2,3,Float64,9})
|
||||
|
||||
@inbounds for q in 1:nips
|
||||
F = I
|
||||
for k in 1:nnodes
|
||||
u_k = element_cache.u_buffer[k]
|
||||
∇N_k_q = geometry_cache.∇N_data[q, k]
|
||||
F += u_k ⊗ ∇N_k_q
|
||||
end
|
||||
|
||||
C_tensor = symmetric(F' ⋅ F)
|
||||
E = SymmetricTensor{2,3}(0.5 * (C_tensor - I))
|
||||
|
||||
σ, 𝔻, _ = compute_stress(material, E, NamedTuple(), Δt)
|
||||
|
||||
@inbounds material_workspace.fields[q] = (σ=σ, 𝔻=𝔻)
|
||||
material_workspace.states[q] = NamedTuple()
|
||||
end
|
||||
|
||||
return nothing
|
||||
end
|
||||
|
||||
# StatelessStrainDependent — strain at each IP, no persistent state.
|
||||
@inline function update_material_cache!(
|
||||
material_workspace::AssemblyMaterialWorkspace,
|
||||
@@ -137,30 +187,9 @@ end
|
||||
elem_id::Int,
|
||||
Δt::Float64,
|
||||
)
|
||||
nips = length(element_cache.ips)
|
||||
nnodes = length(geometry_cache.X)
|
||||
I = one(Tensor{2,3,Float64,9})
|
||||
|
||||
@inbounds for q in 1:nips
|
||||
# Deformation gradient F = I + ∇u
|
||||
F = I
|
||||
for k in 1:nnodes
|
||||
u_k = element_cache.u_buffer[k]
|
||||
∇N_k_q = geometry_cache.∇N_data[q, k]
|
||||
F += u_k ⊗ ∇N_k_q
|
||||
end
|
||||
|
||||
# Green–Lagrange strain E = ½(F'F − I)
|
||||
C_tensor = symmetric(F' ⋅ F)
|
||||
E = SymmetricTensor{2,3}(0.5 * (C_tensor - I))
|
||||
|
||||
σ, 𝔻, _ = compute_stress(material, E, NamedTuple(), 0.0)
|
||||
|
||||
@inbounds material_workspace.fields[q] = (σ=σ, 𝔻=𝔻)
|
||||
material_workspace.states[q] = NamedTuple()
|
||||
end
|
||||
|
||||
return nothing
|
||||
return update_material_cache_stateless_strain!(
|
||||
material_workspace, geometry_cache, material, element_cache, Δt,
|
||||
)
|
||||
end
|
||||
|
||||
# StatefulStrainDependent — read old state from `global_cache`, compute the
|
||||
|
||||
Reference in New Issue
Block a user