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.
|
# SPDX-FileCopyrightText: 2015-2026 Jukka Aho
|
||||||
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Abstract types for continuum mechanics domain.
|
Abstract types for continuum mechanics domain.
|
||||||
@@ -24,7 +24,7 @@ DESIGN PHILOSOPHY: Formulations are DOMAIN-AGNOSTIC dimensionality concepts.
|
|||||||
Formulation (domain-agnostic):
|
Formulation (domain-agnostic):
|
||||||
- Describes DIMENSIONALITY and geometric simplifications
|
- Describes DIMENSIONALITY and geometric simplifications
|
||||||
- Used by multiple physics domains
|
- Used by multiple physics domains
|
||||||
- Examples: FullThreeD, Axisymmetric
|
- Examples: ThreeDimensional, Axisymmetric
|
||||||
- Can be reused across continuum, heat, acoustics, etc.
|
- Can be reused across continuum, heat, acoustics, etc.
|
||||||
|
|
||||||
Theory (domain-specific):
|
Theory (domain-specific):
|
||||||
@@ -34,8 +34,8 @@ Theory (domain-specific):
|
|||||||
|
|
||||||
# Why Separate Them?
|
# Why Separate Them?
|
||||||
|
|
||||||
Problem: Heat transfer needs FullThreeD and Axisymmetric, just like continuum!
|
Problem: Heat transfer needs ThreeDimensional and Axisymmetric, just like continuum!
|
||||||
If FullThreeD is defined in domains/continuum/, heat can't use it without duplication.
|
If ThreeDimensional is defined in domains/continuum/, heat can't use it without duplication.
|
||||||
|
|
||||||
Solution: Formulations are dimensionality (shared), theories are physics (domain-specific).
|
Solution: Formulations are dimensionality (shared), theories are physics (domain-specific).
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ Solution: Formulations are dimensionality (shared), theories are physics (domain
|
|||||||
|
|
||||||
```julia
|
```julia
|
||||||
# Domain-agnostic formulations
|
# Domain-agnostic formulations
|
||||||
FullThreeD() # Used by: continuum, heat, poisson, acoustics
|
ThreeDimensional() # Used by: continuum, heat, poisson, acoustics
|
||||||
Axisymmetric() # Used by: continuum, heat, etc.
|
Axisymmetric() # Used by: continuum, heat, etc.
|
||||||
|
|
||||||
# Domain-specific theories (in domains/*/types.jl)
|
# Domain-specific theories (in domains/*/types.jl)
|
||||||
@@ -75,7 +75,7 @@ PlaneStrain (ε_xx, ε_yy, ε_xy, ε_zz = 0):
|
|||||||
- Out-of-plane strain ε_zz = 0
|
- Out-of-plane strain ε_zz = 0
|
||||||
- Examples: Dams, tunnels, retaining walls, long cylinders
|
- Examples: Dams, tunnels, retaining walls, long cylinders
|
||||||
|
|
||||||
FullThreeD:
|
ThreeDimensional:
|
||||||
- No simplifications, all six stress/strain components
|
- No simplifications, all six stress/strain components
|
||||||
- Most accurate but most expensive
|
- Most accurate but most expensive
|
||||||
|
|
||||||
@@ -97,6 +97,6 @@ Plane Strain (thick section):
|
|||||||
- Constitutive: 3×3 reduced stiffness matrix (different from plane stress!)
|
- Constitutive: 3×3 reduced stiffness matrix (different from plane stress!)
|
||||||
|
|
||||||
# See Also
|
# See Also
|
||||||
- Concrete theories: `continuum/types.jl` (FullThreeD, PlaneStress, PlaneStrain, Axisymmetric)
|
- Concrete theories: `continuum/types.jl` (ThreeDimensional, PlaneStress, PlaneStrain, Axisymmetric)
|
||||||
"""
|
"""
|
||||||
abstract type AbstractContinuumTheory end
|
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
|
```julia
|
||||||
S = @DOFSet{u::DOF{Displacement{3}, Vertex}, σ::DOF{SymmetricTensor{2,3}, Cell}}
|
S = @DOFSet{u::DOF{Displacement{3}, Vertex}, σ::DOF{SymmetricTensor{2,3}, Cell}}
|
||||||
kernel = HellingerReissnerKernel(
|
kernel = HellingerReissnerKernel(
|
||||||
ContinuumFormulation{FullThreeD}(),
|
ContinuumFormulation{ThreeDimensional}(),
|
||||||
LinearElastic(E = 210e9, ν = 0.3),
|
LinearElastic(E = 210e9, ν = 0.3),
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ S = @DOFSet{
|
|||||||
sig::DOF{SymmetricTensor{2,3}, Cell},
|
sig::DOF{SymmetricTensor{2,3}, Cell},
|
||||||
}
|
}
|
||||||
kernel = HuWashizuKernel(
|
kernel = HuWashizuKernel(
|
||||||
ContinuumFormulation{FullThreeD}(),
|
ContinuumFormulation{ThreeDimensional}(),
|
||||||
LinearElastic(E = 210e9, ν = 0.3),
|
LinearElastic(E = 210e9, ν = 0.3),
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# This file is a part of JuliaFEM.
|
# SPDX-FileCopyrightText: 2015-2026 Jukka Aho
|
||||||
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Continuum mechanics kernel - defines the weak form only.
|
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)
|
3. Block builder: compute_stiffness_block (builds D×D blocks)
|
||||||
|
|
||||||
The DOF-based / matrix-free assembler microkernel surface
|
The DOF-based / matrix-free assembler microkernel surface
|
||||||
(`qpoint_buffer_eltype`, `update_qpoint_buffer!`, `evaluate_entry`,
|
(`qpoint_buffer_eltype`, `prepare_dof_based_material_workspace!`,
|
||||||
`evaluate_mass_entry`, `reference_fields`) is implemented further
|
`update_qpoint_buffer!`, `evaluate_entry`, `evaluate_mass_entry`,
|
||||||
down in this file. Everything else (geometry preprocessing,
|
`reference_fields`) is implemented in this file and in
|
||||||
|
`dof_based_pass1.jl`. Everything else (geometry preprocessing,
|
||||||
integration, assembly, DOF mapping) belongs elsewhere.
|
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).
|
existing material structs stay untouched).
|
||||||
|
|
||||||
# Type Parameters
|
# Type Parameters
|
||||||
- `Theory`: Continuum theory (FullThreeD, PlaneStress, PlaneStrain, Axisymmetric)
|
- `Theory`: Continuum theory (ThreeDimensional, PlaneStress, PlaneStrain, Axisymmetric)
|
||||||
- `Mat`: Material model (LinearElastic, NeoHookean, etc.)
|
- `Mat`: Material model (LinearElastic, NeoHookean, etc.)
|
||||||
|
|
||||||
# Fields
|
# Fields
|
||||||
@@ -45,7 +46,7 @@ existing material structs stay untouched).
|
|||||||
|
|
||||||
```julia
|
```julia
|
||||||
kernel = ContinuumKernel(
|
kernel = ContinuumKernel(
|
||||||
ContinuumFormulation{FullThreeD}(),
|
ContinuumFormulation{ThreeDimensional}(),
|
||||||
LinearElastic(E=210e9, ν=0.3),
|
LinearElastic(E=210e9, ν=0.3),
|
||||||
Displacement{3}();
|
Displacement{3}();
|
||||||
density = 7850.0, # for mass matrix; omit for static-only
|
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_β))
|
return dcontract(B_k_α, dcontract(C, B_l_β))
|
||||||
end
|
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(
|
compute_stiffness_block(
|
||||||
grad_k::Vec{D},
|
grad_k::Vec{D},
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ function hex8_symmetric_uniaxial_eliminated_dirichlet(
|
|||||||
end
|
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`
|
Assemble `K`, apply [`hex8_symmetric_uniaxial_eliminated_dirichlet`](@ref), solve `K u = 0`
|
||||||
with elimination lift, and return `u`.
|
with elimination lift, and return `u`.
|
||||||
@@ -115,7 +115,7 @@ function material_lab_linear_elastic_uniaxial_solve(
|
|||||||
E::Float64,
|
E::Float64,
|
||||||
ν::Float64,
|
ν::Float64,
|
||||||
δx::Float64;
|
δx::Float64;
|
||||||
formulation = ContinuumFormulation{FullThreeD}(),
|
formulation = ContinuumFormulation{ThreeDimensional}(),
|
||||||
)
|
)
|
||||||
material = LinearElastic(E = E, ν = ν)
|
material = LinearElastic(E = E, ν = ν)
|
||||||
kernel = ContinuumKernel(formulation, material, Displacement{3}())
|
kernel = ContinuumKernel(formulation, material, Displacement{3}())
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ This is the first mixed kernel: `evaluate_entry` dispatches on
|
|||||||
using Tensors
|
using Tensors
|
||||||
|
|
||||||
using ..JuliaFEM: AbstractKernel, AbstractFormulation
|
using ..JuliaFEM: AbstractKernel, AbstractFormulation
|
||||||
using ..JuliaFEM: ContinuumFormulation, FullThreeD, AbstractContinuumTheory
|
using ..JuliaFEM: ContinuumFormulation, ThreeDimensional, AbstractContinuumTheory
|
||||||
using ..JuliaFEM: AbstractMaterial, Displacement
|
using ..JuliaFEM: AbstractMaterial, Displacement
|
||||||
using ..JuliaFEM: AssemblyMaterialWorkspace, compute_stress
|
using ..JuliaFEM: AssemblyMaterialWorkspace, compute_stress
|
||||||
import ..JuliaFEM: qpoint_buffer_eltype, update_qpoint_buffer!, evaluate_entry,
|
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.
|
(field 2). See the file-level docstring for the weak form.
|
||||||
|
|
||||||
# Fields
|
# Fields
|
||||||
- `formulation::ContinuumFormulation{Theory}` — geometric driver (`FullThreeD`, …)
|
- `formulation::ContinuumFormulation{Theory}` — geometric driver (`ThreeDimensional`, …)
|
||||||
- `material::Mat` — mechanical material (`LinearElastic`, …)
|
- `material::Mat` — mechanical material (`LinearElastic`, …)
|
||||||
- `inv_bulk::Float64` — `1/κ` for the `−κ⁻¹ ∫ p q dΩ` term (`0` = incompressible limit)
|
- `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
|
```julia
|
||||||
S = @DOFSet{u::DOF{Displacement{3}, Vertex}, p::DOF{Float64, Cell}}
|
S = @DOFSet{u::DOF{Displacement{3}, Vertex}, p::DOF{Float64, Cell}}
|
||||||
kernel = MixedUPKernel(
|
kernel = MixedUPKernel(
|
||||||
ContinuumFormulation{FullThreeD}(),
|
ContinuumFormulation{ThreeDimensional}(),
|
||||||
LinearElastic(E = 210e9, ν = 0.3),
|
LinearElastic(E = 210e9, ν = 0.3),
|
||||||
inv_bulk = 1.0 / (210e9 / 3), # order-of-magnitude compressible term
|
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.
|
`p` on `Cell`. See the file-level docstring for the weak form.
|
||||||
|
|
||||||
# Fields
|
# Fields
|
||||||
- `formulation::ContinuumFormulation{Theory}` — geometric driver (`FullThreeD`, …)
|
- `formulation::ContinuumFormulation{Theory}` — geometric driver (`ThreeDimensional`, …)
|
||||||
- `μ::Float64` — dynamic viscosity (Stokes: `σ = 2μ ε(u)` with symmetric gradient `ε`)
|
- `μ::Float64` — dynamic viscosity (Stokes: `σ = 2μ ε(u)` with symmetric gradient `ε`)
|
||||||
- `inv_bulk::Float64` — `1/κ` for the `−κ⁻¹ ∫ p q dΩ` term (`0` = incompressible)
|
- `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
|
```julia
|
||||||
S = @DOFSet{u::DOF{Displacement{3}, Vertex}, p::DOF{Float64, Cell}}
|
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
|
struct StokesMixedKernel{Theory<:AbstractContinuumTheory} <: AbstractKernel
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
# This file is a part of JuliaFEM.
|
# SPDX-FileCopyrightText: 2015-2026 Jukka Aho
|
||||||
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Concrete types for continuum mechanics formulations and theories.
|
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
|
Bulk three-dimensional model: no in-plane or axisymmetric reduction. All
|
||||||
are carried; no geometric simplifications. Domain-agnostic — used by both
|
independent tensor components are retained at the quadrature point (six for
|
||||||
`ContinuumKernel` (solid mechanics) and `HeatKernel` (heat conduction).
|
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
|
PlaneStress <: AbstractContinuumTheory
|
||||||
@@ -60,7 +63,7 @@ Used as a type tag inside `ContinuumKernel{Theory, Material, Field}`.
|
|||||||
# Examples
|
# Examples
|
||||||
|
|
||||||
```julia
|
```julia
|
||||||
ContinuumFormulation{FullThreeD}()
|
ContinuumFormulation{ThreeDimensional}()
|
||||||
ContinuumFormulation{PlaneStress}()
|
ContinuumFormulation{PlaneStress}()
|
||||||
ContinuumFormulation{PlaneStrain}()
|
ContinuumFormulation{PlaneStrain}()
|
||||||
ContinuumFormulation{Axisymmetric}()
|
ContinuumFormulation{Axisymmetric}()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# This file is a part of JuliaFEM.
|
# SPDX-FileCopyrightText: 2015-2026 Jukka Aho
|
||||||
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Geometry cache update functions for continuum elements.
|
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.X` — node coordinates (one entry per element node)
|
||||||
- `geometry_cache.N_data[ip, k]` — basis values
|
- `geometry_cache.N_data[ip, k]` — basis values
|
||||||
- `geometry_cache.∇N_data[ip, k]` — physical gradients `∇N`
|
- `geometry_cache.∇N_data[ip, k]` — physical gradients `∇N` as `Vec{3}` (tangent to the
|
||||||
- `geometry_cache.detJ_w[ip]` — `det(J) * w` for integration
|
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
|
The function is allocation-free; it reads topology, basis, and the
|
||||||
integration points from `element_cache` and writes back into the
|
integration points from `element_cache` and writes back into the
|
||||||
pre-allocated `geometry_cache` arrays. For each integration point the
|
pre-allocated `geometry_cache` arrays.
|
||||||
Jacobian `J = X ⊗ ∇_ξ N` is built on the fly, then physical gradients
|
|
||||||
are obtained via `J^{-T} · ∇_ξ N`.
|
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!(
|
@inline function update_geometry_cache!(
|
||||||
geometry_cache::GeometryCache,
|
geometry_cache::GeometryCache,
|
||||||
@@ -35,8 +45,6 @@ are obtained via `J^{-T} · ∇_ξ N`.
|
|||||||
conn = mesh.connectivity[elem_id]
|
conn = mesh.connectivity[elem_id]
|
||||||
nnodes = length(conn)
|
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
|
@inbounds for i in 1:nnodes
|
||||||
node = conn[i]
|
node = conn[i]
|
||||||
geometry_cache.X[i] = mesh.nodes[node]
|
geometry_cache.X[i] = mesh.nodes[node]
|
||||||
@@ -44,28 +52,75 @@ are obtained via `J^{-T} · ∇_ξ N`.
|
|||||||
|
|
||||||
ips = element_cache.ips
|
ips = element_cache.ips
|
||||||
nips = length(ips)
|
nips = length(ips)
|
||||||
|
D = dim(element_cache.topology)
|
||||||
|
|
||||||
@inbounds for ip_idx in 1:nips
|
if D == 3
|
||||||
ip = ips[ip_idx]
|
@inbounds for ip_idx in 1:nips
|
||||||
ξ = ip.coords
|
ip = ips[ip_idx]
|
||||||
|
ξ = ip.coords
|
||||||
|
|
||||||
N_vals = get_basis_functions( element_cache.topology, element_cache.basis, ξ)
|
N_vals = get_basis_functions( element_cache.topology, element_cache.basis, ξ)
|
||||||
dN_dξ = get_basis_derivatives(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]
|
||||||
J = geometry_cache.X[1] ⊗ dN_dξ[1]
|
for i in 2:nnodes
|
||||||
for i in 2:nnodes
|
J += geometry_cache.X[i] ⊗ dN_dξ[i]
|
||||||
J += geometry_cache.X[i] ⊗ dN_dξ[i]
|
end
|
||||||
|
|
||||||
|
J_inv_T = transpose(inv(J))
|
||||||
|
|
||||||
|
for k in 1:nnodes
|
||||||
|
geometry_cache.N_data[ip_idx, k] = N_vals[k]
|
||||||
|
geometry_cache.∇N_data[ip_idx, k] = J_inv_T ⋅ dN_dξ[k]
|
||||||
|
end
|
||||||
|
|
||||||
|
geometry_cache.detJ_w[ip_idx] = det(J) * ip.weight
|
||||||
end
|
end
|
||||||
|
elseif D == 2
|
||||||
|
F = geometry_eltype(geometry_cache)
|
||||||
|
@inbounds for ip_idx in 1:nips
|
||||||
|
ip = ips[ip_idx]
|
||||||
|
ξ = ip.coords
|
||||||
|
|
||||||
J_inv_T = transpose(inv(J))
|
N_vals = get_basis_functions( element_cache.topology, element_cache.basis, ξ)
|
||||||
|
dN_dξ = get_basis_derivatives(element_cache.topology, element_cache.basis, ξ)
|
||||||
|
|
||||||
for k in 1:nnodes
|
v1 = zero(Vec{3,F})
|
||||||
geometry_cache.N_data[ip_idx, k] = N_vals[k]
|
v2 = zero(Vec{3,F})
|
||||||
geometry_cache.∇N_data[ip_idx, k] = J_inv_T ⋅ dN_dξ[k]
|
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
|
end
|
||||||
|
else
|
||||||
geometry_cache.detJ_w[ip_idx] = det(J) * ip.weight
|
throw(ArgumentError(
|
||||||
|
"update_geometry_cache!: topology spatial dimension $D is not supported " *
|
||||||
|
"(expected 2 or 3 for continuum assembly)",
|
||||||
|
))
|
||||||
end
|
end
|
||||||
|
|
||||||
return nothing
|
return nothing
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# This file is a part of JuliaFEM.
|
# SPDX-FileCopyrightText: 2015-2026 Jukka Aho
|
||||||
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Material cache update functions for continuum elements.
|
Material cache update functions for continuum elements.
|
||||||
@@ -10,6 +10,7 @@ Computes stress, tangent modulus, and internal state at integration points.
|
|||||||
using Tensors
|
using Tensors
|
||||||
using ..JuliaFEM: GlobalMaterialCache, get_old_state, set_state!
|
using ..JuliaFEM: GlobalMaterialCache, get_old_state, set_state!
|
||||||
using ..JuliaFEM: continuum_kinematics, SmallStrainKinematics, GreenLagrangeKinematics
|
using ..JuliaFEM: continuum_kinematics, SmallStrainKinematics, GreenLagrangeKinematics
|
||||||
|
using ..JuliaFEM: material_behavior, StatelessStrainDependent
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# GLOBAL MATERIAL CACHE — behavior-dispatched implementations
|
# GLOBAL MATERIAL CACHE — behavior-dispatched implementations
|
||||||
@@ -126,6 +127,55 @@ end
|
|||||||
return nothing
|
return nothing
|
||||||
end
|
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.
|
# StatelessStrainDependent — strain at each IP, no persistent state.
|
||||||
@inline function update_material_cache!(
|
@inline function update_material_cache!(
|
||||||
material_workspace::AssemblyMaterialWorkspace,
|
material_workspace::AssemblyMaterialWorkspace,
|
||||||
@@ -137,30 +187,9 @@ end
|
|||||||
elem_id::Int,
|
elem_id::Int,
|
||||||
Δt::Float64,
|
Δt::Float64,
|
||||||
)
|
)
|
||||||
nips = length(element_cache.ips)
|
return update_material_cache_stateless_strain!(
|
||||||
nnodes = length(geometry_cache.X)
|
material_workspace, geometry_cache, material, element_cache, Δt,
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# StatefulStrainDependent — read old state from `global_cache`, compute the
|
# StatefulStrainDependent — read old state from `global_cache`, compute the
|
||||||
|
|||||||
Reference in New Issue
Block a user