- 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
16 KiB
title, date, author, status, last_updated, tags
| title | date | author | status | last_updated | tags | ||||
|---|---|---|---|---|---|---|---|---|---|
| NeoHookean Hyperelastic Material with Automatic Differentiation | 2025-11-11 | JuliaFEM Contributors | Authoritative | 2025-11-11 |
|
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 tensorI_1 = \text{tr}(C)- First invariantJ = \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
struct NeoHookean <: AbstractElasticMaterial
μ::Float64 # Shear modulus [Pa]
λ::Float64 # Lamé parameter [Pa]
end
Design Decisions:
- Immutable struct - Thread-safe, cache-friendly
- Float64 only - No generic types (performance)
- Inherits from AbstractElasticMaterial - Type hierarchy for dispatch
- Minimal fields - Only material constants (no history)
Dual Constructor
# Option 1: Lamé parameters (direct)
rubber = NeoHookean(μ=1e6, λ=1e9)
# Option 2: Engineering constants (convenience)
rubber = NeoHookean(E_mod=3e6, nu=0.45)
Implementation Strategy:
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
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:
- Domain check:
J > 0(negative Jacobian = inverted element) - Symmetric tensor input: Uses SymmetricTensor{2,3} (6 components, not 9)
- No allocations: Pure function, stack-allocated tensors
Stress Computation (The Magic!)
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:
- Tensors.gradient() - Computes
\nabla_C \psiusing forward-mode AD - Tensors.hessian() - Computes
\nabla^2_C \psiusing nested forward-mode AD - Factor of 2 and 4 - Chain rule for stress and tangent definitions
- 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
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
# 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:
# 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)
# 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:
- Error-prone: Easy to make sign errors, index mistakes
- Maintenance: Change energy → must rederive everything
- Time: Days to weeks for complex models
- Validation: How to verify? Finite differences (slow, inaccurate)
Automatic Differentiation (This Implementation)
# 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:
- Correctness: Machine precision (no human errors)
- Maintainability: Change ψ → done
- Time: Minutes
- 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):
# 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!
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:
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
- Construction: Valid inputs, invalid inputs, both constructor variants
- Strain Energy: Reference state, uniaxial, shear, invalid deformations
- Stress: Small strain, large strain, pure shear, symmetry
- Tangent: Structure, finite difference validation, positive definiteness
- AD Verification: Consistency between stress and energy gradient
- Limits: Small strain → linear elastic, incompressibility
- Performance: Zero allocation, type stability
Running Tests
cd /path/to/JuliaFEM.jl
julia --project=. test/test_neo_hookean.jl
Expected output:
Test Summary: | Pass Total Time
Neo-Hookean Material | 41 41 1.8s
References
Theoretical Background
- Holzapfel (2000) - "Nonlinear Solid Mechanics" - Definitive reference for hyperelasticity
- Bonet & Wood (2008) - "Nonlinear Continuum Mechanics for Finite Element Analysis"
- Wriggers (2008) - "Nonlinear Finite Element Methods"
Implementation References
- Tensors.jl documentation - https://github.com/Ferrite-FEM/Tensors.jl
- Automatic Differentiation (Griewank & Walther, 2008) - "Evaluating Derivatives"
- JuliaFEM Architecture -
docs/book/element_architecture.md
Benchmarking References
- BenchmarkTools.jl - https://github.com/JuliaCI/BenchmarkTools.jl
- 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
- Uniaxial tension: Measure stress-stretch curve → fit E and ν
- Simple shear: Measure shear stress-strain → verify μ
- 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):
- Nearly incompressible formulation - Mixed pressure-displacement
- Ogden model - Better large-strain accuracy
- Anisotropic extension - Fiber-reinforced materials
- Visco-hyperelasticity - Rate-dependent behavior
- Manual derivatives option - For high-performance production use
Author: JuliaFEM Contributors
License: MIT
Last Updated: November 11, 2025
Version: 1.0.0