mirror of
https://github.com/JuliaFEM/JuliaFEM.jl.git
synced 2026-09-17 09:12:09 +00:00
2bcc7ecd1e
New 747-line test file for plasticity NEW API (test-driven development): - Visionary tests for J2 plasticity with isotropic hardening - Tests for radial return mapping algorithm - Tests for kinematic hardening and Bauschinger effect - Tests for perfect plasticity and cyclic loading - Extensive documentation of intended API design - Tests currently skipped (@test_skip) until implementation complete - Documents return mapping, consistent tangent, state storage - Documents nodal assembly for plasticity This test file serves as both test suite and API design documentation for the new plasticity material model interface.
748 lines
22 KiB
Julia
748 lines
22 KiB
Julia
"""
|
||
# Plasticity - NEW API (Test-Driven Development)
|
||
|
||
**What:** Shows how plasticity models SHOULD work with the NEW API
|
||
|
||
**Why:**
|
||
- **Permanent deformation** - Irreversible (metals, soils)
|
||
- **Yield criterion** - von Mises, Tresca, Drucker-Prager
|
||
- **Hardening** - Isotropic, kinematic, mixed
|
||
- **Rate-independence** - Path-independent (classical plasticity)
|
||
- **History-dependent** - Internal state variables
|
||
|
||
**NEW API Concepts:**
|
||
1. **Plastic material types** - J2Plasticity, DruckerPrager
|
||
2. **Yield function** - f(σ, α) ≤ 0 (elastic domain)
|
||
3. **Flow rule** - Plastic strain rate direction
|
||
4. **Hardening laws** - Isotropic (expanding yield surface), kinematic (translation)
|
||
5. **Return mapping** - Radial return, closest point projection
|
||
|
||
**Test Problems:**
|
||
|
||
## Test 1: J2 Plasticity (von Mises)
|
||
- Yield: f = √(3J₂) - σ_y(ε_p)
|
||
- Isotropic hardening
|
||
- Validates return mapping algorithm
|
||
|
||
## Test 2: Kinematic Hardening
|
||
- Backstress α (yield surface translates)
|
||
- Armstrong-Frederick model
|
||
- Validates ratcheting behavior
|
||
|
||
## Test 3: Perfect Plasticity
|
||
- No hardening: σ_y = constant
|
||
- Validates elastic-perfectly plastic
|
||
- Tests limit load
|
||
|
||
## Test 4: Cyclic Loading (Bauschinger Effect)
|
||
- Load → Unload → Reverse load
|
||
- Validates kinematic hardening
|
||
- Tests hysteresis loop
|
||
|
||
**Expected Behavior (when implemented):**
|
||
✅ Yield criterion correctly evaluated
|
||
✅ Elastic-plastic split accurate
|
||
✅ Return mapping converges
|
||
✅ Hardening modulus computed correctly
|
||
✅ Consistent tangent for Newton
|
||
✅ Path-independence validated
|
||
|
||
**Status:** 🚧 VISIONARY TEST - Implementation in progress
|
||
"""
|
||
|
||
using Test
|
||
using JuliaFEM
|
||
using Tensors
|
||
using LinearAlgebra
|
||
using Statistics
|
||
|
||
@testset "Plasticity - NEW API (TDD)" begin
|
||
|
||
# =============================================================================
|
||
# J2 PLASTICITY (VON MISES)
|
||
# =============================================================================
|
||
|
||
@testset "J2 Plasticity - Isotropic Hardening (Visionary)" begin
|
||
@test_skip begin # Skip until implemented
|
||
|
||
# Material parameters
|
||
E = 200e3 # Young's modulus (MPa)
|
||
ν = 0.3 # Poisson's ratio
|
||
σ_y0 = 250.0 # Initial yield stress (MPa)
|
||
H = 2000.0 # Hardening modulus (MPa)
|
||
|
||
# NEW: J2 plasticity material
|
||
material = J2Plasticity(
|
||
E=E,
|
||
ν=ν,
|
||
yield_stress=σ_y0,
|
||
hardening=IsotropicHardening(H=H),
|
||
hardening_law=:linear # or :exponential, :voce
|
||
)
|
||
|
||
# Strain history (uniaxial tension)
|
||
ε_max = 0.005 # 0.5% total strain
|
||
n_steps = 100
|
||
ε_history = range(0, ε_max, length=n_steps)
|
||
|
||
# Strain tensor (uniaxial)
|
||
σ_history = []
|
||
ε_p_history = []
|
||
|
||
# Internal state
|
||
state = PlasticState(
|
||
ε_p=zero(SymmetricTensor{2,3}), # Plastic strain
|
||
ε_p_eq=0.0, # Equivalent plastic strain
|
||
α=zero(SymmetricTensor{2,3}) # Backstress (if kinematic)
|
||
)
|
||
|
||
for ε in ε_history
|
||
# Strain tensor (uniaxial tension in x)
|
||
ε_total = SymmetricTensor{2,3}((
|
||
ε, 0.0, 0.0,
|
||
0.0, -ν * ε, 0.0,
|
||
0.0, 0.0, -ν * ε
|
||
))
|
||
|
||
# Compute stress (with return mapping)
|
||
σ, state_new = compute_stress(material, ε_total, state)
|
||
|
||
push!(σ_history, σ[1, 1]) # Axial stress
|
||
push!(ε_p_history, state_new.ε_p_eq)
|
||
|
||
state = state_new
|
||
end
|
||
|
||
# Validate elastic region
|
||
ε_elastic = σ_y0 / E
|
||
elastic_indices = findall(ε_history .<= ε_elastic)
|
||
|
||
for i in elastic_indices
|
||
# Elastic: σ = E ε
|
||
@test isapprox(σ_history[i], E * ε_history[i], rtol=0.01)
|
||
@test ε_p_history[i] == 0.0
|
||
end
|
||
|
||
# Validate plastic region
|
||
plastic_indices = findall(ε_history .> ε_elastic)
|
||
|
||
for i in plastic_indices
|
||
# Plastic: σ_y(ε_p) = σ_y0 + H ε_p
|
||
ε_p = ε_p_history[i]
|
||
σ_y_current = σ_y0 + H * ε_p
|
||
|
||
# Stress should be at yield
|
||
@test isapprox(σ_history[i], σ_y_current, rtol=0.01)
|
||
end
|
||
|
||
end
|
||
end
|
||
|
||
# =============================================================================
|
||
# RETURN MAPPING ALGORITHM
|
||
# =============================================================================
|
||
|
||
@testset "Radial Return Mapping (Visionary)" begin
|
||
@test_skip begin
|
||
|
||
material = J2Plasticity(
|
||
E=200e3,
|
||
ν=0.3,
|
||
yield_stress=250.0,
|
||
hardening=IsotropicHardening(H=2000.0)
|
||
)
|
||
|
||
# Trial elastic step (exceed yield)
|
||
ε_trial = SymmetricTensor{2,3}((
|
||
0.003, 0.001, 0.0,
|
||
0.001, 0.002, 0.0,
|
||
0.0, 0.0, 0.0
|
||
))
|
||
|
||
state = PlasticState(
|
||
ε_p=zero(SymmetricTensor{2,3}),
|
||
ε_p_eq=0.0,
|
||
α=zero(SymmetricTensor{2,3})
|
||
)
|
||
|
||
# Elastic predictor
|
||
σ_trial = elastic_stress(material, ε_trial - state.ε_p)
|
||
|
||
# Yield function
|
||
s_trial = dev(σ_trial) # Deviatoric stress
|
||
q_trial = sqrt(1.5 * dcontract(s_trial, s_trial)) # von Mises stress
|
||
|
||
f_trial = q_trial - material.yield_stress
|
||
|
||
if f_trial > 0
|
||
# Plastic: Return mapping required
|
||
σ, state_new = return_mapping(material, σ_trial, state)
|
||
|
||
# Validate yield criterion satisfied
|
||
s = dev(σ)
|
||
q = sqrt(1.5 * dcontract(s, s))
|
||
σ_y_current = material.yield_stress + material.H * state_new.ε_p_eq
|
||
|
||
@test isapprox(q, σ_y_current, atol=1e-6)
|
||
|
||
# Validate plastic strain increased
|
||
@test state_new.ε_p_eq > state.ε_p_eq
|
||
|
||
else
|
||
# Elastic: No return mapping
|
||
@test f_trial <= 0
|
||
end
|
||
|
||
end
|
||
end
|
||
|
||
# =============================================================================
|
||
# KINEMATIC HARDENING (ARMSTRONG-FREDERICK)
|
||
# =============================================================================
|
||
|
||
@testset "Kinematic Hardening (Visionary)" begin
|
||
@test_skip begin
|
||
|
||
# Material with kinematic hardening
|
||
material = J2Plasticity(
|
||
E=200e3,
|
||
ν=0.3,
|
||
yield_stress=250.0,
|
||
hardening=KinematicHardening(
|
||
C=5000.0, # Kinematic hardening modulus
|
||
γ=50.0 # Armstrong-Frederick parameter
|
||
),
|
||
mixed_hardening=false
|
||
)
|
||
|
||
# Cyclic loading: tension → compression
|
||
ε_max = 0.005
|
||
n_cycles = 3
|
||
|
||
ε_history = []
|
||
σ_history = []
|
||
|
||
state = PlasticState(
|
||
ε_p=zero(SymmetricTensor{2,3}),
|
||
ε_p_eq=0.0,
|
||
α=zero(SymmetricTensor{2,3}) # Backstress
|
||
)
|
||
|
||
for cycle in 1:n_cycles
|
||
# Tension
|
||
for ε in range(0, ε_max, length=50)
|
||
ε_tensor = SymmetricTensor{2,3}((ε, 0.0, 0.0, 0.0, 0.0, 0.0))
|
||
σ, state = compute_stress(material, ε_tensor, state)
|
||
|
||
push!(ε_history, ε)
|
||
push!(σ_history, σ[1, 1])
|
||
end
|
||
|
||
# Compression
|
||
for ε in range(ε_max, -ε_max, length=100)
|
||
ε_tensor = SymmetricTensor{2,3}((ε, 0.0, 0.0, 0.0, 0.0, 0.0))
|
||
σ, state = compute_stress(material, ε_tensor, state)
|
||
|
||
push!(ε_history, ε)
|
||
push!(σ_history, σ[1, 1])
|
||
end
|
||
|
||
# Back to tension
|
||
for ε in range(-ε_max, 0, length=50)
|
||
ε_tensor = SymmetricTensor{2,3}((ε, 0.0, 0.0, 0.0, 0.0, 0.0))
|
||
σ, state = compute_stress(material, ε_tensor, state)
|
||
|
||
push!(ε_history, ε)
|
||
push!(σ_history, σ[1, 1])
|
||
end
|
||
end
|
||
|
||
# Validate Bauschinger effect
|
||
# Yield stress in compression < initial yield
|
||
σ_y_compression = minimum(σ_history[ε_history.<0])
|
||
@test abs(σ_y_compression) < material.yield_stress
|
||
|
||
# Validate hysteresis loop closes
|
||
# (For stabilized cycle)
|
||
@test length(ε_history) > 0
|
||
|
||
end
|
||
end
|
||
|
||
# =============================================================================
|
||
# PERFECT PLASTICITY (NO HARDENING)
|
||
# =============================================================================
|
||
|
||
@testset "Perfect Plasticity (Visionary)" begin
|
||
@test_skip begin
|
||
|
||
# No hardening: H = 0
|
||
material = J2Plasticity(
|
||
E=200e3,
|
||
ν=0.3,
|
||
yield_stress=250.0,
|
||
hardening=NoHardening() # H = 0
|
||
)
|
||
|
||
# Large strain (well into plastic)
|
||
ε_max = 0.01 # 1% strain
|
||
ε_history = range(0, ε_max, length=100)
|
||
|
||
σ_history = []
|
||
state = PlasticState(
|
||
ε_p=zero(SymmetricTensor{2,3}),
|
||
ε_p_eq=0.0,
|
||
α=zero(SymmetricTensor{2,3})
|
||
)
|
||
|
||
for ε in ε_history
|
||
ε_tensor = SymmetricTensor{2,3}((ε, 0.0, 0.0, 0.0, 0.0, 0.0))
|
||
σ, state = compute_stress(material, ε_tensor, state)
|
||
push!(σ_history, σ[1, 1])
|
||
end
|
||
|
||
# After yield, stress should be constant
|
||
ε_yield = material.yield_stress / material.E
|
||
plastic_indices = findall(ε_history .> ε_yield)
|
||
|
||
σ_plastic = σ_history[plastic_indices]
|
||
|
||
# All plastic stresses ≈ σ_y (no hardening!)
|
||
@test all(isapprox.(σ_plastic, material.yield_stress, rtol=0.01))
|
||
|
||
end
|
||
end
|
||
|
||
# =============================================================================
|
||
# CONSISTENT TANGENT (FOR NEWTON)
|
||
# =============================================================================
|
||
|
||
@testset "Consistent Tangent (Visionary)" begin
|
||
@test_skip begin
|
||
|
||
material = J2Plasticity(
|
||
E=200e3,
|
||
ν=0.3,
|
||
yield_stress=250.0,
|
||
hardening=IsotropicHardening(H=2000.0)
|
||
)
|
||
|
||
# Strain state (plastic)
|
||
ε = SymmetricTensor{2,3}((0.003, 0.001, 0.0, 0.001, 0.002, 0.0))
|
||
state = PlasticState(
|
||
ε_p=SymmetricTensor{2,3}((0.001, 0.0, 0.0, 0.0, 0.0, 0.0)),
|
||
ε_p_eq=0.001,
|
||
α=zero(SymmetricTensor{2,3})
|
||
)
|
||
|
||
# Compute stress and tangent
|
||
σ, state_new, C_ep = compute_stress_tangent(material, ε, state)
|
||
|
||
# Validate tangent via finite difference
|
||
δε = 1e-8
|
||
|
||
for i in 1:6 # Voigt notation
|
||
ε_pert = ε + δε * basis_symmetric_tensor(i)
|
||
σ_pert, _ = compute_stress(material, ε_pert, state)
|
||
|
||
dσ_numerical = (σ_pert - σ) / δε
|
||
dσ_tangent = C_ep ⊡ basis_symmetric_tensor(i)
|
||
|
||
@test isapprox(dσ_numerical, dσ_tangent, rtol=0.01)
|
||
end
|
||
|
||
# Validate symmetry (major)
|
||
for i in 1:6, j in 1:6
|
||
@test isapprox(C_ep[i, j], C_ep[j, i], atol=1e-10)
|
||
end
|
||
|
||
end
|
||
end
|
||
|
||
# =============================================================================
|
||
# MULTI-AXIAL LOADING
|
||
# =============================================================================
|
||
|
||
@testset "Multi-Axial Loading (Visionary)" begin
|
||
@test_skip begin
|
||
|
||
material = J2Plasticity(
|
||
E=200e3,
|
||
ν=0.3,
|
||
yield_stress=250.0,
|
||
hardening=IsotropicHardening(H=2000.0)
|
||
)
|
||
|
||
# Combined tension + shear
|
||
ε_axial_max = 0.003
|
||
ε_shear_max = 0.002
|
||
|
||
n_steps = 100
|
||
σ_history = []
|
||
|
||
state = PlasticState(
|
||
ε_p=zero(SymmetricTensor{2,3}),
|
||
ε_p_eq=0.0,
|
||
α=zero(SymmetricTensor{2,3})
|
||
)
|
||
|
||
for i in 1:n_steps
|
||
# Proportional loading
|
||
ε_axial = ε_axial_max * i / n_steps
|
||
ε_shear = ε_shear_max * i / n_steps
|
||
|
||
ε = SymmetricTensor{2,3}((
|
||
ε_axial, ε_shear, 0.0,
|
||
ε_shear, 0.0, 0.0,
|
||
0.0, 0.0, 0.0
|
||
))
|
||
|
||
σ, state = compute_stress(material, ε, state)
|
||
push!(σ_history, σ)
|
||
end
|
||
|
||
# Validate von Mises yield criterion
|
||
for σ in σ_history
|
||
s = dev(σ)
|
||
q = sqrt(1.5 * dcontract(s, s))
|
||
σ_y_current = material.yield_stress + material.H * state.ε_p_eq
|
||
|
||
# Should be at or below yield
|
||
@test q <= σ_y_current + 1e-6
|
||
end
|
||
|
||
end
|
||
end
|
||
|
||
# =============================================================================
|
||
# PSEUDO-CODE: PLASTICITY INTEGRATION
|
||
# =============================================================================
|
||
|
||
@testset "Plasticity Integration Pattern (Visionary)" begin
|
||
# Pseudo-code showing return mapping
|
||
|
||
println("\n" * "="^70)
|
||
println("PLASTICITY INTEGRATION (RETURN MAPPING)")
|
||
println("="^70)
|
||
|
||
integration_pseudo = """
|
||
# Return mapping algorithm (radial return for J2)
|
||
|
||
function compute_stress_plastic(material, ε_total, state_old)
|
||
# 1. Elastic predictor
|
||
ε_elastic_trial = ε_total - state_old.ε_p
|
||
σ_trial = C_elastic ⊡ ε_elastic_trial
|
||
|
||
# 2. Check yield
|
||
s_trial = dev(σ_trial) # Deviatoric
|
||
q_trial = sqrt(1.5 * s_trial : s_trial) # von Mises
|
||
|
||
σ_y = material.σ_y0 + H * state_old.ε_p_eq
|
||
f_trial = q_trial - σ_y
|
||
|
||
if f_trial <= 0
|
||
# Elastic: Accept trial state
|
||
return σ_trial, state_old
|
||
end
|
||
|
||
# 3. Plastic corrector (return mapping)
|
||
# Solve for Δλ (plastic multiplier)
|
||
# f = q - σ_y(ε_p + Δλ) = 0
|
||
|
||
# Newton iteration
|
||
Δλ = 0.0
|
||
for iter in 1:max_iter
|
||
σ_y_current = material.σ_y0 + H * (state_old.ε_p_eq + Δλ)
|
||
q_current = q_trial - 3*G*Δλ # G = shear modulus
|
||
|
||
f = q_current - σ_y_current
|
||
|
||
if abs(f) < tol
|
||
break
|
||
end
|
||
|
||
# Derivative: df/dΔλ
|
||
df_dΔλ = -3*G - H
|
||
|
||
# Update
|
||
Δλ -= f / df_dΔλ
|
||
end
|
||
|
||
# 4. Update stress and state
|
||
n = s_trial / norm(s_trial) # Flow direction
|
||
|
||
σ = σ_trial - 2*G*Δλ * n
|
||
ε_p_new = state_old.ε_p + Δλ * n
|
||
ε_p_eq_new = state_old.ε_p_eq + Δλ
|
||
|
||
state_new = PlasticState(ε_p_new, ε_p_eq_new, state_old.α)
|
||
|
||
return σ, state_new
|
||
end
|
||
"""
|
||
|
||
println(integration_pseudo)
|
||
println("="^70)
|
||
println("✓ Elastic predictor: Assume elastic step")
|
||
println("✓ Check yield: f(σ_trial) ≤ 0?")
|
||
println("✓ Return mapping: Project back to yield surface")
|
||
println("✓ Newton iteration: Solve for plastic multiplier Δλ")
|
||
println("✓ Update state: ε_p, ε_p_eq, α")
|
||
println("="^70)
|
||
end
|
||
|
||
# =============================================================================
|
||
# KEY ARCHITECTURAL INSIGHTS
|
||
# =============================================================================
|
||
|
||
println("\n" * "="^70)
|
||
println("PLASTICITY ARCHITECTURE INSIGHTS (NEW API)")
|
||
println("="^70)
|
||
println("✓ J2 plasticity: von Mises yield, isotropic/kinematic hardening")
|
||
println("✓ Yield function: f(σ, α) = √(3J₂) - σ_y(ε_p)")
|
||
println("✓ Return mapping: Radial return, closest point projection")
|
||
println("✓ Consistent tangent: C_ep for Newton quadratic convergence")
|
||
println("✓ Internal state: ε_p, ε_p_eq, α (per integration point!)")
|
||
println("✓ Isotropic hardening: Yield surface expands")
|
||
println("✓ Kinematic hardening: Yield surface translates (Bauschinger)")
|
||
println("✓ Path-independent: Same final state for same strain path")
|
||
println("✓ Works with Newton-Krylov (tangent from return mapping)")
|
||
println("="^70)
|
||
|
||
end
|
||
|
||
"""
|
||
# IMPLEMENTATION NOTES
|
||
|
||
## J2 Plasticity (von Mises)
|
||
|
||
### Yield Function
|
||
|
||
**Definition:**
|
||
f(σ, ε_p) = √(3J₂) - σ_y(ε_p)
|
||
|
||
where:
|
||
- J₂ = (1/2) s:s (second deviatoric invariant)
|
||
- s = σ - (1/3)tr(σ)I (deviatoric stress)
|
||
- σ_y(ε_p) = yield stress (function of plastic strain)
|
||
|
||
**Equivalent form:**
|
||
f = q - σ_y
|
||
|
||
where q = √(3J₂) = von Mises stress.
|
||
|
||
**Elastic domain:** f ≤ 0
|
||
|
||
**Yield surface:** f = 0
|
||
|
||
### Flow Rule
|
||
|
||
**Associative plasticity:** Plastic strain rate direction = yield gradient
|
||
|
||
ε̇_p = λ̇ ∂f/∂σ = λ̇ (3/2) s/q = λ̇ n
|
||
|
||
where:
|
||
- λ̇ = plastic multiplier (rate)
|
||
- n = (3/2) s/q = flow direction (unit deviatoric)
|
||
|
||
**Properties:**
|
||
- Incompressible: tr(ε̇_p) = 0 (volume preserving)
|
||
- Radial: ε̇_p ∝ s (proportional to deviatoric stress)
|
||
|
||
### Hardening Laws
|
||
|
||
**Isotropic (linear):**
|
||
σ_y(ε_p) = σ_y0 + H ε_p_eq
|
||
|
||
where:
|
||
- σ_y0 = initial yield stress
|
||
- H = hardening modulus
|
||
- ε_p_eq = ∫ √(2/3 ε̇_p:ε̇_p) dt = equivalent plastic strain
|
||
|
||
**Isotropic (exponential/Voce):**
|
||
σ_y(ε_p) = σ_∞ - (σ_∞ - σ_y0) exp(-b ε_p_eq)
|
||
|
||
Saturates to σ_∞.
|
||
|
||
**Kinematic (Armstrong-Frederick):**
|
||
α̇ = C ε̇_p - γ α λ̇
|
||
|
||
where:
|
||
- α = backstress (2nd order tensor)
|
||
- C = kinematic hardening modulus
|
||
- γ = recall parameter
|
||
|
||
**Modified yield:**
|
||
f = √(3/2 (s-α):(s-α)) - σ_y
|
||
|
||
### Return Mapping Algorithm
|
||
|
||
**Problem:** Given ε_{n+1}, find σ_{n+1} and state_{n+1}.
|
||
|
||
**Elastic predictor:**
|
||
```
|
||
ε_e_trial = ε_{n+1} - ε_p_n
|
||
σ_trial = C_elastic : ε_e_trial
|
||
```
|
||
|
||
**Check yield:**
|
||
```
|
||
f_trial = q_trial - σ_y(ε_p_eq_n)
|
||
```
|
||
|
||
**If f_trial ≤ 0:** Elastic, return (σ_trial, state_n)
|
||
|
||
**If f_trial > 0:** Plastic, solve for Δλ:
|
||
|
||
**Consistency condition:**
|
||
f(σ_{n+1}, ε_p_eq_{n+1}) = 0
|
||
|
||
**Discretized flow rule:**
|
||
ε_p_{n+1} = ε_p_n + Δλ n
|
||
|
||
**Stress update:**
|
||
σ_{n+1} = σ_trial - 2G Δλ n
|
||
|
||
where G = shear modulus.
|
||
|
||
**Yield condition:**
|
||
q_{n+1} = q_trial - 3G Δλ = σ_y(ε_p_eq_n + Δλ)
|
||
|
||
**Solve for Δλ (Newton):**
|
||
```julia
|
||
function return_mapping(σ_trial, state, material)
|
||
s_trial = dev(σ_trial)
|
||
q_trial = sqrt(1.5 * dcontract(s_trial, s_trial))
|
||
n = s_trial / norm(s_trial)
|
||
|
||
# Initial guess
|
||
Δλ = 0.0
|
||
ε_p_eq_old = state.ε_p_eq
|
||
G = material.E / (2*(1 + material.ν))
|
||
H = material.H
|
||
|
||
for iter in 1:max_iter
|
||
# Current yield stress
|
||
σ_y = material.σ_y0 + H * (ε_p_eq_old + Δλ)
|
||
|
||
# Residual
|
||
f = q_trial - 3*G*Δλ - σ_y
|
||
|
||
if abs(f) < tol
|
||
break
|
||
end
|
||
|
||
# Derivative
|
||
df_dΔλ = -3*G - H
|
||
|
||
# Newton update
|
||
Δλ -= f / df_dΔλ
|
||
end
|
||
|
||
# Update stress
|
||
σ = σ_trial - 2*G*Δλ * n
|
||
|
||
# Update state
|
||
ε_p_new = state.ε_p + Δλ * n
|
||
ε_p_eq_new = ε_p_eq_old + Δλ
|
||
|
||
return σ, PlasticState(ε_p_new, ε_p_eq_new, state.α)
|
||
end
|
||
```
|
||
|
||
### Consistent Tangent
|
||
|
||
**For Newton convergence:** Need C_ep = dσ/dε (algorithmic tangent).
|
||
|
||
**Elastic:**
|
||
C_ep = C_elastic
|
||
|
||
**Plastic:** More complex!
|
||
|
||
C_ep = C_elastic - (2G)² / (3G + H) * (n ⊗ n)
|
||
|
||
where ⊗ = outer product.
|
||
|
||
**Derivation:** Chain rule through return mapping.
|
||
|
||
**Properties:**
|
||
- Symmetric (major symmetry)
|
||
- Positive-definite (for H > 0)
|
||
- Converges to C_elastic as Δλ → 0
|
||
|
||
## Internal State Storage
|
||
|
||
**Per integration point:**
|
||
```julia
|
||
struct PlasticState{dim}
|
||
ε_p::SymmetricTensor{2,dim} # Plastic strain
|
||
ε_p_eq::Float64 # Equivalent plastic strain
|
||
α::SymmetricTensor{2,dim} # Backstress (kinematic)
|
||
end
|
||
```
|
||
|
||
**Element-level:**
|
||
```julia
|
||
struct PlasticElement
|
||
topology::AbstractTopology
|
||
basis::AbstractBasis
|
||
nodes::NTuple{N,Int}
|
||
state::Vector{PlasticState} # One per integration point!
|
||
end
|
||
```
|
||
|
||
**Key:** State is HISTORY-DEPENDENT, must be stored!
|
||
|
||
## Nodal Assembly (Plasticity)
|
||
|
||
```julia
|
||
function tangent_matvec_plastic!(w, v, u_current, material, elements, states)
|
||
Threads.@threads for node_i in 1:n_nodes
|
||
w_local = zero(Vec{3})
|
||
|
||
for elem in node_to_elements[node_i]
|
||
for (ip_idx, ip) in enumerate(integration_points(elem))
|
||
# Current state at this integration point
|
||
state = states[elem][ip_idx]
|
||
|
||
# Strain
|
||
ε = compute_strain(elem, ip, u_current)
|
||
|
||
# Consistent tangent (elastic or plastic)
|
||
σ, state_new, C_ep = compute_stress_tangent(material, ε, state)
|
||
|
||
for node_j in elem.nodes
|
||
# Tangent block
|
||
K_t_ij = compute_plastic_tangent_block(elem, node_i, node_j, C_ep, ip)
|
||
|
||
v_j = Vec{3}(v[3*(node_j-1)+1:3*node_j])
|
||
w_local += K_t_ij ⊡ v_j
|
||
end
|
||
|
||
# Update state (for next iteration)
|
||
states[elem][ip_idx] = state_new
|
||
end
|
||
end
|
||
|
||
w[3*(node_i-1)+1:3*node_i] = w_local
|
||
end
|
||
end
|
||
```
|
||
|
||
**Key:** State updated during tangent computation!
|
||
|
||
## Next Steps
|
||
|
||
1. Implement `J2Plasticity` material type
|
||
2. Implement `PlasticState` struct
|
||
3. Implement `return_mapping` algorithm
|
||
4. Implement `compute_stress_plastic`
|
||
5. Implement `consistent_tangent_plastic`
|
||
6. Implement hardening laws (isotropic, kinematic)
|
||
7. Implement state storage (per integration point)
|
||
8. Validate against analytical solutions
|
||
9. Validate against experimental data
|
||
10. Performance benchmarks
|
||
|
||
"""
|