mirror of
https://github.com/JuliaFEM/JuliaFEM.jl.git
synced 2026-09-17 17:22:10 +00:00
256 lines
8.3 KiB
Julia
256 lines
8.3 KiB
Julia
|
|
"""
|
|||
|
|
# Integration Points API Tests (test/integration/)
|
|||
|
|
|
|||
|
|
## What
|
|||
|
|
Tests the zero-allocation integration points API using `get_gauss_points!(topology, scheme)`.
|
|||
|
|
This validates that quadrature point queries are completely compile-time resolved with
|
|||
|
|
zero runtime allocation.
|
|||
|
|
|
|||
|
|
## Why
|
|||
|
|
Integration point evaluation happens in the **innermost assembly loop** - potentially
|
|||
|
|
billions of times for large problems. Even a single allocation per call would be
|
|||
|
|
catastrophic for performance.
|
|||
|
|
|
|||
|
|
This test validates the CRITICAL performance requirement:
|
|||
|
|
- **Zero allocations**: `@allocated get_gauss_points!(...)` must return 0
|
|||
|
|
- **Compile-time resolution**: All return types are NTuple (stack-allocated)
|
|||
|
|
- **Type stability**: Returns `Tuple{Tuple{Float64, Vec{D}},...}`
|
|||
|
|
|
|||
|
|
The old API had performance issues:
|
|||
|
|
- Heap-allocated arrays for integration points (allocates every call)
|
|||
|
|
- Type-unstable returns (Any or AbstractArray)
|
|||
|
|
- Runtime dispatch instead of compile-time specialization
|
|||
|
|
|
|||
|
|
The new API fixes all of this using NTuple and generated functions.
|
|||
|
|
|
|||
|
|
## How
|
|||
|
|
**Zero Allocation Tests:**
|
|||
|
|
- Confirms `@allocated get_gauss_points!(...)` == 0 for all topologies
|
|||
|
|
- Tests Segment, Triangle, Tetrahedron, Quadrilateral, Hexahedron
|
|||
|
|
- Various Gauss orders: {1}, {2}, {3}
|
|||
|
|
|
|||
|
|
**Return Type Validation:**
|
|||
|
|
- Verifies return is Tuple of (weight, ξ) pairs
|
|||
|
|
- Each weight is Float64
|
|||
|
|
- Each ξ is Vec{D} from Tensors.jl (D = dimension)
|
|||
|
|
|
|||
|
|
**Integration Point Counts:**
|
|||
|
|
- Segment: 1, 2, 3 points (Gauss{1}, {2}, {3})
|
|||
|
|
- Triangle: 1, 3, 6 points
|
|||
|
|
- Tetrahedron: 1, 4, 5 points
|
|||
|
|
- Quadrilateral: 1, 4, 9 points (tensor product: n²)
|
|||
|
|
- Hexahedron: 1, 8, 27 points (tensor product: n³)
|
|||
|
|
|
|||
|
|
**Weight Sum Validation:**
|
|||
|
|
- Weights must sum to reference element volume/area:
|
|||
|
|
- Segment: 2.0 (length of [-1,1])
|
|||
|
|
- Triangle: 0.5 (area of reference triangle)
|
|||
|
|
- Tetrahedron: 1/6 (volume of reference tet)
|
|||
|
|
- Quadrilateral: 4.0 (area of [-1,1]²)
|
|||
|
|
- Hexahedron: 8.0 (volume of [-1,1]³)
|
|||
|
|
|
|||
|
|
**Assembly Loop Pattern:**
|
|||
|
|
- Demonstrates real usage: `for (w, ξ) in get_gauss_points!(...)`
|
|||
|
|
- Verifies zero allocation in actual assembly code
|
|||
|
|
- Shows integration with basis function evaluation
|
|||
|
|
|
|||
|
|
**Performance Benchmarking:**
|
|||
|
|
- Compares new approach vs old (hypothetical)
|
|||
|
|
- Target: ~1 μs for 1000 iterations, 0 allocations
|
|||
|
|
|
|||
|
|
## Expected Results
|
|||
|
|
- ✅ **Zero allocations**: All `@allocated` checks return 0
|
|||
|
|
- ✅ **Correct types**: Returns Tuple{Tuple{Float64, Vec{D}},...}
|
|||
|
|
- ✅ **Correct counts**: Point counts match Gauss order
|
|||
|
|
- ✅ **Correct weights**: Sum to reference element volume
|
|||
|
|
- ✅ **Assembly pattern**: Zero allocations in realistic usage
|
|||
|
|
- ✅ **Performance**: Sub-microsecond per 1000 iterations
|
|||
|
|
|
|||
|
|
## API Pattern (NEW vs DEPRECATED)
|
|||
|
|
```julia
|
|||
|
|
# ✅ NEW API (zero-allocation):
|
|||
|
|
for (weight, ξ) in get_gauss_points!(Triangle, Gauss{2})
|
|||
|
|
N = get_basis_functions(Triangle(), Lagrange{Triangle,1}(), ξ)
|
|||
|
|
dN = get_basis_derivatives(Triangle(), Lagrange{Triangle,1}(), ξ)
|
|||
|
|
# ... assembly using N, dN, weight
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
# ❌ DEPRECATED API (allocates every call):
|
|||
|
|
ips = get_integration_points(element) # Heap allocation!
|
|||
|
|
for ip in ips
|
|||
|
|
eval_basis!(bi, X, ip) # Mutation, type-unstable
|
|||
|
|
# ...
|
|||
|
|
end
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Architecture Principle
|
|||
|
|
**Compile-Time vs Runtime Resolution**
|
|||
|
|
|
|||
|
|
The new API pushes ALL integration point computation to compile time:
|
|||
|
|
- Topology type known at compile time → correct quadrature selected
|
|||
|
|
- Gauss order known at compile time → correct number of points
|
|||
|
|
- Return type fully inferred → NTuple allocated on stack
|
|||
|
|
|
|||
|
|
This is the FOUNDATION for zero-allocation assembly!
|
|||
|
|
|
|||
|
|
## Performance Target
|
|||
|
|
For 1000 assembly iterations over 3-point triangle quadrature:
|
|||
|
|
- **Time**: < 1 μs (compile-time overhead amortized)
|
|||
|
|
- **Allocations**: 0 bytes (all stack-allocated)
|
|||
|
|
- **GC**: 0% (no heap pressure)
|
|||
|
|
|
|||
|
|
If this test fails, the entire assembly chain will be slow!
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
# Test: Zero-Allocation Integration Points
|
|||
|
|
# =========================================
|
|||
|
|
|
|||
|
|
using Test
|
|||
|
|
using JuliaFEM
|
|||
|
|
using Tensors
|
|||
|
|
using BenchmarkTools
|
|||
|
|
|
|||
|
|
@testset "Integration Points API" begin
|
|||
|
|
@testset "Zero Allocation" begin
|
|||
|
|
# All integration point queries should allocate zero bytes
|
|||
|
|
@test (@allocated get_gauss_points!(Segment, Gauss{1})) == 0
|
|||
|
|
@test (@allocated get_gauss_points!(Triangle, Gauss{1})) == 0
|
|||
|
|
@test (@allocated get_gauss_points!(Tetrahedron, Gauss{1})) == 0
|
|||
|
|
@test (@allocated get_gauss_points!(Hexahedron, Gauss{2})) == 0
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
@testset "Return Type" begin
|
|||
|
|
# Should return NTuple of (Float64, Vec{D}) pairs
|
|||
|
|
ips = get_gauss_points!(Triangle, Gauss{1})
|
|||
|
|
|
|||
|
|
@test isa(ips, Tuple)
|
|||
|
|
@test length(ips) == 1
|
|||
|
|
|
|||
|
|
w, ξ = ips[1]
|
|||
|
|
@test isa(w, Float64)
|
|||
|
|
@test isa(ξ, Vec{2})
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
@testset "Segment" begin
|
|||
|
|
# 1-point Gauss
|
|||
|
|
ips = get_gauss_points!(Segment, Gauss{1})
|
|||
|
|
@test length(ips) == 1
|
|||
|
|
w, ξ = ips[1]
|
|||
|
|
@test w ≈ 2.0
|
|||
|
|
@test ξ[1] ≈ 0.0
|
|||
|
|
|
|||
|
|
# 2-point Gauss
|
|||
|
|
ips = get_gauss_points!(Segment, Gauss{2})
|
|||
|
|
@test length(ips) == 2
|
|||
|
|
@test sum(ip[1] for ip in ips) ≈ 2.0 # Weights sum to length
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
@testset "Triangle" begin
|
|||
|
|
# 1-point Gauss (centroid)
|
|||
|
|
ips = get_gauss_points!(Triangle, Gauss{1})
|
|||
|
|
@test length(ips) == 1
|
|||
|
|
w, ξ = ips[1]
|
|||
|
|
@test w ≈ 0.5 # Area of reference triangle
|
|||
|
|
@test ξ[1] ≈ 1 / 3
|
|||
|
|
@test ξ[2] ≈ 1 / 3
|
|||
|
|
|
|||
|
|
# 3-point Gauss
|
|||
|
|
ips = get_gauss_points!(Triangle, Gauss{2})
|
|||
|
|
@test length(ips) == 3
|
|||
|
|
@test sum(ip[1] for ip in ips) ≈ 0.5
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
@testset "Tetrahedron" begin
|
|||
|
|
# 1-point Gauss (centroid)
|
|||
|
|
ips = get_gauss_points!(Tetrahedron, Gauss{1})
|
|||
|
|
@test length(ips) == 1
|
|||
|
|
w, ξ = ips[1]
|
|||
|
|
@test w ≈ 1 / 6 # Volume of reference tetrahedron
|
|||
|
|
@test ξ[1] ≈ 0.25
|
|||
|
|
@test ξ[2] ≈ 0.25
|
|||
|
|
@test ξ[3] ≈ 0.25
|
|||
|
|
|
|||
|
|
# 4-point Gauss
|
|||
|
|
ips = get_gauss_points!(Tetrahedron, Gauss{2})
|
|||
|
|
@test length(ips) == 4
|
|||
|
|
@test sum(ip[1] for ip in ips) ≈ 1 / 6
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
@testset "Quadrilateral" begin
|
|||
|
|
# 2×2 Gauss (standard for Q1)
|
|||
|
|
ips = get_gauss_points!(Quadrilateral, Gauss{2})
|
|||
|
|
@test length(ips) == 4
|
|||
|
|
@test sum(ip[1] for ip in ips) ≈ 4.0 # Area of reference quad
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
@testset "Hexahedron" begin
|
|||
|
|
# 2×2×2 Gauss (standard for Hex8)
|
|||
|
|
ips = get_gauss_points!(Hexahedron, Gauss{2})
|
|||
|
|
@test length(ips) == 8
|
|||
|
|
@test sum(ip[1] for ip in ips) ≈ 8.0 # Volume of reference hex
|
|||
|
|
end
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
@testset "Usage in Assembly Loop" begin
|
|||
|
|
# Demonstrate zero-allocation assembly pattern
|
|||
|
|
function assemble_element_stiffness()
|
|||
|
|
K = 0.0
|
|||
|
|
for (w, ξ) in get_gauss_points!(Triangle, Gauss{2})
|
|||
|
|
# Shape functions
|
|||
|
|
N1 = 1 - ξ[1] - ξ[2]
|
|||
|
|
N2 = ξ[1]
|
|||
|
|
N3 = ξ[2]
|
|||
|
|
|
|||
|
|
# Accumulate (simplified stiffness)
|
|||
|
|
K += w * (N1^2 + N2^2 + N3^2)
|
|||
|
|
end
|
|||
|
|
return K
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
# Should allocate zero
|
|||
|
|
@test (@allocated assemble_element_stiffness()) == 0
|
|||
|
|
|
|||
|
|
# Verify result is consistent
|
|||
|
|
K1 = assemble_element_stiffness()
|
|||
|
|
K2 = assemble_element_stiffness()
|
|||
|
|
@test K1 ≈ K2
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
@testset "Performance Comparison" begin
|
|||
|
|
println("\n" * "="^70)
|
|||
|
|
println("PERFORMANCE: Integration Points vs Old Approach")
|
|||
|
|
println("="^70)
|
|||
|
|
|
|||
|
|
# New approach (compile-time, Vec{D})
|
|||
|
|
new_approach() = begin
|
|||
|
|
sum_val = 0.0
|
|||
|
|
for _ in 1:1000
|
|||
|
|
for (w, ξ) in get_gauss_points!(Triangle, Gauss{2})
|
|||
|
|
sum_val += w * sum(ξ)
|
|||
|
|
end
|
|||
|
|
end
|
|||
|
|
return sum_val
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
println("\nNew approach (compile-time + Vec{D}):")
|
|||
|
|
display(@benchmark $new_approach())
|
|||
|
|
|
|||
|
|
println("\n\nExpected: ~1 μs, 0 allocations")
|
|||
|
|
println("="^70)
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
println("\n✓ All integration point tests passed!")
|
|||
|
|
println("\nUsage Example (NEW API):")
|
|||
|
|
println("```julia")
|
|||
|
|
println("# Zero-allocation loop over integration points:")
|
|||
|
|
println("for (weight, ξ) in get_gauss_points!(Triangle, Gauss{2})")
|
|||
|
|
println(" # NEW API (recommended):")
|
|||
|
|
println(" N = get_basis_functions(Triangle(), Lagrange{1}(), ξ)")
|
|||
|
|
println(" dN = get_basis_derivatives(Triangle(), Lagrange{1}(), ξ)")
|
|||
|
|
println(" # ... compute element matrices")
|
|||
|
|
println("end")
|
|||
|
|
println("```")
|
|||
|
|
println()
|
|||
|
|
println("Note: eval_basis! and eval_dbasis! are DEPRECATED.")
|
|||
|
|
println("Use get_basis_functions and get_basis_derivatives instead.")
|