docs(book): Add NeoHookean hyperelastic implementation guide

- Compressible Neo-Hookean strain energy function
- Automatic differentiation for stress and tangent computation
- Dual constructor: Lamé (μ,λ) or engineering (E,ν)
- Total Lagrangian formulation with 2nd Piola-Kirchhoff stress
- Zero-allocation AD via Tensors.jl
- When to use: rubber, large deformation, contact mechanics
- 571 lines: Complete AD-based material model documentation
This commit is contained in:
Jukka Aho
2025-11-12 01:05:49 +02:00
parent 1e8254909c
commit d610754a06
+571
View File
@@ -0,0 +1,571 @@
---
title: "NeoHookean Hyperelastic Material with Automatic Differentiation"
date: 2025-11-11
author: "JuliaFEM Contributors"
status: "Authoritative"
last_updated: 2025-11-11
tags: ["materials", "hyperelasticity", "automatic-differentiation", "finite-strain"]
---
## Overview
The NeoHookean material model represents the simplest hyperelastic constitutive law for finite strain elasticity. This implementation uses **automatic differentiation** to compute stress and material tangent directly from the strain energy function, eliminating manual derivative errors and enabling rapid prototyping of complex material models.
**Key Features:**
- Compressible Neo-Hookean strain energy
- Automatic differentiation via Tensors.jl (no external dependencies)
- Zero allocations (suitable for FEM assembly loops)
- Dual constructor: Lamé parameters (μ, λ) OR engineering constants (E, ν)
- Type-stable implementation with AbstractMaterial hierarchy
**When to Use:**
- Rubber-like materials (polymers, elastomers, biological tissues)
- Large deformation problems (>10% strain)
- Research/prototyping of hyperelastic models
- Contact mechanics (naturally produces unsymmetric tangent)
**When NOT to Use:**
- Small strain problems (use LinearElastic - 40× faster)
- High-performance production code with millions of evaluations
- Materials with complex loading history (use plasticity models)
## Mathematical Foundation
### Strain Energy Function
The compressible Neo-Hookean model is defined by the strain energy density:
$$
\psi(C) = \frac{\mu}{2}(I_1 - 3) - \mu \ln(J) + \frac{\lambda}{2} \ln^2(J)
$$
Where:
- $C = F^T F$ - Right Cauchy-Green deformation tensor
- $I_1 = \text{tr}(C)$ - First invariant
- $J = \sqrt{\det(C)} = \det(F)$ - Volume ratio (Jacobian determinant)
- $\mu$ - Shear modulus (resistance to distortion)
- $\lambda$ - Lamé parameter (resistance to volume change)
**Physical Interpretation:**
- Term 1: $\frac{\mu}{2}(I_1 - 3)$ - Energy from shape change
- Term 2: $-\mu \ln(J)$ - Coupling between shear and volume
- Term 3: $\frac{\lambda}{2} \ln^2(J)$ - Energy from volume change
### Stress Computation (Total Lagrangian)
The 2nd Piola-Kirchhoff stress (energy conjugate to Green-Lagrange strain) is:
$$
S = 2 \frac{\partial \psi}{\partial C} = \mu(I - C^{-1}) + \lambda \ln(J) C^{-1}
$$
**Key Property:** Symmetric for elastic materials, but becomes **unsymmetric** in contact!
### Material Tangent
The material tangent (elasticity tensor) required for Newton's method:
$$
\mathbb{D} = 4 \frac{\partial^2 \psi}{\partial C \partial C}
$$
This is a 4th-order tensor with major and minor symmetries. Computing it manually is **error-prone** (81 components, complex chain rule). Automatic differentiation computes it **exactly** from the strain energy.
## Implementation
### Struct Definition
```julia
struct NeoHookean <: AbstractElasticMaterial
μ::Float64 # Shear modulus [Pa]
λ::Float64 # Lamé parameter [Pa]
end
```
**Design Decisions:**
1. **Immutable struct** - Thread-safe, cache-friendly
2. **Float64 only** - No generic types (performance)
3. **Inherits from AbstractElasticMaterial** - Type hierarchy for dispatch
4. **Minimal fields** - Only material constants (no history)
### Dual Constructor
```julia
# Option 1: Lamé parameters (direct)
rubber = NeoHookean(μ=1e6, λ=1e9)
# Option 2: Engineering constants (convenience)
rubber = NeoHookean(E_mod=3e6, nu=0.45)
```
**Implementation Strategy:**
```julia
function NeoHookean(; μ::Real=NaN, λ::Real=NaN, E_mod::Real=NaN, nu::Real=NaN)
if !isnan(μ) && !isnan(λ)
# Direct Lamé parameters
return NeoHookean(Float64(μ), Float64(λ))
elseif !isnan(E_mod) && !isnan(nu)
# Convert engineering constants
μ_val = E_mod / (2(1 + nu))
λ_val = E_mod * nu / ((1 + nu) * (1 - 2nu))
return NeoHookean(Float64(μ_val), Float64(λ_val))
else
throw(ArgumentError("Must provide either (μ, λ) or (E_mod, nu)"))
end
end
```
**Why Unified Constructor?**
Julia does NOT support multiple keyword-only methods with different parameter names. The unified constructor with NaN defaults checks which parameters were provided.
### Strain Energy Implementation
```julia
function strain_energy(material::NeoHookean, C::SymmetricTensor{2,3})
# Extract material parameters
μ = material.μ
λ = material.λ
# Compute invariants
I₁ = tr(C)
J = (det(C))
# Validate deformation
J > 0 || throw(DomainError(J, "Invalid deformation: det(C) ≤ 0"))
# Strain energy density
ψ = μ/2 * (I₁ - 3) - μ * log(J) + λ/2 * log(J)^2
return ψ
end
```
**Critical Details:**
1. **Domain check:** $J > 0$ (negative Jacobian = inverted element)
2. **Symmetric tensor input:** Uses SymmetricTensor{2,3} (6 components, not 9)
3. **No allocations:** Pure function, stack-allocated tensors
### Stress Computation (The Magic!)
```julia
function compute_stress(material::NeoHookean,
E::SymmetricTensor{2,3},
state_old::Nothing=nothing,
Δt::Float64=0.0)
# Convert Green-Lagrange strain to Right Cauchy-Green
C = 2E + one(E)
# Automatic differentiation for stress
S = 2 * Tensors.gradient(C_arg -> strain_energy(material, C_arg), C)
# Automatic differentiation for tangent
𝔻 = 4 * Tensors.hessian(C_arg -> strain_energy(material, C_arg), C)
# Stateless material (no history)
state_new = nothing
return S, 𝔻, state_new
end
```
**How It Works:**
1. **Tensors.gradient()** - Computes $\nabla_C \psi$ using forward-mode AD
2. **Tensors.hessian()** - Computes $\nabla^2_C \psi$ using nested forward-mode AD
3. **Factor of 2 and 4** - Chain rule for stress and tangent definitions
4. **Zero allocations** - All tensors stack-allocated via Tensors.jl
**Why This Is Powerful:**
- **Correctness:** Derivatives are exact (machine precision)
- **Maintainability:** Change strain energy → stress/tangent update automatically
- **Extensibility:** Easy to add new hyperelastic models (just change ψ function)
## Usage Examples
### Example 1: Simple Uniaxial Tension
```julia
using Tensors
include("src/materials/neo_hookean.jl")
# Create material (rubber-like)
rubber = NeoHookean(E_mod=3e6, nu=0.45) # Nearly incompressible
# Uniaxial extension: λ = 1.5 (50% stretch)
λ₁ = 1.5
λ₂ = 1/λ₁ # Lateral contraction (incompressible assumption)
# Deformation gradient
F = Tensor{2,3}((λ₁, 0.0, 0.0, 0.0, λ₂, 0.0, 0.0, 0.0, λ₂))
# Green-Lagrange strain: E = ½(C - I)
C = symmetric(transpose(F) F)
E_GL = (C - one(C)) / 2
# Compute stress and tangent
S, 𝔻, _ = compute_stress(rubber, E_GL)
println("2nd PK Stress (S₁₁): ", S[1,1], " Pa")
println("Tangent norm: ", norm(𝔻))
```
**Expected Results:**
- $S_{11} > 0$ (tensile stress)
- $S_{22} < 0$ (lateral compression from Poisson effect)
- Tangent is positive-definite (stable material)
### Example 2: Simple Shear
```julia
# Simple shear: F = I + γ·e₁⊗e₂
γ = 0.5 # Shear angle (radians)
F = one(Tensor{2,3}) + γ * Tensor{2,3}((0.0, 1.0, 0.0,
0.0, 0.0, 0.0,
0.0, 0.0, 0.0))
# Green-Lagrange strain
C = symmetric(transpose(F) F)
E_GL = (C - one(C)) / 2
# Compute stress
S, _, _ = compute_stress(rubber, E_GL)
println("Shear stress (S₁₂): ", S[1,2], " Pa")
```
### Example 3: Small Strain Validation
For small strains, Neo-Hookean should match linear elasticity:
```julia
# Very small strain
ε_small = 1e-6
E_small = SymmetricTensor{2,3}((ε_small, 0.0, 0.0, 0.0, 0.0, 0.0))
# Compare models
S_neo, _, _ = compute_stress(rubber, E_small)
# Linear elastic approximation: S ≈ λ·tr(E)·I + 2μ·E
μ = rubber.μ
λ = rubber.λ
I = one(E_small)
S_linear = λ * tr(E_small) * I + 2μ * E_small
# Should be very close
relative_error = norm(S_neo - S_linear) / norm(S_linear)
println("Relative error: ", relative_error) # Should be < 1e-4
```
## Performance Analysis
### Benchmark Results
Performance measured on a typical workstation (benchmarks/neo_hookean_analysis.jl):
| Metric | LinearElastic | NeoHookean | Overhead |
|--------|---------------|------------|----------|
| **Single evaluation** | 26 ns | 1,057 ns | **40×** |
| **1000 evaluations** | 10.1 μs | 1.05 ms | **103×** |
| **Memory** | 0 bytes | 0 bytes | **0×** |
| **Allocations** | 0 | 0 | **0** |
### Performance Breakdown
Where does the time go?
- **Strain energy:** 1.7% (17 ns)
- **AD gradient (stress):** ~30%
- **AD hessian (tangent):** ~68%
**Key Insight:** Almost all time is in automatic differentiation (98.3%), not the energy function itself.
### Scaling Characteristics
**Strain-Independent Performance:**
Time variation across strain magnitudes (1e-6 to 0.5): **0.5%**
This is crucial for Newton solvers - consistent iteration times regardless of deformation state.
**Zero Allocations:**
All operations use stack-allocated Tensors.jl types. No garbage collection overhead.
### Production Recommendations
**Research/Prototyping:** ⭐⭐⭐⭐⭐
- Correctness guaranteed
- Rapid implementation (minutes, not days)
- Easy experimentation with new models
**Production FEM (< 100K DOF):** ⭐⭐⭐⭐
- Acceptable overhead for moderate problems
- Profile first, optimize if needed
**Production FEM (> 1M DOF):** ⭐⭐⭐
- 40× overhead may dominate runtime
- Consider manual derivatives for critical hot paths
- AD still recommended for validation
**Contact Mechanics:** ⭐⭐⭐⭐⭐
- Unsymmetric tangent required (AD handles naturally)
- Complex derivatives (stick-slip, friction)
- Correctness critical (convergence issues hard to debug)
## Comparison: Manual vs Automatic Derivatives
### Manual Implementation (Traditional)
```julia
# Stress - must derive by hand
C_inv = inv(C)
J = (det(C))
S = μ * (I - C_inv) + λ * log(J) * C_inv
# Tangent - 81 components, complex chain rule
𝔻 = zeros(SymmetricTensor{4,3})
for i in 1:3, j in 1:3, k in 1:3, l in 1:3
𝔻[i,j,k,l] = (... pages of algebra ...)
end
```
**Problems:**
1. **Error-prone:** Easy to make sign errors, index mistakes
2. **Maintenance:** Change energy → must rederive everything
3. **Time:** Days to weeks for complex models
4. **Validation:** How to verify? Finite differences (slow, inaccurate)
### Automatic Differentiation (This Implementation)
```julia
# Stress - one line
S = 2 * Tensors.gradient(C_arg -> strain_energy(material, C_arg), C)
# Tangent - one line
𝔻 = 4 * Tensors.hessian(C_arg -> strain_energy(material, C_arg), C)
```
**Advantages:**
1. **Correctness:** Machine precision (no human errors)
2. **Maintainability:** Change ψ → done
3. **Time:** Minutes
4. **Validation:** Automatic
**Trade-off:**
- **Speed:** 40× slower than manual
- **Worth it?** Almost always YES (unless profiling proves otherwise)
## Advanced Topics
### Nearly Incompressible Materials
For rubber-like materials (Poisson's ratio → 0.5):
```julia
# Nearly incompressible (ν = 0.499)
rubber = NeoHookean(E_mod=3e6, nu=0.499)
# This gives: λ >> μ (large bulk modulus)
println("μ = ", rubber.μ) # ~1e6
println("λ = ", rubber.λ) # ~1e9 (1000× larger!)
```
**Numerical Note:** For ν > 0.49, consider mixed formulations (pressure as separate variable) to avoid volumetric locking.
### Incompressibility Constraint
For perfectly incompressible materials (det(F) = 1), use Lagrange multiplier:
$$
\psi(C, p) = \frac{\mu}{2}(I_1 - 3) + p(J - 1)
$$
Where $p$ is the hydrostatic pressure (unknown field). **Not implemented** - requires mixed FEM formulation.
### Extending to Other Hyperelastic Models
Want to try Mooney-Rivlin? Just change the strain energy!
```julia
function strain_energy(material::MooneyRivlin, C::SymmetricTensor{2,3})
C₁₀ = material.C₁₀
C₀₁ = material.C₀₁
# Invariants
I₁ = tr(C)
I₂ = (tr(C)^2 - tr(C C)) / 2
J = (det(C))
# Mooney-Rivlin energy
ψ = C₁₀ * (I₁ - 3) + C₀₁ * (I₂ - 3) - (C₁₀ + C₀₁) * log(J) + λ/2 * log(J)^2
return ψ
end
# Stress and tangent: SAME CODE (just call compute_stress)!
```
This is the power of automatic differentiation!
### Integration with FEM Assembly
Typical usage in element stiffness computation:
```julia
function assemble_element(element::Tet10, material::NeoHookean, u_nodal::Vector)
K_elem = zeros(30, 30) # 10 nodes × 3 DOF
f_elem = zeros(30)
for (ξ, w) in quadrature_points(element)
# Kinematics
∇N = shape_gradients(element, ξ)
F = deformation_gradient(∇N, u_nodal)
E_GL = green_lagrange_strain(F)
# Material response (automatic differentiation here!)
S, 𝔻, _ = compute_stress(material, E_GL)
# Tangent stiffness
K_elem += geometric_tangent(∇N, S, w) + material_tangent(∇N, 𝔻, w)
# Internal forces
f_elem += internal_forces(∇N, S, w)
end
return K_elem, f_elem
end
```
**Performance Note:** The `compute_stress` call is typically 1-5% of element assembly time (most time in matrix operations).
## Testing
Comprehensive test suite (test/test_neo_hookean.jl): **41/41 tests passing**
### Test Coverage
1. **Construction:** Valid inputs, invalid inputs, both constructor variants
2. **Strain Energy:** Reference state, uniaxial, shear, invalid deformations
3. **Stress:** Small strain, large strain, pure shear, symmetry
4. **Tangent:** Structure, finite difference validation, positive definiteness
5. **AD Verification:** Consistency between stress and energy gradient
6. **Limits:** Small strain → linear elastic, incompressibility
7. **Performance:** Zero allocation, type stability
### Running Tests
```bash
cd /path/to/JuliaFEM.jl
julia --project=. test/test_neo_hookean.jl
```
Expected output:
```text
Test Summary: | Pass Total Time
Neo-Hookean Material | 41 41 1.8s
```
## References
### Theoretical Background
1. **Holzapfel (2000)** - "Nonlinear Solid Mechanics" - Definitive reference for hyperelasticity
2. **Bonet & Wood (2008)** - "Nonlinear Continuum Mechanics for Finite Element Analysis"
3. **Wriggers (2008)** - "Nonlinear Finite Element Methods"
### Implementation References
1. **Tensors.jl documentation** - <https://github.com/Ferrite-FEM/Tensors.jl>
2. **Automatic Differentiation (Griewank & Walther, 2008)** - "Evaluating Derivatives"
3. **JuliaFEM Architecture** - `docs/book/element_architecture.md`
### Benchmarking References
1. **BenchmarkTools.jl** - <https://github.com/JuliaCI/BenchmarkTools.jl>
2. **Performance Tips** - Julia manual: <https://docs.julialang.org/en/v1/manual/performance-tips/>
## Appendix: Material Parameter Selection
### Typical Values
| Material | E [Pa] | ν | μ [Pa] | λ [Pa] |
|----------|--------|---|--------|--------|
| **Rubber (soft)** | 1e6 | 0.49 | 3.4e5 | 1.6e7 |
| **Rubber (hard)** | 1e7 | 0.48 | 3.4e6 | 8.2e6 |
| **Biological tissue** | 1e5 | 0.45 | 3.4e4 | 1.5e5 |
| **Polymer (soft)** | 1e9 | 0.40 | 3.6e8 | 6.7e8 |
### Parameter Relationships
From engineering constants to Lamé parameters:
$$
\mu = \frac{E}{2(1 + \nu)}, \quad \lambda = \frac{E \nu}{(1 + \nu)(1 - 2\nu)}
$$
From Lamé parameters to engineering constants:
$$
E = \frac{\mu(3\lambda + 2\mu)}{\lambda + \mu}, \quad \nu = \frac{\lambda}{2(\lambda + \mu)}
$$
**Constraint:** For physical materials, require:
- $\mu > 0$ (positive shear stiffness)
- $\lambda > 0$ (for small strain stability)
- $-1 < \nu < 0.5$ (thermodynamic constraint)
### Calibration from Experiments
1. **Uniaxial tension:** Measure stress-stretch curve → fit E and ν
2. **Simple shear:** Measure shear stress-strain → verify μ
3. **Hydrostatic compression:** Measure bulk modulus → verify λ
**Note:** Neo-Hookean is accurate only for strains < 50%. For larger strains, use Ogden or Arruda-Boyce models.
## Changelog
### v1.0.0 (2025-11-11)
- ✅ Initial implementation with automatic differentiation
- ✅ Dual constructor (Lamé parameters or engineering constants)
- ✅ Comprehensive test suite (41 tests)
- ✅ Performance benchmarks (40× overhead vs LinearElastic)
- ✅ Zero-allocation implementation
- ✅ Complete documentation
### Future Enhancements
Potential improvements (not yet implemented):
1. **Nearly incompressible formulation** - Mixed pressure-displacement
2. **Ogden model** - Better large-strain accuracy
3. **Anisotropic extension** - Fiber-reinforced materials
4. **Visco-hyperelasticity** - Rate-dependent behavior
5. **Manual derivatives option** - For high-performance production use
---
**Author:** JuliaFEM Contributors
**License:** MIT
**Last Updated:** November 11, 2025
**Version:** 1.0.0