mirror of
https://github.com/JuliaFEM/JuliaFEM.jl.git
synced 2026-09-21 10:23:37 +00:00
bb3c3c0680
- Added @inline to is_finite_strain, has_state - Trait functions called in assembly loops - Performance optimization for plastic materials
367 lines
11 KiB
Julia
367 lines
11 KiB
Julia
"""
|
||
Perfect Plasticity Material (J2 Plasticity with Kinematic Hardening)
|
||
|
||
Classical von Mises plasticity with:
|
||
- Small strain formulation
|
||
- Associative flow rule (normality)
|
||
- Radial return mapping algorithm
|
||
- Kinematic hardening (backstress evolution)
|
||
|
||
Reference:
|
||
- Simo & Hughes (1998) - "Computational Inelasticity"
|
||
- De Souza Neto et al. (2008) - "Computational Methods for Plasticity"
|
||
|
||
Theory
|
||
======
|
||
|
||
Yield Function (von Mises):
|
||
f = √(3/2)||dev(σ - α)|| - σ_y
|
||
|
||
Where:
|
||
σ - Cauchy stress tensor
|
||
α - Backstress (kinematic hardening)
|
||
σ_y - Yield stress
|
||
dev(·) - Deviatoric part
|
||
|
||
Elastic Domain:
|
||
f ≤ 0 → Elastic behavior
|
||
f > 0 → Plastic loading (return to surface)
|
||
|
||
Flow Rule (Associative):
|
||
dε^p = dλ · ∂f/∂σ = dλ · n
|
||
|
||
Where:
|
||
n = √(3/2) · dev(σ - α) / ||dev(σ - α)|| (flow direction)
|
||
dλ - Plastic multiplier
|
||
|
||
Hardening Law (Kinematic):
|
||
dα = (2/3) H · dε^p
|
||
|
||
Where:
|
||
H - Hardening modulus
|
||
|
||
Consistency Condition:
|
||
f = 0 during plastic loading
|
||
df = 0 (stress remains on yield surface)
|
||
|
||
Algorithm
|
||
=========
|
||
|
||
Radial Return Mapping (closest point projection):
|
||
|
||
1. Elastic Predictor:
|
||
σ_trial = σ_n + 𝔻 : Δε
|
||
|
||
2. Check Yield:
|
||
f_trial = √(3/2)||dev(σ_trial - α_n)|| - σ_y
|
||
|
||
3a. If f_trial ≤ 0: ELASTIC
|
||
σ_{n+1} = σ_trial
|
||
α_{n+1} = α_n
|
||
ε^p_{n+1} = ε^p_n
|
||
|
||
3b. If f_trial > 0: PLASTIC
|
||
Solve for plastic multiplier Δλ:
|
||
f(σ_trial - 2μΔλ·n - (2/3)HΔλ·n, α_n + (2/3)HΔλ·n) = 0
|
||
|
||
Update state:
|
||
n = dev(σ_trial - α_n) / ||dev(σ_trial - α_n)||
|
||
Δλ = (f_trial) / (3μ + H)
|
||
σ_{n+1} = σ_trial - 2μΔλ·n
|
||
α_{n+1} = α_n + (2/3)HΔλ·n
|
||
ε^p_{n+1} = ε^p_n + Δλ·n
|
||
|
||
4. Consistent Tangent:
|
||
𝔻^ep = 𝔻 - (4μ²/(3μ+H)) · (n ⊗ n)
|
||
|
||
Performance
|
||
===========
|
||
|
||
Expected: ~10-20× slower than LinearElastic due to:
|
||
- State updates (memory writes)
|
||
- Conditional logic (elastic vs plastic)
|
||
- Tensor deviatoric decomposition
|
||
|
||
But still fast: ~200-500 ns per evaluation
|
||
"""
|
||
|
||
using Tensors
|
||
using LinearAlgebra
|
||
|
||
# Load abstract types
|
||
include("abstract_material.jl")
|
||
|
||
"""
|
||
PlasticityState <: AbstractMaterialState
|
||
|
||
State variables for perfect plasticity model.
|
||
|
||
# Fields
|
||
- `ε_p::SymmetricTensor{2,3,Float64,6}` - Plastic strain tensor
|
||
- `α::SymmetricTensor{2,3,Float64,6}` - Backstress (kinematic hardening)
|
||
- `κ::Float64` - Equivalent plastic strain (scalar)
|
||
|
||
# Notes
|
||
Immutable for thread safety. Updates create new state.
|
||
"""
|
||
struct PlasticityState <: AbstractMaterialState
|
||
ε_p::SymmetricTensor{2,3,Float64,6} # Plastic strain
|
||
α::SymmetricTensor{2,3,Float64,6} # Backstress
|
||
κ::Float64 # Equivalent plastic strain
|
||
|
||
function PlasticityState(ε_p::SymmetricTensor{2,3,Float64,6},
|
||
α::SymmetricTensor{2,3,Float64,6},
|
||
κ::Float64)
|
||
κ ≥ 0.0 || throw(ArgumentError("Equivalent plastic strain must be non-negative, got κ = $κ"))
|
||
new(ε_p, α, κ)
|
||
end
|
||
end
|
||
|
||
"""
|
||
PlasticityState()
|
||
|
||
Initialize state with zero plastic strain and backstress.
|
||
"""
|
||
PlasticityState() = PlasticityState(zero(SymmetricTensor{2,3}),
|
||
zero(SymmetricTensor{2,3}),
|
||
0.0)
|
||
|
||
# Zero constructor for Base.zero compatibility
|
||
Base.zero(::Type{PlasticityState}) = PlasticityState()
|
||
|
||
"""
|
||
PerfectPlasticity <: AbstractPlasticMaterial
|
||
|
||
J2 (von Mises) plasticity with kinematic hardening.
|
||
|
||
# Fields
|
||
- `E::Float64` - Young's modulus [Pa]
|
||
- `ν::Float64` - Poisson's ratio [-]
|
||
- `σ_y::Float64` - Yield stress [Pa]
|
||
- `H::Float64` - Hardening modulus [Pa]
|
||
|
||
# Derived Properties
|
||
- `μ = E/(2(1+ν))` - Shear modulus
|
||
- `λ = Eν/((1+ν)(1-2ν))` - Lamé parameter
|
||
|
||
# Type Hierarchy
|
||
`PerfectPlasticity <: AbstractPlasticMaterial <: AbstractMaterial`
|
||
|
||
# Construction
|
||
```julia
|
||
# Basic construction
|
||
steel = PerfectPlasticity(E=200e9, ν=0.3, σ_y=250e6, H=1e9)
|
||
|
||
# Perfect plasticity (no hardening)
|
||
steel_perfect = PerfectPlasticity(E=200e9, ν=0.3, σ_y=250e6, H=0.0)
|
||
```
|
||
|
||
# Theory
|
||
Classical J2 plasticity:
|
||
- von Mises yield criterion
|
||
- Associative flow rule (normality)
|
||
- Kinematic hardening (backstress evolution)
|
||
- Radial return mapping
|
||
|
||
# Performance
|
||
~200-500 ns per evaluation (10-20× slower than LinearElastic)
|
||
"""
|
||
struct PerfectPlasticity <: AbstractPlasticMaterial
|
||
E::Float64 # Young's modulus [Pa]
|
||
ν::Float64 # Poisson's ratio [-]
|
||
σ_y::Float64 # Yield stress [Pa]
|
||
H::Float64 # Hardening modulus [Pa]
|
||
|
||
# Derived properties (for performance)
|
||
μ::Float64 # Shear modulus
|
||
λ::Float64 # Lamé parameter
|
||
|
||
function PerfectPlasticity(E::Float64, ν::Float64, σ_y::Float64, H::Float64)
|
||
# Validate inputs
|
||
E > 0.0 || throw(ArgumentError("Young's modulus must be positive, got E = $E"))
|
||
-1.0 < ν < 0.5 || throw(ArgumentError("Poisson's ratio must satisfy -1 < ν < 0.5, got ν = $ν"))
|
||
σ_y > 0.0 || throw(ArgumentError("Yield stress must be positive, got σ_y = $σ_y"))
|
||
H ≥ 0.0 || throw(ArgumentError("Hardening modulus must be non-negative, got H = $H"))
|
||
|
||
# Compute Lamé parameters
|
||
μ = E / (2(1 + ν))
|
||
λ = E * ν / ((1 + ν) * (1 - 2ν))
|
||
|
||
new(E, ν, σ_y, H, μ, λ)
|
||
end
|
||
end
|
||
|
||
"""
|
||
PerfectPlasticity(; E, ν, σ_y, H)
|
||
|
||
Keyword constructor for perfect plasticity material.
|
||
|
||
# Arguments
|
||
- `E::Real` - Young's modulus [Pa]
|
||
- `ν::Real` - Poisson's ratio [-], must satisfy -1 < ν < 0.5
|
||
- `σ_y::Real` - Yield stress [Pa]
|
||
- `H::Real` - Hardening modulus [Pa] (H=0 for perfect plasticity)
|
||
|
||
# Example
|
||
```julia
|
||
# Linear hardening
|
||
steel = PerfectPlasticity(E=200e9, ν=0.3, σ_y=250e6, H=1e9)
|
||
|
||
# Perfect plasticity (no hardening)
|
||
steel_perfect = PerfectPlasticity(E=200e9, ν=0.3, σ_y=250e6, H=0.0)
|
||
```
|
||
"""
|
||
PerfectPlasticity(; E::Real, ν::Real, σ_y::Real, H::Real) =
|
||
PerfectPlasticity(Float64(E), Float64(ν), Float64(σ_y), Float64(H))
|
||
|
||
# Trait declaration: PerfectPlasticity has strain-dependent tangent and state
|
||
material_behavior(::PerfectPlasticity) = StatefulStrainDependent()
|
||
|
||
# State type trait: PerfectPlasticity uses PlasticityState
|
||
state_type(::Type{PerfectPlasticity}) = PlasticityState
|
||
|
||
"""
|
||
compute_stress(material::PerfectPlasticity,
|
||
ε::SymmetricTensor{2,3},
|
||
state_old::Union{Nothing,PlasticityState}=nothing,
|
||
Δt::Float64=0.0)
|
||
|
||
Compute stress and consistent tangent using radial return mapping.
|
||
|
||
# Algorithm
|
||
1. Elastic predictor: σ_trial = 𝔻 : (ε - ε^p_old)
|
||
2. Check yield: f = √(3/2)||dev(σ_trial - α)|| - σ_y
|
||
3. Plastic corrector (if f > 0):
|
||
- Compute flow direction: n = dev(σ_trial - α) / ||dev(σ_trial - α)||
|
||
- Solve for plastic multiplier: Δλ = f / (3μ + H)
|
||
- Update stress: σ = σ_trial - 2μΔλ·n
|
||
- Update backstress: α_new = α + (2/3)HΔλ·n
|
||
- Update plastic strain: ε^p_new = ε^p + Δλ·n
|
||
4. Consistent tangent: 𝔻^ep = 𝔻 - (4μ²/(3μ+H))·(n⊗n)
|
||
|
||
# Arguments
|
||
- `material::PerfectPlasticity` - Material parameters
|
||
- `ε::SymmetricTensor{2,3}` - Total strain tensor
|
||
- `state_old::Union{Nothing,PlasticityState}` - Previous state (nothing = initial)
|
||
- `Δt::Float64` - Time increment (unused for rate-independent plasticity)
|
||
|
||
# Returns
|
||
- `σ::SymmetricTensor{2,3}` - Cauchy stress tensor
|
||
- `𝔻::SymmetricTensor{4,3}` - Consistent tangent modulus
|
||
- `state_new::PlasticityState` - Updated state
|
||
|
||
# Performance
|
||
~200-500 ns per evaluation (elastic), ~300-600 ns (plastic)
|
||
"""
|
||
function compute_stress(material::PerfectPlasticity,
|
||
ε::SymmetricTensor{2,3},
|
||
state_old::Union{Nothing,PlasticityState}=nothing,
|
||
Δt::Float64=0.0)
|
||
# Extract material parameters
|
||
μ = material.μ
|
||
λ = material.λ
|
||
σ_y = material.σ_y
|
||
H = material.H
|
||
|
||
# Initialize state if needed
|
||
if state_old === nothing
|
||
state_old = PlasticityState()
|
||
end
|
||
|
||
# Extract old state
|
||
ε_p_old = state_old.ε_p
|
||
α_old = state_old.α
|
||
κ_old = state_old.κ
|
||
|
||
# Elastic strain
|
||
ε_e = ε - ε_p_old
|
||
|
||
# STEP 1: Elastic Predictor
|
||
# σ_trial = λ·tr(ε_e)·I + 2μ·ε_e
|
||
I = one(ε)
|
||
σ_trial = λ * tr(ε_e) * I + 2μ * ε_e
|
||
|
||
# STEP 2: Check Yield Criterion
|
||
# Deviatoric part of relative stress
|
||
s_trial = dev(σ_trial - α_old)
|
||
|
||
# Von Mises equivalent stress
|
||
s_trial_norm = √(3 / 2) * √(s_trial ⊡ s_trial) # ||s||
|
||
|
||
# Yield function
|
||
f_trial = s_trial_norm - σ_y
|
||
|
||
# STEP 3: Plastic Corrector or Return
|
||
if f_trial ≤ 0.0
|
||
# ==================== ELASTIC ====================
|
||
σ = σ_trial
|
||
state_new = state_old # No state change
|
||
|
||
# Elastic tangent
|
||
𝔻 = λ * I ⊗ I + 2μ * symmetric_identity_tensor()
|
||
|
||
else
|
||
# ==================== PLASTIC ====================
|
||
# Flow direction (unit deviatoric tensor)
|
||
n = s_trial / s_trial_norm
|
||
|
||
# Plastic multiplier (closed-form solution for J2 plasticity with kinematic hardening)
|
||
# Derivation: After return mapping:
|
||
# dev(σ - α_new) = dev(σ_trial - 2μΔλn - α_old - (2/3)HΔλn)
|
||
# = s_trial - (2μ + 2H/3)Δλn (since dev(n) = n)
|
||
# Yield criterion: √(3/2)||dev(σ - α_new)|| = σ_y
|
||
# Since n is parallel to s_trial:
|
||
# √(3/2)(||s_trial|| - (2μ + 2H/3)Δλ) = σ_y
|
||
# √(3/2)||s_trial|| - σ_y = √(3/2)(2μ + 2H/3)Δλ
|
||
# f_trial = √(3/2)(2μ + 2H/3)Δλ
|
||
# Δλ = f_trial / (√(3/2)(2μ + 2H/3))
|
||
# Δλ = f_trial / (√(3/2) * 2(3μ + H)/3)
|
||
# Δλ = 3f_trial / (2√(3/2)(3μ + H))
|
||
# Δλ = 3f_trial / (2(3μ + H)/√(3/2))
|
||
# Δλ = 3f_trial * √(3/2) / (2(3μ + H))
|
||
# Simplifying: √(3/2) * 3/2 = √(27/8) = 3√3/(2√8) = 3√3/(4√2) = 3/(2√(2/3))
|
||
# But cleaner: Δλ = f_trial / ((2μ + 2H/3))
|
||
Δλ = f_trial / (2μ + (2.0 / 3.0) * H)
|
||
|
||
# Update stress (radial return) - before backstress!
|
||
σ = σ_trial - 2μ * Δλ * n
|
||
|
||
# Update backstress (kinematic hardening) - must use same n
|
||
α_new = α_old + (2.0 / 3.0) * H * Δλ * n
|
||
|
||
# Update plastic strain
|
||
ε_p_new = ε_p_old + Δλ * n
|
||
|
||
# Update equivalent plastic strain
|
||
κ_new = κ_old + Δλ
|
||
|
||
# New state
|
||
state_new = PlasticityState(ε_p_new, α_new, κ_new)
|
||
|
||
# Consistent tangent (elastoplastic)
|
||
# For kinematic hardening: 𝔻^ep = 𝔻^e - (4μ²/(2μ + 2H/3)) · (n ⊗ n)
|
||
𝔻_e = λ * I ⊗ I + 2μ * symmetric_identity_tensor()
|
||
|
||
# Algorithmic tangent (consistent with return mapping)
|
||
𝔻 = 𝔻_e - (4μ^2 / (2μ + (2.0 / 3.0) * H)) * (n ⊗ n)
|
||
end
|
||
|
||
return σ, 𝔻, state_new
|
||
end
|
||
|
||
"""
|
||
symmetric_identity_tensor()
|
||
|
||
Fourth-order symmetric identity tensor: 𝕀 = ½(δᵢₖδⱼₗ + δᵢₗδⱼₖ)
|
||
|
||
Used in constructing elastic tangent: 𝔻 = λ·I⊗I + 2μ·𝕀
|
||
|
||
# Returns
|
||
`SymmetricTensor{4,3,Float64}` - Symmetric identity tensor
|
||
"""
|
||
@inline function symmetric_identity_tensor()
|
||
# Construct 4th order identity with major and minor symmetry
|
||
return SymmetricTensor{4,3}((i, j, k, l) ->
|
||
(i == k && j == l ? 0.5 : 0.0) + (i == l && j == k ? 0.5 : 0.0))
|
||
end
|