Files
JuliaFEM.jl/test/test_deformation_gradient.jl
T
Jukka Aho 1f07f3f7aa test: Add comprehensive deformation gradient computation validation
- Tests compute_deformation_gradient() for both FiniteStrain and SmallStrain formulations
- Identity case: u=0 → F=I, det(F)=1
- Pure translation: constant u → ∇u=0 → F=I (rigid body motion)
- Pure stretch: uniaxial extension (10%, 20%) → diagonal F
- Simple shear: u_x = γ·y → off-diagonal F components
- Validates F = I + ∇u (finite strain) vs F = I (small strain approximation)
- Physical constraint: det(F) > 0 (orientation preservation)
- Incompressibility check: det(F) ≈ 1 for volume-preserving deformation
- Symmetry verification for Right Cauchy-Green tensor C = F^T·F
- Type stability and zero allocation checks
- Integration with new API: get_basis_derivatives(Hexahedron(), Lagrange{}, ξ)
- Tests Hex8 elements with various deformation patterns
- 393 lines validating fundamental kinematics with Tensors.jl
2025-11-12 00:07:40 +02:00

394 lines
12 KiB
Julia

# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
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