mirror of
https://github.com/JuliaFEM/JuliaFEM.jl.git
synced 2026-09-30 05:19:19 +00:00
363 lines
11 KiB
Julia
363 lines
11 KiB
Julia
|
|
# This file is a part of JuliaFEM.
|
|||
|
|
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
|
|||
|
|
|
|||
|
|
"""
|
|||
|
|
Material cache for storing state variables at integration points.
|
|||
|
|
|
|||
|
|
Uses the compositional state variable trait system to automatically construct
|
|||
|
|
caches based on material requirements.
|
|||
|
|
|
|||
|
|
# Design Philosophy
|
|||
|
|
|
|||
|
|
1. **Automatic construction**: Cache structure inferred from material traits
|
|||
|
|
2. **Type-stable**: NamedTuple-based storage with compile-time known types
|
|||
|
|
3. **Zero-allocation**: Immutable state updates (functional style)
|
|||
|
|
4. **Compositional**: State variables are independent, combinable building blocks
|
|||
|
|
|
|||
|
|
# Example
|
|||
|
|
|
|||
|
|
```julia
|
|||
|
|
# For LinearElastic (stateless)
|
|||
|
|
material = LinearElastic(E=210e9, ν=0.3)
|
|||
|
|
cache = create_global_material_cache(material, n_ips=8, n_elems=100)
|
|||
|
|
# → GlobalMaterialCache{NamedTuple{(),Tuple{}}} with empty states
|
|||
|
|
|
|||
|
|
# For PerfectPlasticity (stateful)
|
|||
|
|
material = PerfectPlasticity(E=210e9, ν=0.3, σ_y=250e6, H=1e9)
|
|||
|
|
cache = create_global_material_cache(material, n_ips=8, n_elems=100)
|
|||
|
|
# → GlobalMaterialCache{NamedTuple{(:ε_p, :α, :κ), Tuple{...}}} with state variables
|
|||
|
|
```
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
using Tensors
|
|||
|
|
|
|||
|
|
# ============================================================================
|
|||
|
|
# MATERIAL CACHE STRUCTURE
|
|||
|
|
# ============================================================================
|
|||
|
|
|
|||
|
|
"""
|
|||
|
|
GlobalMaterialCache{StateType}
|
|||
|
|
|
|||
|
|
Global material state cache for all integration points across all elements.
|
|||
|
|
|
|||
|
|
Stores material state variables using the compositional state variable system.
|
|||
|
|
Used for time-stepping and persistent state storage.
|
|||
|
|
|
|||
|
|
# Type Parameters
|
|||
|
|
- `StateType`: NamedTuple type for state variables (inferred from material)
|
|||
|
|
|
|||
|
|
# Fields
|
|||
|
|
- `states::Matrix{StateType}`: State at each IP and element [nips, nelems]
|
|||
|
|
- `states_old::Matrix{StateType}`: State from previous time step [nips, nelems]
|
|||
|
|
|
|||
|
|
# Design
|
|||
|
|
|
|||
|
|
The cache uses NamedTuples to store state variables, where:
|
|||
|
|
- Keys are state variable symbols (e.g., `:ε_p`, `:α`, `:κ`)
|
|||
|
|
- Values are concrete types (e.g., `SymmetricTensor{2,3,Float64,6}`, `Float64`)
|
|||
|
|
|
|||
|
|
For stateless materials, `StateType = NamedTuple{(),Tuple{}}` (empty).
|
|||
|
|
|
|||
|
|
# Example
|
|||
|
|
|
|||
|
|
```julia
|
|||
|
|
# Stateful material (plasticity)
|
|||
|
|
StateType = NamedTuple{(:ε_p, :α, :κ), Tuple{SymmetricTensor{2,3,Float64,6}, SymmetricTensor{2,3,Float64,6}, Float64}}
|
|||
|
|
cache = GlobalMaterialCache{StateType}(n_ips, n_elems)
|
|||
|
|
cache.states[q, elem_id] # → (ε_p=..., α=..., κ=...)
|
|||
|
|
|
|||
|
|
# Stateless material (linear elastic)
|
|||
|
|
StateType = NamedTuple{(),Tuple{}}
|
|||
|
|
cache = GlobalMaterialCache{StateType}(n_ips, n_elems)
|
|||
|
|
cache.states[q, elem_id] # → NamedTuple()
|
|||
|
|
```
|
|||
|
|
"""
|
|||
|
|
struct GlobalMaterialCache{StateType<:NamedTuple}
|
|||
|
|
states::Matrix{StateType}
|
|||
|
|
states_old::Matrix{StateType}
|
|||
|
|
|
|||
|
|
function GlobalMaterialCache{StateType}(n_ips::Int, n_elems::Int) where {StateType<:NamedTuple}
|
|||
|
|
# Initialize with zero/default states
|
|||
|
|
zero_state = create_zero_state(StateType)
|
|||
|
|
states = [zero_state for _ in 1:n_ips, _ in 1:n_elems]
|
|||
|
|
states_old = [zero_state for _ in 1:n_ips, _ in 1:n_elems]
|
|||
|
|
new{StateType}(states, states_old)
|
|||
|
|
end
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
"""
|
|||
|
|
create_zero_state(::Type{StateType}) where {StateType<:NamedTuple}
|
|||
|
|
|
|||
|
|
Create a zero-initialized state of the given NamedTuple type.
|
|||
|
|
|
|||
|
|
# Example
|
|||
|
|
```julia
|
|||
|
|
StateType = NamedTuple{(:ε_p, :α, :κ), Tuple{SymmetricTensor{2,3,Float64,6}, SymmetricTensor{2,3,Float64,6}, Float64}}
|
|||
|
|
zero_state = create_zero_state(StateType)
|
|||
|
|
# → (ε_p=zero(SymmetricTensor{2,3}), α=zero(SymmetricTensor{2,3}), κ=0.0)
|
|||
|
|
```
|
|||
|
|
"""
|
|||
|
|
function create_zero_state(::Type{StateType}) where {StateType<:NamedTuple}
|
|||
|
|
# Get field names and types
|
|||
|
|
field_names = fieldnames(StateType)
|
|||
|
|
field_types = [fieldtype(StateType, name) for name in field_names]
|
|||
|
|
|
|||
|
|
if isempty(field_names)
|
|||
|
|
return NamedTuple()
|
|||
|
|
else
|
|||
|
|
# Create zero value for each field
|
|||
|
|
zero_values = [zero(T) for T in field_types]
|
|||
|
|
return NamedTuple{field_names}(zero_values)
|
|||
|
|
end
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
# ============================================================================
|
|||
|
|
# CACHE CONSTRUCTION FROM MATERIAL TRAITS
|
|||
|
|
# ============================================================================
|
|||
|
|
|
|||
|
|
"""
|
|||
|
|
material_state_type(material::AbstractMaterial) -> Type{<:NamedTuple}
|
|||
|
|
|
|||
|
|
Infer state NamedTuple type from material's required state variables.
|
|||
|
|
|
|||
|
|
Uses the compositional trait system:
|
|||
|
|
1. Query `required_state_variables(material)` → tuple of state variable types
|
|||
|
|
2. For each state variable, query `state_variable_type(var)` → concrete type
|
|||
|
|
3. For each state variable, query `default_symbol(var)` → symbol name
|
|||
|
|
4. Construct NamedTuple type from symbols and concrete types
|
|||
|
|
|
|||
|
|
# Example
|
|||
|
|
```julia
|
|||
|
|
# LinearElastic (stateless)
|
|||
|
|
material = LinearElastic(E=210e9, ν=0.3)
|
|||
|
|
StateType = material_state_type(material)
|
|||
|
|
# → NamedTuple{(),Tuple{}}
|
|||
|
|
|
|||
|
|
# PerfectPlasticity (stateful)
|
|||
|
|
material = PerfectPlasticity(E=210e9, ν=0.3, σ_y=250e6, H=1e9)
|
|||
|
|
StateType = material_state_type(material)
|
|||
|
|
# → NamedTuple{(:ε_p, :α, :κ), Tuple{SymmetricTensor{2,3,Float64,6}, SymmetricTensor{2,3,Float64,6}, Float64}}
|
|||
|
|
```
|
|||
|
|
"""
|
|||
|
|
function material_state_type(material::AbstractMaterial)
|
|||
|
|
# Query required state variables from material instance
|
|||
|
|
vars = required_state_variables(material)
|
|||
|
|
|
|||
|
|
if isempty(vars)
|
|||
|
|
# Stateless material
|
|||
|
|
return NamedTuple{(),Tuple{}}
|
|||
|
|
else
|
|||
|
|
# Stateful material - build NamedTuple type
|
|||
|
|
symbols = tuple([default_symbol(var) for var in vars]...)
|
|||
|
|
types = tuple([state_variable_type(var) for var in vars]...)
|
|||
|
|
|
|||
|
|
return NamedTuple{symbols, Tuple{types...}}
|
|||
|
|
end
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
"""
|
|||
|
|
create_global_material_cache(material::AbstractMaterial; n_ips::Int, n_elems::Int)
|
|||
|
|
|
|||
|
|
Create global material cache for the given material, integration points, and elements.
|
|||
|
|
|
|||
|
|
Automatically infers the correct cache structure from material traits.
|
|||
|
|
|
|||
|
|
# Arguments
|
|||
|
|
- `material`: Material model instance
|
|||
|
|
- `n_ips`: Number of integration points per element
|
|||
|
|
- `n_elems`: Number of elements
|
|||
|
|
|
|||
|
|
# Returns
|
|||
|
|
`GlobalMaterialCache{StateType}` where `StateType` is inferred from material
|
|||
|
|
|
|||
|
|
# Example
|
|||
|
|
```julia
|
|||
|
|
# Stateless material
|
|||
|
|
mat = LinearElastic(E=210e9, ν=0.3)
|
|||
|
|
cache = create_global_material_cache(mat, n_ips=8, n_elems=100)
|
|||
|
|
# → GlobalMaterialCache{NamedTuple{(),Tuple{}}}
|
|||
|
|
|
|||
|
|
# Stateful material
|
|||
|
|
mat = PerfectPlasticity(E=210e9, ν=0.3, σ_y=250e6, H=1e9)
|
|||
|
|
cache = create_global_material_cache(mat, n_ips=8, n_elems=100)
|
|||
|
|
# → GlobalMaterialCache{NamedTuple{(:ε_p, :α, :κ), Tuple{...}}}
|
|||
|
|
```
|
|||
|
|
"""
|
|||
|
|
function create_global_material_cache(material::AbstractMaterial; n_ips::Int, n_elems::Int)
|
|||
|
|
StateType = material_state_type(material)
|
|||
|
|
return GlobalMaterialCache{StateType}(n_ips, n_elems)
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
# ============================================================================
|
|||
|
|
# CACHE ACCESS AND UPDATE FUNCTIONS
|
|||
|
|
# ============================================================================
|
|||
|
|
|
|||
|
|
"""
|
|||
|
|
get_state(cache::GlobalMaterialCache, ip::Int, elem_id::Int) -> NamedTuple
|
|||
|
|
|
|||
|
|
Get current state at integration point for element.
|
|||
|
|
|
|||
|
|
# Arguments
|
|||
|
|
- `cache`: Global material cache
|
|||
|
|
- `ip`: Integration point index (1-based)
|
|||
|
|
- `elem_id`: Element ID (1-based)
|
|||
|
|
|
|||
|
|
# Returns
|
|||
|
|
NamedTuple of state variables (empty for stateless materials)
|
|||
|
|
"""
|
|||
|
|
@inline function get_state(cache::GlobalMaterialCache, ip::Int, elem_id::Int)
|
|||
|
|
return cache.states[ip, elem_id]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
"""
|
|||
|
|
get_old_state(cache::GlobalMaterialCache, ip::Int, elem_id::Int) -> NamedTuple
|
|||
|
|
|
|||
|
|
Get state from previous time step at integration point for element.
|
|||
|
|
|
|||
|
|
# Arguments
|
|||
|
|
- `cache`: Global material cache
|
|||
|
|
- `ip`: Integration point index (1-based)
|
|||
|
|
- `elem_id`: Element ID (1-based)
|
|||
|
|
|
|||
|
|
# Returns
|
|||
|
|
NamedTuple of state variables from previous step
|
|||
|
|
"""
|
|||
|
|
@inline function get_old_state(cache::GlobalMaterialCache, ip::Int, elem_id::Int)
|
|||
|
|
@inbounds return cache.states_old[ip, elem_id]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
"""
|
|||
|
|
set_state!(cache::GlobalMaterialCache, ip::Int, elem_id::Int, state::NamedTuple)
|
|||
|
|
|
|||
|
|
Update current state at integration point for element (in-place).
|
|||
|
|
|
|||
|
|
# Arguments
|
|||
|
|
- `cache`: Global material cache
|
|||
|
|
- `ip`: Integration point index (1-based)
|
|||
|
|
- `elem_id`: Element ID (1-based)
|
|||
|
|
- `state`: New state NamedTuple
|
|||
|
|
|
|||
|
|
# Note
|
|||
|
|
This is an in-place operation.
|
|||
|
|
"""
|
|||
|
|
@inline function set_state!(cache::GlobalMaterialCache, ip::Int, elem_id::Int, state::NamedTuple)
|
|||
|
|
@inbounds cache.states[ip, elem_id] = state
|
|||
|
|
return nothing
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
"""
|
|||
|
|
update_cache!(cache::GlobalMaterialCache)
|
|||
|
|
|
|||
|
|
Copy current states to states_old (for time stepping).
|
|||
|
|
|
|||
|
|
Call this at the beginning of each time/load step to save the converged
|
|||
|
|
state from the previous step.
|
|||
|
|
|
|||
|
|
# Example
|
|||
|
|
```julia
|
|||
|
|
# At the start of a new time step
|
|||
|
|
update_cache!(cache)
|
|||
|
|
|
|||
|
|
# Now solve for new states
|
|||
|
|
for elem_id in 1:n_elems
|
|||
|
|
for ip in 1:n_ips
|
|||
|
|
state_old = get_old_state(cache, ip, elem_id)
|
|||
|
|
# ... solve for state_new ...
|
|||
|
|
set_state!(cache, ip, elem_id, state_new)
|
|||
|
|
end
|
|||
|
|
end
|
|||
|
|
```
|
|||
|
|
"""
|
|||
|
|
function update_cache!(cache::GlobalMaterialCache)
|
|||
|
|
cache.states_old .= cache.states
|
|||
|
|
return nothing
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
"""
|
|||
|
|
reset_cache!(cache::GlobalMaterialCache)
|
|||
|
|
|
|||
|
|
Reset all states to zero (for restarting analysis).
|
|||
|
|
|
|||
|
|
# Example
|
|||
|
|
```julia
|
|||
|
|
reset_cache!(cache)
|
|||
|
|
# All states now zero-initialized
|
|||
|
|
```
|
|||
|
|
"""
|
|||
|
|
function reset_cache!(cache::GlobalMaterialCache{StateType}) where {StateType}
|
|||
|
|
zero_state = create_zero_state(StateType)
|
|||
|
|
fill!(cache.states, zero_state)
|
|||
|
|
fill!(cache.states_old, zero_state)
|
|||
|
|
return nothing
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
# ============================================================================
|
|||
|
|
# HELPER FUNCTIONS FOR STATE VARIABLE ACCESS
|
|||
|
|
# ============================================================================
|
|||
|
|
|
|||
|
|
"""
|
|||
|
|
get_state_variable(state::NamedTuple, var_type::Type{<:AbstractStateVariable})
|
|||
|
|
|
|||
|
|
Extract a specific state variable from a state NamedTuple.
|
|||
|
|
|
|||
|
|
# Arguments
|
|||
|
|
- `state`: State NamedTuple (e.g., from `get_state(cache, ip)`)
|
|||
|
|
- `var_type`: State variable type (e.g., `PlasticStrain`)
|
|||
|
|
|
|||
|
|
# Returns
|
|||
|
|
Value of the requested state variable
|
|||
|
|
|
|||
|
|
# Example
|
|||
|
|
```julia
|
|||
|
|
state = get_state(cache, 1) # → (ε_p=..., α=..., κ=...)
|
|||
|
|
ε_p = get_state_variable(state, PlasticStrain)
|
|||
|
|
α = get_state_variable(state, Backstress)
|
|||
|
|
κ = get_state_variable(state, EquivalentPlasticStrain)
|
|||
|
|
```
|
|||
|
|
"""
|
|||
|
|
@generated function get_state_variable(state::StateType, var_type::Type{VarType}) where {StateType<:NamedTuple, VarType<:AbstractStateVariable}
|
|||
|
|
# Get the symbol for this state variable
|
|||
|
|
sym = default_symbol(VarType)
|
|||
|
|
|
|||
|
|
# Check if state has this field
|
|||
|
|
if hasfield(StateType, sym)
|
|||
|
|
return :(state.$sym)
|
|||
|
|
else
|
|||
|
|
error("State type $StateType does not have field $sym for variable $VarType")
|
|||
|
|
end
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
"""
|
|||
|
|
set_state_variable(state::NamedTuple, var_type::Type{<:AbstractStateVariable}, value)
|
|||
|
|
|
|||
|
|
Create new state NamedTuple with updated variable value.
|
|||
|
|
|
|||
|
|
Returns a new NamedTuple (immutable update).
|
|||
|
|
|
|||
|
|
# Arguments
|
|||
|
|
- `state`: Current state NamedTuple
|
|||
|
|
- `var_type`: State variable type to update
|
|||
|
|
- `value`: New value for the state variable
|
|||
|
|
|
|||
|
|
# Returns
|
|||
|
|
New state NamedTuple with updated value
|
|||
|
|
|
|||
|
|
# Example
|
|||
|
|
```julia
|
|||
|
|
state = get_state(cache, 1)
|
|||
|
|
new_state = set_state_variable(state, PlasticStrain, new_ε_p)
|
|||
|
|
set_state!(cache, 1, new_state)
|
|||
|
|
```
|
|||
|
|
"""
|
|||
|
|
@generated function set_state_variable(state::StateType, var_type::Type{VarType}, value) where {StateType<:NamedTuple, VarType<:AbstractStateVariable}
|
|||
|
|
# Get the symbol for this state variable
|
|||
|
|
sym = default_symbol(VarType)
|
|||
|
|
|
|||
|
|
# Check if state has this field
|
|||
|
|
if hasfield(StateType, sym)
|
|||
|
|
return :(merge(state, NamedTuple{($(QuoteNode(sym)),)}((value,))))
|
|||
|
|
else
|
|||
|
|
error("State type $StateType does not have field $sym for variable $VarType")
|
|||
|
|
end
|
|||
|
|
end
|