chore(test): drop deformation gradient regression stub

Remove unused geometry tests superseded by continuum kinematics coverage.

- Delete `test/geometry/test_deformation_gradient.jl`.
This commit is contained in:
Jukka Aho
2026-05-09 18:26:57 +03:00
parent 07b5ffeedc
commit 99fc4e5b34
-561
View File
@@ -1,561 +0,0 @@
# 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