Files
JuliaFEM.jl/test/integration/test_integration_api.jl
T
Jukka Aho 4fdbedae1b test(integration): add integration points API test
New 255-line test file for zero-allocation integration points API:
- Tests get_gauss_points! function for all topologies
- Validates zero-allocation performance requirement
- Tests return types (NTuple of (weight, ξ) pairs)
- Tests integration point counts for different Gauss orders
- Validates weight sums equal reference element volumes
- Tests usage in assembly loop pattern
- Includes performance benchmarking

Comprehensive test ensuring integration points API provides
zero-allocation compile-time resolved quadrature points.
2025-12-15 08:18:51 +02:00

256 lines
8.3 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.
"""
# 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.")