Files
JuliaFEM.jl/test/geometry/test_deformation_gradient.jl
T
Jukka Aho 1ed455604f test(geometry): add deformation gradient test
New 561-line test file for deformation gradient computation:
- Tests F = ∂x/∂X = I + ∂u/∂X for finite strain mechanics
- Tests identity case (u = 0 → F = I)
- Tests pure translation (rigid body motion)
- Tests pure stretch and simple shear cases
- Tests small vs finite strain difference
- Validates physical constraints (det(F) > 0)
- Tests Hex8 and Tet10 elements
- Validates zero-allocation performance

Comprehensive test for deformation gradient computation essential
for finite strain analysis and hyperelastic materials.
2025-12-15 08:13:44 +02:00

562 lines
17 KiB
Julia
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
"""
# Deformation Gradient Tests (test/geometry/)
## What
Tests computation of the deformation gradient **F = ∂x/∂X = I + ∂u/∂X**, which maps
from reference (undeformed) to current (deformed) configuration in finite strain mechanics.
## Why
The deformation gradient is **FUNDAMENTAL** to finite strain analysis:
- **Green-Lagrange strain**: E = ½(F^T F - I)
- **Cauchy-Green deformation tensor**: C = F^T F
- **Volume ratio**: J = det(F) (incompressibility requires J = 1)
- **Polar decomposition**: F = RU (rotation × stretch)
- **Material frame**: All stress/strain in reference configuration
Physical requirements:
- **det(F) > 0**: No material inversion (orientation preserved)
- **det(F) ≈ 1**: Nearly incompressible materials (rubber, metal plasticity)
- **F = I**: Undeformed configuration (u = 0)
This test validates:
- **Identity case**: u = 0 → F = I, det(F) = 1
- **Pure translation**: ∇u = 0 → F = I (rigid body motion)
- **Pure stretch**: Diagonal F with stretch ratios
- **Simple shear**: Off-diagonal F terms
- **Small vs finite strain**: Difference in formulations
- **Physical constraints**: det(F) > 0 always
- **Zero allocations**: Hot path allocates nothing
## How
**Test Cases:**
**1. Identity (u = 0)**:
- Unit cube Hex8 element, zero displacement
- Expected: F = I, det(F) = 1.0
- Both finite strain and small strain give same result
**2. Pure Translation**:
- Uniform displacement u = (0.5, 0.5, 0.5) at all nodes
- Expected: ∇u = 0 → F = I + 0 = I
- Validates that rigid body motion doesn't deform material
**3. Pure Stretch (x-direction)**:
- Displacement u_x = 0.1·X (10% engineering strain)
- Expected: F = diag(1.1, 1.0, 1.0), det(F) = 1.1
- Validates uniaxial extension
**4. Simple Shear**:
- Displacement u_x = 0.1·y (shear deformation)
- Expected: F_{12} = 0.1, det(F) = 1.0 (volume preserving)
- Validates shear kinematics
**5. Small vs Finite Strain Difference**:
- 20% stretch: significant displacement gradient
- **Finite strain**: F = I + ∇u (includes gradient)
- **Small strain**: F = I (ignores gradient, approximation)
- Validates that formulations differ for large deformations
**6. Physical Constraint**:
- All physical deformations must have det(F) > 0
- Negative det(F) → inverted element (unphysical)
**7. Tet10 (Quadratic) Elements**:
- Higher-order elements with 10 nodes
- Same tests as Hex8 but with quadratic basis
- Validates that API works for all element types
**8. Zero Allocations**:
- `@allocated compute_deformation_gradient(...)` must return 0
- Critical for performance in assembly loops
## Expected Results
- ✅ **Identity**: F = I, det(F) = 1.0
- ✅ **Translation**: F = I (∇u = 0)
- ✅ **Stretch**: F_{ii} = 1 + ε_{ii}, det(F) = product of stretches
- ✅ **Shear**: Off-diagonal terms non-zero, det(F) = 1.0
- ✅ **Finite ≠ Small**: Different F for large deformations (20%+)
- ✅ **Physical**: det(F) > 0 always
- ✅ **Tet10**: Works with quadratic elements
- ✅ **Zero allocations**: @allocated = 0
## Mathematical Background
**Deformation Gradient (Finite Strain):**
```
F = ∂x/∂X = I + ∂u/∂X
```
Where:
- x = X + u (current position = reference + displacement)
- X = reference coordinates
- u = displacement vector
- ∂u/∂X = displacement gradient
**Small Strain Approximation:**
```
F ≈ I (ignores ∂u/∂X, valid for ||∇u|| << 1)
```
**Computation:**
```julia
# Displacement gradient
∇u = ∑ᵢ uᵢ ⊗ (∂Nᵢ/∂X)
# Physical derivatives
∂Nᵢ/∂X = J⁻¹ · ∂Nᵢ/∂ξ
# Deformation gradient
F = I + ∇u
```
## Formulation Comparison
**Finite Strain** (use when ||∇u|| > 0.01):
- F = I + ∇u (full nonlinear kinematics)
- E = ½(F^T F - I) (Green-Lagrange strain)
- S = ∂W/∂E (2nd Piola-Kirchhoff stress)
- Required for: Rubber, large rotations, metal forming
**Small Strain** (use when ||∇u|| < 0.01):
- F ≈ I (linear approximation)
- ε = ½(∇u + ∇u^T) (engineering strain)
- σ = C:ε (Cauchy stress)
- Valid for: Linear elasticity, small vibrations
## Architecture Principle
**Tensors.jl for Kinematics**
All kinematic quantities use Tensors.jl:
- Displacement: Vec{3,Float64}
- Gradient: Tensor{2,3} (⊗ outer product)
- Deformation gradient: Tensor{2,3}
- Identity: one(Tensor{2,3})
This provides:
- Natural tensor notation (F[i,j])
- Automatic differentiation ready
- Zero-allocation operations
- GPU compatible
## Usage Pattern
```julia
# In assembly loop:
for ip in integration_points
# Get basis derivatives
dN_dξ = get_basis_derivatives(topology, basis, ip.ξ)
# Compute Jacobian
J = compute_jacobian(X_nodes, dN_dξ)
# Compute deformation gradient
F = compute_deformation_gradient(X_nodes, u_nodes, dN_dξ, J, FiniteStrain())
# Compute strain
E = 0.5 * (F' ⊡ F - I)
# Material model
S, 𝔻, state = compute_stress(material, E, state_old, Δt)
# ... assemble
end
```
## Critical Performance Path
Deformation gradient is computed at EVERY integration point, EVERY Newton iteration.
For 100k elements × 8 integration points × 10 Newton iterations = 8M calls!
Zero allocations are MANDATORY!
"""
using Test
using Tensors
using LinearAlgebra
# Use JuliaFEM for basis functions
using JuliaFEM
# Load our new deformation gradient code
include("../src/physics/deformation_gradient.jl")
@testset "Deformation Gradient - Low Level API" begin
@testset "Identity case (u = 0)" begin
# Unit cube element, no displacement
X_nodes = (
Vec(0.0, 0.0, 0.0),
Vec(1.0, 0.0, 0.0),
Vec(1.0, 1.0, 0.0),
Vec(0.0, 1.0, 0.0),
Vec(0.0, 0.0, 1.0),
Vec(1.0, 0.0, 1.0),
Vec(1.0, 1.0, 1.0),
Vec(0.0, 1.0, 1.0)
)
# Zero displacement
u_nodes = tuple([zero(Vec{3,Float64}) for _ in 1:8]...)
# At element center ξ = (0, 0, 0)
ξ = Vec(0.0, 0.0, 0.0)
# Hex8 basis function derivatives at center (new API)
dN_dξ = get_basis_derivatives(Hexahedron(), Lagrange{Hexahedron,1}(), ξ)
# Compute Jacobian
J = zero(Tensor{2,3,Float64,9})
for i in 1:8
J += X_nodes[i] dN_dξ[i]
end
# Finite strain: Should give F = I + 0 = I
F_finite = compute_deformation_gradient(X_nodes, u_nodes, dN_dξ, J, FiniteStrain())
@test F_finite one(Tensor{2,3})
@test det(F_finite) 1.0
# Small strain: Should also give F = I
F_small = compute_deformation_gradient(X_nodes, u_nodes, dN_dξ, J, SmallStrain())
@test F_small one(Tensor{2,3})
@test det(F_small) 1.0
end
@testset "Pure translation" begin
# Unit cube
X_nodes = (
Vec(0.0, 0.0, 0.0),
Vec(1.0, 0.0, 0.0),
Vec(1.0, 1.0, 0.0),
Vec(0.0, 1.0, 0.0),
Vec(0.0, 0.0, 1.0),
Vec(1.0, 0.0, 1.0),
Vec(1.0, 1.0, 1.0),
Vec(0.0, 1.0, 1.0)
)
# Uniform translation: u = (0.5, 0.5, 0.5) everywhere
u_const = Vec(0.5, 0.5, 0.5)
u_nodes = tuple([u_const for _ in 1:8]...)
ξ = Vec(0.0, 0.0, 0.0)
dN_dξ = get_basis_derivatives(Hexahedron(), Lagrange{Hexahedron,1}(), ξ)
J = zero(Tensor{2,3,Float64,9})
for i in 1:8
J += X_nodes[i] dN_dξ[i]
end
# Pure translation ⇒ ∇u = 0 ⇒ F = I
F = compute_deformation_gradient(X_nodes, u_nodes, dN_dξ, J, FiniteStrain())
@test F one(Tensor{2,3})
@test det(F) 1.0
end
@testset "Pure stretch in x-direction" begin
# Unit cube
X_nodes = (
Vec(0.0, 0.0, 0.0),
Vec(1.0, 0.0, 0.0),
Vec(1.0, 1.0, 0.0),
Vec(0.0, 1.0, 0.0),
Vec(0.0, 0.0, 1.0),
Vec(1.0, 0.0, 1.0),
Vec(1.0, 1.0, 1.0),
Vec(0.0, 1.0, 1.0)
)
# Stretch: u_x = 0.1 * X (10% stretch in x)
u_nodes = (
Vec(0.0, 0.0, 0.0), # u = 0.1 * 0 = 0
Vec(0.1, 0.0, 0.0), # u = 0.1 * 1 = 0.1
Vec(0.1, 0.0, 0.0), # u = 0.1 * 1 = 0.1
Vec(0.0, 0.0, 0.0), # u = 0.1 * 0 = 0
Vec(0.0, 0.0, 0.0), # u = 0.1 * 0 = 0
Vec(0.1, 0.0, 0.0), # u = 0.1 * 1 = 0.1
Vec(0.1, 0.0, 0.0), # u = 0.1 * 1 = 0.1
Vec(0.0, 0.0, 0.0) # u = 0.1 * 0 = 0
)
ξ = Vec(0.0, 0.0, 0.0)
dN_dξ = get_basis_derivatives(Hexahedron(), Lagrange{Hexahedron,1}(), ξ)
J = zero(Tensor{2,3,Float64,9})
for i in 1:8
J += X_nodes[i] dN_dξ[i]
end
F = compute_deformation_gradient(X_nodes, u_nodes, dN_dξ, J, FiniteStrain())
# Expected: F = [1.1 0 0]
# [0 1 0]
# [0 0 1]
@test F[1, 1] 1.1 atol = 1e-10
@test F[2, 2] 1.0 atol = 1e-10
@test F[3, 3] 1.0 atol = 1e-10
@test F[1, 2] 0.0 atol = 1e-10
@test F[1, 3] 0.0 atol = 1e-10
@test F[2, 3] 0.0 atol = 1e-10
@test det(F) 1.1 atol = 1e-10
end
@testset "Simple shear" begin
# Unit cube
X_nodes = (
Vec(0.0, 0.0, 0.0),
Vec(1.0, 0.0, 0.0),
Vec(1.0, 1.0, 0.0),
Vec(0.0, 1.0, 0.0),
Vec(0.0, 0.0, 1.0),
Vec(1.0, 0.0, 1.0),
Vec(1.0, 1.0, 1.0),
Vec(0.0, 1.0, 1.0)
)
# Shear: u_x = 0.1 * y
u_nodes = (
Vec(0.0, 0.0, 0.0), # y=0
Vec(0.0, 0.0, 0.0), # y=0
Vec(0.1, 0.0, 0.0), # y=1
Vec(0.1, 0.0, 0.0), # y=1
Vec(0.0, 0.0, 0.0), # y=0
Vec(0.0, 0.0, 0.0), # y=0
Vec(0.1, 0.0, 0.0), # y=1
Vec(0.1, 0.0, 0.0) # y=1
)
ξ = Vec(0.0, 0.0, 0.0)
dN_dξ = get_basis_derivatives(Hexahedron(), Lagrange{Hexahedron,1}(), ξ)
J = zero(Tensor{2,3,Float64,9})
for i in 1:8
J += X_nodes[i] dN_dξ[i]
end
F = compute_deformation_gradient(X_nodes, u_nodes, dN_dξ, J, FiniteStrain())
# Expected: F = [1 0.1 0]
# [0 1 0]
# [0 0 1]
@test F[1, 1] 1.0 atol = 1e-10
@test F[1, 2] 0.1 atol = 1e-10
@test F[2, 2] 1.0 atol = 1e-10
@test F[3, 3] 1.0 atol = 1e-10
@test det(F) 1.0 atol = 1e-10
end
@testset "Small vs Finite strain difference" begin
# Setup with significant displacement gradient
X_nodes = (
Vec(0.0, 0.0, 0.0),
Vec(1.0, 0.0, 0.0),
Vec(1.0, 1.0, 0.0),
Vec(0.0, 1.0, 0.0),
Vec(0.0, 0.0, 1.0),
Vec(1.0, 0.0, 1.0),
Vec(1.0, 1.0, 1.0),
Vec(0.0, 1.0, 1.0)
)
# 20% stretch in x
u_nodes = (
Vec(0.0, 0.0, 0.0),
Vec(0.2, 0.0, 0.0),
Vec(0.2, 0.0, 0.0),
Vec(0.0, 0.0, 0.0),
Vec(0.0, 0.0, 0.0),
Vec(0.2, 0.0, 0.0),
Vec(0.2, 0.0, 0.0),
Vec(0.0, 0.0, 0.0)
)
ξ = Vec(0.0, 0.0, 0.0)
dN_dξ = get_basis_derivatives(Hexahedron(), Lagrange{Hexahedron,1}(), ξ)
J = zero(Tensor{2,3,Float64,9})
for i in 1:8
J += X_nodes[i] dN_dξ[i]
end
F_finite = compute_deformation_gradient(X_nodes, u_nodes, dN_dξ, J, FiniteStrain())
F_small = compute_deformation_gradient(X_nodes, u_nodes, dN_dξ, J, SmallStrain())
# Finite strain includes gradient
@test F_finite[1, 1] 1.2 atol = 1e-10
# Small strain ignores gradient
@test F_small[1, 1] 1.0 atol = 1e-10
# They should be different!
@test !(F_finite F_small)
end
@testset "Physical constraint: det(F) > 0" begin
# Physical deformation must preserve orientation
X_nodes = (
Vec(0.0, 0.0, 0.0),
Vec(1.0, 0.0, 0.0),
Vec(1.0, 1.0, 0.0),
Vec(0.0, 1.0, 0.0),
Vec(0.0, 0.0, 1.0),
Vec(1.0, 0.0, 1.0),
Vec(1.0, 1.0, 1.0),
Vec(0.0, 1.0, 1.0)
)
# Small positive stretch
u_nodes = (
Vec(0.0, 0.0, 0.0),
Vec(0.05, 0.0, 0.0),
Vec(0.05, 0.0, 0.0),
Vec(0.0, 0.0, 0.0),
Vec(0.0, 0.0, 0.0),
Vec(0.05, 0.0, 0.0),
Vec(0.05, 0.0, 0.0),
Vec(0.0, 0.0, 0.0)
)
ξ = Vec(0.0, 0.0, 0.0)
dN_dξ = get_basis_derivatives(Hexahedron(), Lagrange{Hexahedron,1}(), ξ)
J = zero(Tensor{2,3,Float64,9})
for i in 1:8
J += X_nodes[i] dN_dξ[i]
end
F = compute_deformation_gradient(X_nodes, u_nodes, dN_dξ, J, FiniteStrain())
@test det(F) > 0 # Physical requirement
end
end
@testset "Deformation Gradient - Tet10 Element" begin
@testset "Tet10: Identity case" begin
# Regular tetrahedron nodes (4 corners + 6 edge midpoints)
X_nodes = (
Vec(0.0, 0.0, 0.0), # 1: corner
Vec(1.0, 0.0, 0.0), # 2: corner
Vec(0.0, 1.0, 0.0), # 3: corner
Vec(0.0, 0.0, 1.0), # 4: corner
Vec(0.5, 0.0, 0.0), # 5: edge 1-2
Vec(0.5, 0.5, 0.0), # 6: edge 2-3
Vec(0.0, 0.5, 0.0), # 7: edge 3-1
Vec(0.0, 0.0, 0.5), # 8: edge 1-4
Vec(0.5, 0.0, 0.5), # 9: edge 2-4
Vec(0.0, 0.5, 0.5) # 10: edge 3-4
)
# Zero displacement
u_nodes = tuple([zero(Vec{3,Float64}) for _ in 1:10]...)
# At element centroid ξ = (1/4, 1/4, 1/4)
ξ = Vec(0.25, 0.25, 0.25)
# Tet10 basis function derivatives (new API)
dN_dξ = get_basis_derivatives(Tetrahedron(), Lagrange{Tetrahedron,2}(), ξ)
# Compute Jacobian
J = zero(Tensor{2,3,Float64,9})
for i in 1:10
J += X_nodes[i] dN_dξ[i]
end
F = compute_deformation_gradient(X_nodes, u_nodes, dN_dξ, J, FiniteStrain())
@test F one(Tensor{2,3}) atol = 1e-10
@test det(F) 1.0 atol = 1e-10
end
@testset "Tet10: Uniform stretch" begin
# Regular tetrahedron
X_nodes = (
Vec(0.0, 0.0, 0.0),
Vec(1.0, 0.0, 0.0),
Vec(0.0, 1.0, 0.0),
Vec(0.0, 0.0, 1.0),
Vec(0.5, 0.0, 0.0),
Vec(0.5, 0.5, 0.0),
Vec(0.0, 0.5, 0.0),
Vec(0.0, 0.0, 0.5),
Vec(0.5, 0.0, 0.5),
Vec(0.0, 0.5, 0.5)
)
# Isotropic expansion: u = 0.1 * X
u_nodes = (
Vec(0.0, 0.0, 0.0),
Vec(0.1, 0.0, 0.0),
Vec(0.0, 0.1, 0.0),
Vec(0.0, 0.0, 0.1),
Vec(0.05, 0.0, 0.0),
Vec(0.05, 0.05, 0.0),
Vec(0.0, 0.05, 0.0),
Vec(0.0, 0.0, 0.05),
Vec(0.05, 0.0, 0.05),
Vec(0.0, 0.05, 0.05)
)
ξ = Vec(0.25, 0.25, 0.25)
dN_dξ = get_basis_derivatives(Tetrahedron(), Lagrange{Tetrahedron,2}(), ξ)
J = zero(Tensor{2,3,Float64,9})
for i in 1:10
J += X_nodes[i] dN_dξ[i]
end
F = compute_deformation_gradient(X_nodes, u_nodes, dN_dξ, J, FiniteStrain())
# Expected: F ≈ 1.1 * I
@test F[1, 1] 1.1 atol = 1e-10
@test F[2, 2] 1.1 atol = 1e-10
@test F[3, 3] 1.1 atol = 1e-10
@test abs(F[1, 2]) < 1e-10
@test abs(F[1, 3]) < 1e-10
@test abs(F[2, 3]) < 1e-10
@test det(F) 1.1^3 atol = 1e-10
end
end
@testset "Deformation Gradient - Zero Allocation" begin
@testset "Verify zero allocations" begin
# Setup
X_nodes = (
Vec(0.0, 0.0, 0.0),
Vec(1.0, 0.0, 0.0),
Vec(1.0, 1.0, 0.0),
Vec(0.0, 1.0, 0.0),
Vec(0.0, 0.0, 1.0),
Vec(1.0, 0.0, 1.0),
Vec(1.0, 1.0, 1.0),
Vec(0.0, 1.0, 1.0)
)
u_nodes = (
Vec(0.0, 0.0, 0.0),
Vec(0.1, 0.0, 0.0),
Vec(0.1, 0.0, 0.0),
Vec(0.0, 0.0, 0.0),
Vec(0.0, 0.0, 0.0),
Vec(0.1, 0.0, 0.0),
Vec(0.1, 0.0, 0.0),
Vec(0.0, 0.0, 0.0)
)
ξ = Vec(0.0, 0.0, 0.0)
dN_dξ = get_basis_derivatives(Hexahedron(), Lagrange{Hexahedron,1}(), ξ)
J = zero(Tensor{2,3,Float64,9})
for i in 1:8
J += X_nodes[i] dN_dξ[i]
end
# Warm up (compile)
F = compute_deformation_gradient(X_nodes, u_nodes, dN_dξ, J, FiniteStrain())
# Measure allocations
allocs = @allocated compute_deformation_gradient(X_nodes, u_nodes, dN_dξ, J, FiniteStrain())
@test allocs == 0 # Zero allocations!
end
end