mirror of
https://github.com/JuliaFEM/JuliaFEM.jl.git
synced 2026-09-11 06:08:07 +00:00
c514c1bdcc
New 868-line test file for damage mechanics NEW API (test-driven development): - Visionary tests for isotropic damage model - Tests for ductile damage (Lemaitre model) - Tests for crack band regularization (mesh independence) - Tests for damage unloading and irreversibility - Extensive documentation of intended API design - Tests currently skipped (@test_skip) until implementation complete - Documents damage variable, effective stress, damage evolution - Documents regularization and coupled damage-plasticity This test file serves as both test suite and API design documentation for the new damage mechanics material model interface.
869 lines
25 KiB
Julia
869 lines
25 KiB
Julia
"""
|
||
# Damage Mechanics - NEW API (Test-Driven Development)
|
||
|
||
**What:** Shows how damage models SHOULD work with the NEW API
|
||
|
||
**Why:**
|
||
- **Stiffness degradation** - Material weakens (cracks, voids)
|
||
- **Irreversible** - Damage cannot heal (unlike plasticity unloading)
|
||
- **Mesh independence** - Regularization required (crack band, nonlocal)
|
||
- **Failure prediction** - Crack initiation, propagation
|
||
|
||
**NEW API Concepts:**
|
||
1. **Damage variable** - d ∈ [0,1] where 0=intact, 1=failed
|
||
2. **Effective stress** - σ̄ = σ/(1-d) (undamaged configuration)
|
||
3. **Damage evolution** - ḋ = f(ε, ε_max, damage parameters)
|
||
4. **Regularization** - Length scale to avoid mesh sensitivity
|
||
5. **Coupled damage-plasticity** - Combined degradation + permanent deformation
|
||
|
||
**Test Problems:**
|
||
|
||
## Test 1: Isotropic Damage
|
||
- Stiffness reduction: E_eff = (1-d) E
|
||
- Damage driven by strain energy
|
||
- Validates crack initiation
|
||
|
||
## Test 2: Ductile Damage (Lemaitre)
|
||
- Coupled damage-plasticity
|
||
- Damage from plastic dissipation
|
||
- Validates void growth
|
||
|
||
## Test 3: Crack Band Regularization
|
||
- Mesh-independent energy dissipation
|
||
- Length scale h = element size
|
||
- Validates objectivity
|
||
|
||
## Test 4: Damage Unloading
|
||
- Permanent stiffness loss
|
||
- No damage healing
|
||
- Validates irreversibility
|
||
|
||
**Expected Behavior (when implemented):**
|
||
✅ Damage variable d ∈ [0,1]
|
||
✅ Stiffness degrades smoothly
|
||
✅ Mesh-independent fracture energy
|
||
✅ No healing on unload
|
||
✅ Crack localization captured
|
||
✅ Failure criterion satisfied
|
||
|
||
**Status:** 🚧 VISIONARY TEST - Implementation in progress
|
||
"""
|
||
|
||
using Test
|
||
using JuliaFEM
|
||
using Tensors
|
||
using LinearAlgebra
|
||
using Statistics
|
||
|
||
@testset "Damage Mechanics - NEW API (TDD)" begin
|
||
|
||
# =============================================================================
|
||
# ISOTROPIC DAMAGE
|
||
# =============================================================================
|
||
|
||
@testset "Isotropic Damage - Strain-Based (Visionary)" begin
|
||
@test_skip begin # Skip until implemented
|
||
|
||
# Material parameters
|
||
E = 200e3 # Young's modulus (MPa)
|
||
ν = 0.3 # Poisson's ratio
|
||
ε_d0 = 0.001 # Damage threshold strain
|
||
ε_f = 0.01 # Failure strain
|
||
|
||
# NEW: Isotropic damage material
|
||
material = IsotropicDamage(
|
||
E=E,
|
||
ν=ν,
|
||
damage_threshold=ε_d0,
|
||
failure_strain=ε_f,
|
||
evolution_law=:exponential # or :linear, :power
|
||
)
|
||
|
||
# Strain history (uniaxial tension)
|
||
ε_max = 0.015 # Beyond failure
|
||
n_steps = 200
|
||
ε_history = range(0, ε_max, length=n_steps)
|
||
|
||
σ_history = []
|
||
d_history = [] # Damage variable
|
||
|
||
# Internal state
|
||
state = DamageState(
|
||
d=0.0, # Damage variable (0=intact, 1=failed)
|
||
ε_eq_max=0.0 # Maximum equivalent strain (history)
|
||
)
|
||
|
||
for ε in ε_history
|
||
# Strain tensor (uniaxial tension)
|
||
ε_tensor = SymmetricTensor{2,3}((
|
||
ε, 0.0, 0.0,
|
||
0.0, -ν * ε, 0.0,
|
||
0.0, 0.0, -ν * ε
|
||
))
|
||
|
||
# Compute stress (with damage)
|
||
σ, state_new = compute_stress_damage(material, ε_tensor, state)
|
||
|
||
push!(σ_history, σ[1, 1])
|
||
push!(d_history, state_new.d)
|
||
|
||
state = state_new
|
||
end
|
||
|
||
# Validate elastic region (no damage)
|
||
elastic_indices = findall(ε_history .<= ε_d0)
|
||
for i in elastic_indices
|
||
@test isapprox(σ_history[i], E * ε_history[i], rtol=0.01)
|
||
@test d_history[i] == 0.0
|
||
end
|
||
|
||
# Validate damage growth
|
||
damage_indices = findall((ε_history .> ε_d0) .& (ε_history .< ε_f))
|
||
for i in damage_indices
|
||
@test 0.0 < d_history[i] < 1.0
|
||
|
||
# Effective stiffness reduces
|
||
E_eff = E * (1 - d_history[i])
|
||
@test E_eff < E
|
||
end
|
||
|
||
# Validate failure
|
||
failure_indices = findall(ε_history .>= ε_f)
|
||
for i in failure_indices
|
||
@test d_history[i] >= 0.99 # Nearly complete damage
|
||
@test σ_history[i] < 0.1 * maximum(σ_history) # Stress vanishes
|
||
end
|
||
|
||
end
|
||
end
|
||
|
||
# =============================================================================
|
||
# DAMAGE EVOLUTION LAWS
|
||
# =============================================================================
|
||
|
||
@testset "Damage Evolution Laws (Visionary)" begin
|
||
@test_skip begin
|
||
|
||
E = 200e3
|
||
ν = 0.3
|
||
ε_d0 = 0.001
|
||
ε_f = 0.01
|
||
|
||
# Test different evolution laws
|
||
laws = [:linear, :exponential, :power]
|
||
|
||
for law in laws
|
||
material = IsotropicDamage(
|
||
E=E,
|
||
ν=ν,
|
||
damage_threshold=ε_d0,
|
||
failure_strain=ε_f,
|
||
evolution_law=law
|
||
)
|
||
|
||
state = DamageState(d=0.0, ε_eq_max=0.0)
|
||
|
||
# Strain at 50% between threshold and failure
|
||
ε_mid = (ε_d0 + ε_f) / 2
|
||
ε_tensor = SymmetricTensor{2,3}((ε_mid, 0.0, 0.0, 0.0, 0.0, 0.0))
|
||
|
||
σ, state_new = compute_stress_damage(material, ε_tensor, state)
|
||
|
||
# Damage should be growing
|
||
@test 0.0 < state_new.d < 1.0
|
||
|
||
# Different laws give different d values
|
||
println("Law: $law, d = $(state_new.d)")
|
||
end
|
||
|
||
end
|
||
end
|
||
|
||
# =============================================================================
|
||
# DUCTILE DAMAGE (LEMAITRE MODEL)
|
||
# =============================================================================
|
||
|
||
@testset "Ductile Damage - Coupled Plasticity (Visionary)" begin
|
||
@test_skip begin
|
||
|
||
# Coupled damage-plasticity
|
||
material = DuctileDamage(
|
||
# Elastic properties
|
||
E=200e3,
|
||
ν=0.3,
|
||
|
||
# Plasticity
|
||
yield_stress=250.0,
|
||
hardening=IsotropicHardening(H=2000.0),
|
||
|
||
# Damage (Lemaitre)
|
||
damage_threshold=0.001, # Plastic strain threshold
|
||
S_crit=1.0, # Critical damage value
|
||
s_damage=1.5, # Triaxiality sensitivity
|
||
damage_exponent=2.0
|
||
)
|
||
|
||
# Strain history (tension to failure)
|
||
ε_max = 0.05
|
||
n_steps = 200
|
||
ε_history = range(0, ε_max, length=n_steps)
|
||
|
||
σ_history = []
|
||
d_history = []
|
||
ε_p_history = []
|
||
|
||
state = DuctileDamageState(
|
||
ε_p=zero(SymmetricTensor{2,3}),
|
||
ε_p_eq=0.0,
|
||
d=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_ductile_damage(material, ε_tensor, state)
|
||
|
||
push!(σ_history, σ[1, 1])
|
||
push!(d_history, state.d)
|
||
push!(ε_p_history, state.ε_p_eq)
|
||
end
|
||
|
||
# Validate coupling: Damage grows with plastic strain
|
||
@test all(diff(d_history[ε_p_history.>material.damage_threshold]) .>= 0)
|
||
|
||
# Validate softening: Peak stress followed by descent
|
||
σ_max_idx = argmax(σ_history)
|
||
@test σ_max_idx < length(σ_history) # Not at end
|
||
|
||
# After peak, stress decreases (softening)
|
||
@test σ_history[end] < σ_history[σ_max_idx]
|
||
|
||
# Damage increases monotonically
|
||
@test all(diff(d_history) .>= 0)
|
||
|
||
end
|
||
end
|
||
|
||
# =============================================================================
|
||
# CRACK BAND REGULARIZATION
|
||
# =============================================================================
|
||
|
||
@testset "Crack Band Regularization (Visionary)" begin
|
||
@test_skip begin
|
||
|
||
# Fracture energy per unit area (N/mm)
|
||
G_f = 0.1 # Fracture energy
|
||
|
||
# Two different mesh sizes
|
||
h_coarse = 10.0 # mm
|
||
h_fine = 2.0 # mm
|
||
|
||
# Crack band materials (adjust ε_f based on mesh)
|
||
# Energy = G_f = ∫ σ dε * h
|
||
# For linear softening: G_f ≈ (1/2) σ_max ε_f * h
|
||
|
||
σ_max = 10.0 # Tensile strength (MPa)
|
||
|
||
# Coarse mesh: larger ε_f
|
||
ε_f_coarse = 2 * G_f / (σ_max * h_coarse)
|
||
material_coarse = IsotropicDamage(
|
||
E=200e3,
|
||
ν=0.3,
|
||
damage_threshold=σ_max / 200e3,
|
||
failure_strain=ε_f_coarse,
|
||
evolution_law=:linear,
|
||
crack_band_width=h_coarse
|
||
)
|
||
|
||
# Fine mesh: smaller ε_f
|
||
ε_f_fine = 2 * G_f / (σ_max * h_fine)
|
||
material_fine = IsotropicDamage(
|
||
E=200e3,
|
||
ν=0.3,
|
||
damage_threshold=σ_max / 200e3,
|
||
failure_strain=ε_f_fine,
|
||
evolution_law=:linear,
|
||
crack_band_width=h_fine
|
||
)
|
||
|
||
# Compute energy dissipation for both
|
||
function compute_dissipation(material, ε_max, n_steps)
|
||
ε_history = range(0, ε_max, length=n_steps)
|
||
σ_history = []
|
||
|
||
state = DamageState(d=0.0, ε_eq_max=0.0)
|
||
|
||
for ε in ε_history
|
||
ε_tensor = SymmetricTensor{2,3}((ε, 0.0, 0.0, 0.0, 0.0, 0.0))
|
||
σ, state = compute_stress_damage(material, ε_tensor, state)
|
||
push!(σ_history, σ[1, 1])
|
||
end
|
||
|
||
# Integrate σ dε (trapezoid rule)
|
||
W = sum((σ_history[i] + σ_history[i+1]) / 2 * (ε_history[i+1] - ε_history[i])
|
||
for i in 1:length(ε_history)-1)
|
||
|
||
return W
|
||
end
|
||
|
||
W_coarse = compute_dissipation(material_coarse, 1.2 * ε_f_coarse, 500)
|
||
W_fine = compute_dissipation(material_fine, 1.2 * ε_f_fine, 500)
|
||
|
||
# Fracture energy per volume
|
||
G_v_coarse = W_coarse * h_coarse
|
||
G_v_fine = W_fine * h_fine
|
||
|
||
# Should be mesh-independent!
|
||
@test isapprox(G_v_coarse, G_v_fine, rtol=0.1)
|
||
@test isapprox(G_v_coarse, G_f, rtol=0.1)
|
||
|
||
end
|
||
end
|
||
|
||
# =============================================================================
|
||
# DAMAGE UNLOADING (IRREVERSIBILITY)
|
||
# =============================================================================
|
||
|
||
@testset "Damage Unloading - Irreversible (Visionary)" begin
|
||
@test_skip begin
|
||
|
||
material = IsotropicDamage(
|
||
E=200e3,
|
||
ν=0.3,
|
||
damage_threshold=0.001,
|
||
failure_strain=0.01,
|
||
evolution_law=:linear
|
||
)
|
||
|
||
# Load-unload cycle
|
||
ε_max = 0.005 # Partial damage
|
||
|
||
# Loading
|
||
ε_loading = range(0, ε_max, length=100)
|
||
σ_loading = []
|
||
d_loading = []
|
||
|
||
state = DamageState(d=0.0, ε_eq_max=0.0)
|
||
|
||
for ε in ε_loading
|
||
ε_tensor = SymmetricTensor{2,3}((ε, 0.0, 0.0, 0.0, 0.0, 0.0))
|
||
σ, state = compute_stress_damage(material, ε_tensor, state)
|
||
|
||
push!(σ_loading, σ[1, 1])
|
||
push!(d_loading, state.d)
|
||
end
|
||
|
||
d_max = state.d # Damage at peak load
|
||
|
||
# Unloading
|
||
ε_unloading = range(ε_max, 0, length=100)
|
||
σ_unloading = []
|
||
d_unloading = []
|
||
|
||
for ε in ε_unloading
|
||
ε_tensor = SymmetricTensor{2,3}((ε, 0.0, 0.0, 0.0, 0.0, 0.0))
|
||
σ, state = compute_stress_damage(material, ε_tensor, state)
|
||
|
||
push!(σ_unloading, σ[1, 1])
|
||
push!(d_unloading, state.d)
|
||
end
|
||
|
||
# Validate irreversibility
|
||
@test all(d_unloading .≈ d_max) # Damage does not heal!
|
||
|
||
# Validate reduced stiffness
|
||
E_damaged = (1 - d_max) * material.E
|
||
|
||
# Unloading slope should match damaged stiffness
|
||
# (linear regression on unloading curve)
|
||
ε_unload_vals = collect(ε_unloading)
|
||
slope = (σ_unloading[1] - σ_unloading[end]) / (ε_unload_vals[1] - ε_unload_vals[end])
|
||
|
||
@test isapprox(slope, E_damaged, rtol=0.1)
|
||
|
||
end
|
||
end
|
||
|
||
# =============================================================================
|
||
# CONSISTENT TANGENT (DAMAGE)
|
||
# =============================================================================
|
||
|
||
@testset "Consistent Tangent - Damage (Visionary)" begin
|
||
@test_skip begin
|
||
|
||
material = IsotropicDamage(
|
||
E=200e3,
|
||
ν=0.3,
|
||
damage_threshold=0.001,
|
||
failure_strain=0.01,
|
||
evolution_law=:exponential
|
||
)
|
||
|
||
# Strain state (damaged)
|
||
ε = SymmetricTensor{2,3}((0.003, 0.001, 0.0, 0.001, 0.002, 0.0))
|
||
state = DamageState(d=0.3, ε_eq_max=0.003)
|
||
|
||
# Compute stress and tangent
|
||
σ, state_new, C_damage = compute_stress_tangent_damage(material, ε, state)
|
||
|
||
# Validate tangent via finite difference
|
||
δε = 1e-8
|
||
|
||
for i in 1:6 # Voigt notation
|
||
ε_pert = ε + δε * basis_symmetric_tensor(i)
|
||
σ_pert, _ = compute_stress_damage(material, ε_pert, state)
|
||
|
||
dσ_numerical = (σ_pert - σ) / δε
|
||
dσ_tangent = C_damage ⊡ basis_symmetric_tensor(i)
|
||
|
||
@test isapprox(dσ_numerical, dσ_tangent, rtol=0.01)
|
||
end
|
||
|
||
# Validate symmetry
|
||
for i in 1:6, j in 1:6
|
||
@test isapprox(C_damage[i, j], C_damage[j, i], atol=1e-10)
|
||
end
|
||
|
||
# Validate degradation
|
||
C_elastic = compute_elastic_stiffness(material)
|
||
|
||
# Damaged stiffness should be less
|
||
@test norm(C_damage) < norm(C_elastic)
|
||
|
||
end
|
||
end
|
||
|
||
# =============================================================================
|
||
# PSEUDO-CODE: DAMAGE INTEGRATION
|
||
# =============================================================================
|
||
|
||
@testset "Damage Integration Pattern (Visionary)" begin
|
||
# Pseudo-code showing damage evolution
|
||
|
||
println("\n" * "="^70)
|
||
println("DAMAGE INTEGRATION")
|
||
println("="^70)
|
||
|
||
integration_pseudo = """
|
||
# Damage evolution (strain-based isotropic)
|
||
|
||
function compute_stress_damage(material, ε, state_old)
|
||
# 1. Compute equivalent strain
|
||
ε_eq = compute_equivalent_strain(ε) # e.g., sqrt(ε:ε)
|
||
|
||
# 2. Update history (loading surface)
|
||
ε_eq_max = max(state_old.ε_eq_max, ε_eq)
|
||
|
||
# 3. Check damage threshold
|
||
if ε_eq_max <= material.ε_d0
|
||
# No damage
|
||
d = 0.0
|
||
else
|
||
# Damage evolution
|
||
d = compute_damage(material, ε_eq_max)
|
||
end
|
||
|
||
# 4. Effective stress
|
||
# Strain energy equivalence: W = W̄
|
||
# σ : ε = σ̄ : ε in undamaged configuration
|
||
|
||
# Elastic stress (undamaged)
|
||
C_elastic = compute_elastic_stiffness(material)
|
||
σ_undamaged = C_elastic ⊡ ε
|
||
|
||
# Apply damage
|
||
σ = (1 - d) * σ_undamaged
|
||
|
||
# 5. Update state
|
||
state_new = DamageState(d, ε_eq_max)
|
||
|
||
return σ, state_new
|
||
end
|
||
|
||
# Damage evolution laws
|
||
function compute_damage(material, ε_eq_max)
|
||
ε_d0 = material.damage_threshold
|
||
ε_f = material.failure_strain
|
||
|
||
if material.evolution_law == :linear
|
||
# Linear: d = (ε - ε_d0) / (ε_f - ε_d0)
|
||
d = (ε_eq_max - ε_d0) / (ε_f - ε_d0)
|
||
|
||
elseif material.evolution_law == :exponential
|
||
# Exponential: d = 1 - exp(-α(ε - ε_d0))
|
||
α = -log(0.01) / (ε_f - ε_d0) # d(ε_f) ≈ 0.99
|
||
d = 1 - exp(-α * (ε_eq_max - ε_d0))
|
||
|
||
elseif material.evolution_law == :power
|
||
# Power law: d = ((ε - ε_d0)/(ε_f - ε_d0))^n
|
||
n = 2.0
|
||
d = ((ε_eq_max - ε_d0) / (ε_f - ε_d0))^n
|
||
end
|
||
|
||
return clamp(d, 0.0, 0.99) # Numerical: never fully failed
|
||
end
|
||
"""
|
||
|
||
println(integration_pseudo)
|
||
println("="^70)
|
||
println("✓ Equivalent strain: History variable")
|
||
println("✓ Loading surface: ε_eq_max = max(ε_eq_max_old, ε_eq)")
|
||
println("✓ Damage evolution: d = f(ε_eq_max)")
|
||
println("✓ Effective stress: σ = (1-d) σ_undamaged")
|
||
println("✓ Irreversible: d never decreases")
|
||
println("="^70)
|
||
end
|
||
|
||
# =============================================================================
|
||
# KEY ARCHITECTURAL INSIGHTS
|
||
# =============================================================================
|
||
|
||
println("\n" * "="^70)
|
||
println("DAMAGE MECHANICS ARCHITECTURE INSIGHTS (NEW API)")
|
||
println("="^70)
|
||
println("✓ Isotropic damage: Stiffness degradation (1-d)E")
|
||
println("✓ Damage variable: d ∈ [0,1] (0=intact, 1=failed)")
|
||
println("✓ Irreversible: d never decreases (no healing)")
|
||
println("✓ History: ε_eq_max (maximum strain ever reached)")
|
||
println("✓ Evolution laws: Linear, exponential, power")
|
||
println("✓ Crack band: Mesh-independent G_f via length scale h")
|
||
println("✓ Ductile damage: Coupled with plasticity (Lemaitre)")
|
||
println("✓ Consistent tangent: C_damage = ∂σ/∂ε (with damage)")
|
||
println("✓ Regularization: REQUIRED for mesh objectivity")
|
||
println("✓ Works with Newton-Krylov (tangent from damage law)")
|
||
println("="^70)
|
||
|
||
end
|
||
|
||
"""
|
||
# IMPLEMENTATION NOTES
|
||
|
||
## Isotropic Damage
|
||
|
||
### Damage Variable
|
||
|
||
**Definition:** d ∈ [0,1]
|
||
- d = 0: Intact material
|
||
- d = 1: Completely damaged (failed)
|
||
|
||
**Effective stress concept:**
|
||
σ̄ = σ / (1 - d)
|
||
|
||
where σ̄ = stress in undamaged (effective) configuration.
|
||
|
||
**Strain energy equivalence:**
|
||
W(σ, ε) = W̄(σ̄, ε)
|
||
|
||
Implies:
|
||
σ = (1 - d) σ̄
|
||
|
||
where σ̄ = C_elastic : ε.
|
||
|
||
### Equivalent Strain
|
||
|
||
**For isotropic damage:** Need scalar measure of strain state.
|
||
|
||
**Tension-driven:**
|
||
ε_eq = √(<ε_1>² + <ε_2>² + <ε_3>²)
|
||
|
||
where <·> = positive part, ε_i = principal strains.
|
||
|
||
**Reason:** Damage in tension (cracks open), not compression.
|
||
|
||
**Alternative (modified von Mises):**
|
||
ε_eq = κ I_1 / (1-2ν) + √(3J_2) / (1+ν)
|
||
|
||
where κ weighs volumetric vs deviatoric.
|
||
|
||
### Damage Evolution Laws
|
||
|
||
**Linear:**
|
||
d = (ε_eq - ε_d0) / (ε_f - ε_d0) for ε_eq ∈ [ε_d0, ε_f]
|
||
|
||
**Exponential (smoother):**
|
||
d = 1 - exp(-α(ε_eq - ε_d0))
|
||
|
||
where α chosen such that d(ε_f) ≈ 0.99.
|
||
|
||
**Power law:**
|
||
d = ((ε_eq - ε_d0) / (ε_f - ε_d0))^n
|
||
|
||
where n controls softening rate.
|
||
|
||
### History Variable
|
||
|
||
**Loading surface:** ε_eq_max = max(ε_eq_history)
|
||
|
||
**Damage depends on history:**
|
||
d = f(ε_eq_max) NOT f(ε_eq)
|
||
|
||
**Irreversibility:** ε_eq_max only increases.
|
||
|
||
**Update:**
|
||
```julia
|
||
ε_eq_max_new = max(ε_eq_max_old, ε_eq_current)
|
||
```
|
||
|
||
## Crack Band Regularization
|
||
|
||
### Mesh Sensitivity Problem
|
||
|
||
**Without regularization:** Fracture energy depends on mesh size!
|
||
|
||
G_num = ∫ σ dε * h
|
||
|
||
where h = element size.
|
||
|
||
**Finer mesh → less energy dissipation → spurious brittleness.**
|
||
|
||
### Crack Band Model
|
||
|
||
**Idea:** Fracture happens over a band of width h.
|
||
|
||
**Energy balance:**
|
||
G_f = ∫_0^{ε_f} σ dε * h
|
||
|
||
where G_f = fracture energy per unit area (material property).
|
||
|
||
**Adjust failure strain:**
|
||
ε_f = G_f / (∫_0^{ε_f} σ dε * h)
|
||
|
||
**For linear softening:**
|
||
ε_f = 2 G_f / (σ_max h)
|
||
|
||
where σ_max = tensile strength.
|
||
|
||
**Result:** Mesh-independent fracture energy!
|
||
|
||
### Implementation
|
||
|
||
```julia
|
||
struct IsotropicDamage
|
||
E::Float64
|
||
ν::Float64
|
||
damage_threshold::Float64
|
||
failure_strain::Float64 # Computed from G_f and h!
|
||
crack_band_width::Float64 # h (element size)
|
||
end
|
||
|
||
function IsotropicDamage(; E, ν, G_f, σ_max, h)
|
||
ε_d0 = σ_max / E
|
||
ε_f = ε_d0 + 2*G_f / (σ_max * h) # Linear softening
|
||
|
||
return IsotropicDamage(E, ν, ε_d0, ε_f, h)
|
||
end
|
||
```
|
||
|
||
## Ductile Damage (Lemaitre Model)
|
||
|
||
### Coupling: Damage + Plasticity
|
||
|
||
**Damage drives plasticity:**
|
||
σ_y_eff = σ_y / (1 - d)
|
||
|
||
**Plasticity drives damage:**
|
||
ḋ = f(plastic dissipation)
|
||
|
||
### Lemaitre Damage Evolution
|
||
|
||
**Damage rate:**
|
||
ḋ = (Y / S)^s ε̇_p_eq
|
||
|
||
where:
|
||
- Y = damage energy release rate = (σ_eq²) / (2E(1-d)²)
|
||
- S = material damage strength
|
||
- s = damage exponent
|
||
- ε̇_p_eq = equivalent plastic strain rate
|
||
|
||
**Damage threshold:**
|
||
d = 0 until ε_p_eq > ε_p_threshold
|
||
|
||
**Triaxiality influence:**
|
||
Y = Y(σ_eq, σ_m / σ_eq)
|
||
|
||
where σ_m = mean stress (pressure).
|
||
|
||
**High triaxiality → void growth → more damage.**
|
||
|
||
### Integration
|
||
|
||
```julia
|
||
function compute_ductile_damage(material, σ, ε_p_eq, state)
|
||
if ε_p_eq < material.ε_p_threshold
|
||
return 0.0
|
||
end
|
||
|
||
# Damage energy release rate
|
||
σ_eq = von_mises_stress(σ)
|
||
Y = σ_eq^2 / (2 * material.E * (1 - state.d)^2)
|
||
|
||
# Triaxiality (optional)
|
||
σ_m = trace(σ) / 3
|
||
η = σ_m / σ_eq
|
||
|
||
# Damage increment
|
||
Δε_p = ε_p_eq - state.ε_p_eq_old
|
||
Δd = (Y / material.S)^material.s * Δε_p
|
||
|
||
d_new = state.d + Δd
|
||
|
||
return clamp(d_new, 0.0, 0.99)
|
||
end
|
||
```
|
||
|
||
## Consistent Tangent (Damage)
|
||
|
||
**For Newton:** Need C_damage = dσ/dε.
|
||
|
||
**Elastic damage:**
|
||
σ = (1 - d) C_elastic : ε
|
||
|
||
**Tangent:**
|
||
C_damage = (1 - d) C_elastic + ∂d/∂ε ⊗ σ_elastic
|
||
|
||
where ⊗ = outer product.
|
||
|
||
**Derivative of damage:**
|
||
∂d/∂ε = (∂d/∂ε_eq) (∂ε_eq/∂ε)
|
||
|
||
**Chain rule through damage evolution law.**
|
||
|
||
**For exponential:**
|
||
∂d/∂ε_eq = α exp(-α(ε_eq - ε_d0))
|
||
|
||
**For linear:**
|
||
∂d/∂ε_eq = 1 / (ε_f - ε_d0)
|
||
|
||
### Symmetry
|
||
|
||
**Major symmetry:** C_damage may NOT be symmetric if ∂d/∂ε ⊗ σ not symmetric.
|
||
|
||
**Options:**
|
||
1. Symmetrize: C_sym = (C + C^T) / 2
|
||
2. Use unsymmetric solver (GMRES handles it!)
|
||
|
||
## Internal State Storage
|
||
|
||
**Per integration point:**
|
||
```julia
|
||
struct DamageState
|
||
d::Float64 # Damage variable
|
||
ε_eq_max::Float64 # Maximum equivalent strain (history)
|
||
end
|
||
|
||
# Coupled damage-plasticity
|
||
struct DuctileDamageState{dim}
|
||
ε_p::SymmetricTensor{2,dim}
|
||
ε_p_eq::Float64
|
||
d::Float64
|
||
α::SymmetricTensor{2,dim}
|
||
end
|
||
```
|
||
|
||
**Element-level:**
|
||
```julia
|
||
struct DamageElement
|
||
topology::AbstractTopology
|
||
basis::AbstractBasis
|
||
nodes::NTuple{N,Int}
|
||
state::Vector{DamageState} # Per integration point
|
||
end
|
||
```
|
||
|
||
## Nodal Assembly (Damage)
|
||
|
||
```julia
|
||
function tangent_matvec_damage!(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
|
||
state = states[elem][ip_idx]
|
||
|
||
# Strain
|
||
ε = compute_strain(elem, ip, u_current)
|
||
|
||
# Consistent tangent (with damage)
|
||
σ, state_new, C_damage = compute_stress_tangent_damage(material, ε, state)
|
||
|
||
for node_j in elem.nodes
|
||
# Tangent block (damaged stiffness)
|
||
K_t_ij = compute_damage_tangent_block(elem, node_i, node_j, C_damage, ip)
|
||
|
||
v_j = Vec{3}(v[3*(node_j-1)+1:3*node_j])
|
||
w_local += K_t_ij ⊡ v_j
|
||
end
|
||
|
||
# Update state
|
||
states[elem][ip_idx] = state_new
|
||
end
|
||
end
|
||
|
||
w[3*(node_i-1)+1:3*node_i] = w_local
|
||
end
|
||
end
|
||
```
|
||
|
||
**Key:** Damage state updated each iteration!
|
||
|
||
## Regularization Techniques
|
||
|
||
### 1. Crack Band (Local)
|
||
|
||
**Pros:** Simple, fast
|
||
**Cons:** Still some mesh sensitivity
|
||
|
||
### 2. Nonlocal Damage
|
||
|
||
**Averaged equivalent strain:**
|
||
ε̄_eq(x) = (1/V_R) ∫_{B_R(x)} α(||y-x||) ε_eq(y) dy
|
||
|
||
where:
|
||
- B_R(x) = ball of radius R around x
|
||
- α = weight function (Gaussian)
|
||
|
||
**Damage driven by ε̄_eq instead of ε_eq.**
|
||
|
||
**Pros:** Mesh-independent
|
||
**Cons:** Expensive (nonlocal averaging)
|
||
|
||
### 3. Gradient Damage
|
||
|
||
**Higher-order PDE:**
|
||
ε̄_eq - c ∇²ε̄_eq = ε_eq
|
||
|
||
where c = internal length scale.
|
||
|
||
**Requires additional DOF or coupled system.**
|
||
|
||
**Pros:** Mesh-independent, smooth localization
|
||
**Cons:** Complex implementation
|
||
|
||
### 4. Phase Field (Future)
|
||
|
||
**Crack as diffuse interface:**
|
||
φ(x) ∈ [0,1] where φ=1 is crack.
|
||
|
||
**Coupled:**
|
||
- Elasticity with φ-dependent stiffness
|
||
- Allen-Cahn or Ginzburg-Landau equation for φ
|
||
|
||
**Pros:** Arbitrary crack topology, no remeshing
|
||
**Cons:** Very expensive
|
||
|
||
## Next Steps
|
||
|
||
1. Implement `IsotropicDamage` material type
|
||
2. Implement `DamageState` struct
|
||
3. Implement damage evolution laws
|
||
4. Implement crack band regularization
|
||
5. Implement `DuctileDamage` (coupled)
|
||
6. Implement consistent tangent
|
||
7. Validate mesh independence
|
||
8. Validate against experiments
|
||
9. Performance benchmarks
|
||
|
||
"""
|