Files
JuliaFEM.jl/test/topology/test_integration.jl
T
Jukka Aho 14ccc79b40 test(topology): add comprehensive topology and integration test
New 474-line comprehensive test file for topology and integration:
- Tests all 17 topology types using full JuliaFEM module
- Tests reference coordinates, edge/face connectivity
- Tests Gauss quadrature integration for all topologies
- Validates integration weights sum to reference element volumes
- Tests higher-order elements use same quadrature rules
- Validates zero-allocation design (NTuple returns)
- Tests API completeness (all types exported)

Complements test_topology.jl (standalone) by testing integrated system.
This is the ACID TEST - validates real usage pattern with 'using JuliaFEM'.
2025-12-15 12:09:07 +02:00

475 lines
16 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
"""
# Comprehensive Topology and Integration Test Suite (test/topology/)
## What
Complete test coverage for all 17 topology types and Gauss quadrature integration
using the FULL JuliaFEM module. This complements test_topology.jl (standalone) by
testing the integrated system.
## Why
While test_topology.jl validates modules in isolation (avoiding name conflicts),
THIS test validates that topology and integration work correctly when loaded through
`using JuliaFEM`. This is the REAL usage pattern and must pass before we can claim
the integration is complete.
Tests validate:
- **All 17 topologies**: Seg2/3, Tri3/6/7, Quad4/8/9, Tet4/10, Hex8/20/27, Pyr5, Wedge6/15
- **Reference coordinates**: Correct parametric domain for each topology
- **Connectivity**: edges() and faces() return correct NTuple structures
- **Integration rules**: Gauss{1}, Gauss{2}, Gauss{3} for all applicable topologies
- **Weight sums**: Quadrature weights sum to reference element volume/area
- **Type stability**: All returns are NTuple (zero-allocation)
## How
**Topology Tests** (1D → 2D → 3D progression):
**1D Segments:**
- Seg2: 2 nodes, linear, domain [-1,1]
- Seg3: 3 nodes, quadratic, with midpoint node
**2D Triangles:**
- Tri3: 3 nodes, linear, natural coordinates [0,1]
- Tri6: 6 nodes, quadratic, 3 edge midpoints
- Tri7: 7 nodes, cubic, with center node
**2D Quadrilaterals:**
- Quad4: 4 nodes, bilinear, domain [-1,1]²
- Quad8: 8 nodes, serendipity, edge midpoints only
- Quad9: 9 nodes, biquadratic, with center node
**3D Tetrahedra:**
- Tet4: 4 nodes, linear
- Tet10: 10 nodes, quadratic, 6 edge midpoints
**3D Hexahedra:**
- Hex8: 8 nodes, trilinear, domain [-1,1]³
- Hex20: 20 nodes, serendipity, edge midpoints only
- Hex27: 27 nodes, triquadratic, with face and volume nodes
**3D Other:**
- Pyr5: 5 nodes, pyramid with quad base
- Wedge6/15: 6 or 15 nodes, triangular prism
**Integration Tests:**
- IntegrationPoint{N} structure validation
- Gauss quadrature rules for each topology:
- Seg2: 1, 2, 3-point rules
- Tri3: 1, 3, 6-point rules (weights sum to 0.5)
- Quad4: 1, 4, 9-point tensor product rules (weights sum to 4.0)
- Tet4: 1, 4, 5-point rules
- Hex8: 1, 8, 27-point tensor product rules (weights sum to 8.0)
- Wedge6: Combined triangle × line quadrature
- Pyr5: Specialized pyramid quadrature
## Expected Results
- ✅ All 17 topologies instantiate with correct node counts
- ✅ Reference coordinates lie in correct parametric domains
- ✅ Edge/face connectivity returns proper NTuple structures
- ✅ Integration points returned as Tuple{IntegrationPoint{N},...}
- ✅ Quadrature weights sum correctly:
- Seg2: 2.0 (length of [-1,1])
- Tri3: 0.5 (area of reference triangle)
- Quad4: 4.0 (area of [-1,1]²)
- Tet4: 1/6 (volume of reference tetrahedron)
- Hex8: 8.0 (volume of [-1,1]³)
- ✅ Higher-order Gauss rules provide more integration points
- ✅ All types are concrete (type-stable, allocation-free)
## Test Strategy
**Full integration test** using `using JuliaFEM`. This is the ACID TEST - if this
passes, the topology/integration modules are correctly integrated into JuliaFEM.
Contrast with test_topology.jl:
- **test_topology.jl**: Standalone, avoids conflicts, validates module isolation
- **test_integration.jl** (this file): Full integration, validates real usage
Both must pass for complete validation!
## Coverage
- 17 topology types × (nnodes, dim, reference_coordinates, edges, faces) = 85 topology checks
- 8 element types × 3 Gauss orders ≈ 24 integration checks
- Weight sum validation for each integration rule
- Type stability checks for all returns
This is the MOST COMPREHENSIVE test of the new topology/integration infrastructure!
"""
using Test
using JuliaFEM
@testset "Topology and Integration: Complete test suite" begin
# ========================================================================
# TOPOLOGY: 1D SEGMENTS
# ========================================================================
@testset "Seg2 topology" begin
topo = Seg2()
@test nnodes(topo) == 2
@test dim(topo) == 1
coords = reference_coordinates(topo)
@test coords isa NTuple{2,NTuple{1,Float64}}
@test coords[1] == (-1.0,)
@test coords[2] == (1.0,)
e = edges(topo)
@test e isa NTuple{1,Tuple{Int,Int}}
@test e[1] == (1, 2)
@test faces(topo) == ()
end
@testset "Seg3 topology" begin
topo = Seg3()
@test nnodes(topo) == 3
@test dim(topo) == 1
coords = reference_coordinates(topo)
@test coords[1] == (-1.0,)
@test coords[2] == (1.0,)
@test coords[3] == (0.0,)
end
# ========================================================================
# TOPOLOGY: 2D TRIANGLES
# ========================================================================
@testset "Tri3 topology" begin
topo = Tri3()
@test nnodes(topo) == 3
@test dim(topo) == 2
coords = reference_coordinates(topo)
@test coords isa NTuple{3,NTuple{2,Float64}}
@test coords[1] == (0.0, 0.0)
@test coords[2] == (1.0, 0.0)
@test coords[3] == (0.0, 1.0)
e = edges(topo)
@test e isa NTuple{3,Tuple{Int,Int}}
@test length(e) == 3
f = faces(topo)
@test f isa NTuple{1,NTuple{3,Int}}
@test f[1] == (1, 2, 3)
end
@testset "Tri6 topology" begin
topo = Tri6()
@test nnodes(topo) == 6
@test dim(topo) == 2
coords = reference_coordinates(topo)
@test coords[4] == (0.5, 0.0) # Edge node
@test coords[5] == (0.5, 0.5) # Edge node
@test coords[6] == (0.0, 0.5) # Edge node
end
@testset "Tri7 topology" begin
topo = Tri7()
@test nnodes(topo) == 7
@test coords = reference_coordinates(topo)
@test coords[7] (1 / 3, 1 / 3) # Center node
end
# ========================================================================
# TOPOLOGY: 2D QUADRILATERALS
# ========================================================================
@testset "Quad4 topology" begin
topo = Quad4()
@test nnodes(topo) == 4
@test dim(topo) == 2
coords = reference_coordinates(topo)
@test coords isa NTuple{4,NTuple{2,Float64}}
@test coords[1] == (-1.0, -1.0)
@test coords[2] == (1.0, -1.0)
@test coords[3] == (1.0, 1.0)
@test coords[4] == (-1.0, 1.0)
e = edges(topo)
@test length(e) == 4
f = faces(topo)
@test f[1] == (1, 2, 3, 4)
end
@testset "Quad8 topology" begin
topo = Quad8()
@test nnodes(topo) == 8
@test dim(topo) == 2
coords = reference_coordinates(topo)
@test coords[5] == (0.0, -1.0) # Edge node
@test coords[8] == (-1.0, 0.0) # Edge node
end
@testset "Quad9 topology" begin
topo = Quad9()
@test nnodes(topo) == 9
coords = reference_coordinates(topo)
@test coords[9] == (0.0, 0.0) # Center node
end
# ========================================================================
# TOPOLOGY: 3D TETRAHEDRA
# ========================================================================
@testset "Tet4 topology" begin
topo = Tet4()
@test nnodes(topo) == 4
@test dim(topo) == 3
coords = reference_coordinates(topo)
@test coords isa NTuple{4,NTuple{3,Float64}}
@test coords[1] == (0.0, 0.0, 0.0)
@test coords[2] == (1.0, 0.0, 0.0)
@test coords[3] == (0.0, 1.0, 0.0)
@test coords[4] == (0.0, 0.0, 1.0)
e = edges(topo)
@test length(e) == 6 # Tet has 6 edges
f = faces(topo)
@test length(f) == 4 # Tet has 4 triangular faces
end
@testset "Tet10 topology" begin
topo = Tet10()
@test nnodes(topo) == 10
@test dim(topo) == 3
coords = reference_coordinates(topo)
@test coords[5] == (0.5, 0.0, 0.0) # Edge node
end
# ========================================================================
# TOPOLOGY: 3D HEXAHEDRA
# ========================================================================
@testset "Hex8 topology" begin
topo = Hex8()
@test nnodes(topo) == 8
@test dim(topo) == 3
coords = reference_coordinates(topo)
@test coords isa NTuple{8,NTuple{3,Float64}}
@test coords[1] == (-1.0, -1.0, -1.0)
@test coords[7] == (1.0, 1.0, 1.0)
e = edges(topo)
@test length(e) == 12 # Hex has 12 edges
f = faces(topo)
@test length(f) == 6 # Hex has 6 quadrilateral faces
end
@testset "Hex20 topology" begin
topo = Hex20()
@test nnodes(topo) == 20
@test dim(topo) == 3
coords = reference_coordinates(topo)
@test coords[9] == (0.0, -1.0, -1.0) # Edge node
end
@testset "Hex27 topology" begin
topo = Hex27()
@test nnodes(topo) == 27
coords = reference_coordinates(topo)
@test coords[27] == (0.0, 0.0, 0.0) # Volume center node
end
# ========================================================================
# TOPOLOGY: 3D PYRAMIDS
# ========================================================================
@testset "Pyr5 topology" begin
topo = Pyr5()
@test nnodes(topo) == 5
@test dim(topo) == 3
coords = reference_coordinates(topo)
@test coords[5] == (0.0, 0.0, 1.0) # Apex
e = edges(topo)
@test length(e) == 8 # 4 base + 4 to apex
f = faces(topo)
@test length(f) == 5 # 1 quad base + 4 triangular
end
# ========================================================================
# TOPOLOGY: 3D WEDGES
# ========================================================================
@testset "Wedge6 topology" begin
topo = Wedge6()
@test nnodes(topo) == 6
@test dim(topo) == 3
coords = reference_coordinates(topo)
@test coords[1] == (0.0, 0.0, -1.0) # Bottom triangle
@test coords[4] == (0.0, 0.0, 1.0) # Top triangle
e = edges(topo)
@test length(e) == 9 # 3 bottom + 3 top + 3 vertical
f = faces(topo)
@test length(f) == 5 # 2 triangular + 3 quadrilateral
end
@testset "Wedge15 topology" begin
topo = Wedge15()
@test nnodes(topo) == 15
@test dim(topo) == 3
end
# ========================================================================
# INTEGRATION: GAUSS QUADRATURE
# ========================================================================
@testset "Integration points structure" begin
ip = IntegrationPoint{2}((0.5, 0.5), 1.0)
@test ip.ξ == (0.5, 0.5)
@test ip.weight == 1.0
@test ip.ξ isa NTuple{2,Float64}
end
@testset "Gauss quadrature for Seg2" begin
ips = integration_points(Gauss{2}(), Seg2())
@test ips isa Tuple
@test length(ips) == 2 # 2-point Gauss rule
@test all(ip -> ip isa IntegrationPoint{1}, ips)
# Check weights sum correctly
total_weight = sum(ip.weight for ip in ips)
@test total_weight 2.0 # Domain [-1,1] has length 2
end
@testset "Gauss quadrature for Tri3" begin
ips1 = integration_points(Gauss{1}(), Tri3())
@test length(ips1) == 1 # 1-point rule
@test ips1[1].ξ (1 / 3, 1 / 3) # Centroid
@test ips1[1].weight 0.5 # Triangle area
ips3 = integration_points(Gauss{3}(), Tri3())
@test length(ips3) == 3 # 3-point rule
# Check weights sum to triangle area
total_weight = sum(ip.weight for ip in ips3)
@test total_weight 0.5
end
@testset "Gauss quadrature for Quad4" begin
ips1 = integration_points(Gauss{1}(), Quad4())
@test length(ips1) == 1 # 1-point rule
ips2 = integration_points(Gauss{2}(), Quad4())
@test length(ips2) == 4 # 2² = 4 points
ips3 = integration_points(Gauss{3}(), Quad4())
@test length(ips3) == 9 # 3² = 9 points
# Check weights sum to square area
total_weight = sum(ip.weight for ip in ips2)
@test total_weight 4.0 # Domain [-1,1]² has area 4
end
@testset "Gauss quadrature for Tet4" begin
ips = integration_points(Gauss{1}(), Tet4())
@test length(ips) == 1
@test all(ip -> ip isa IntegrationPoint{3}, ips)
end
@testset "Gauss quadrature for Hex8" begin
ips1 = integration_points(Gauss{1}(), Hex8())
@test length(ips1) == 1 # 1-point rule
ips2 = integration_points(Gauss{2}(), Hex8())
@test length(ips2) == 8 # 2³ = 8 points
ips3 = integration_points(Gauss{3}(), Hex8())
@test length(ips3) == 27 # 3³ = 27 points
# Check weights sum to cube volume
total_weight = sum(ip.weight for ip in ips2)
@test total_weight 8.0 # Domain [-1,1]³ has volume 8
end
@testset "Gauss quadrature for Wedge6" begin
ips = integration_points(Gauss{6}(), Wedge6())
@test length(ips) == 6
@test all(ip -> ip isa IntegrationPoint{3}, ips)
end
@testset "Gauss quadrature for Pyr5" begin
ips = integration_points(Gauss{5}(), Pyr5())
@test length(ips) == 5
@test all(ip -> ip isa IntegrationPoint{3}, ips)
end
# ========================================================================
# INTEGRATION: HIGHER ORDER ELEMENTS
# ========================================================================
@testset "Quadratic elements use same quadrature" begin
# Tri3 and Tri6 can use same rules
ips_tri3 = integration_points(Gauss{3}(), Tri3())
ips_tri6 = integration_points(Gauss{3}(), Tri6())
@test length(ips_tri3) == length(ips_tri6)
# Quad4 and Quad9 can use same rules
ips_quad4 = integration_points(Gauss{2}(), Quad4())
ips_quad9 = integration_points(Gauss{2}(), Quad9())
@test length(ips_quad4) == length(ips_quad9)
# Hex8 and Hex27 can use same rules
ips_hex8 = integration_points(Gauss{2}(), Hex8())
ips_hex27 = integration_points(Gauss{2}(), Hex27())
@test length(ips_hex8) == length(ips_hex27)
end
# ========================================================================
# ZERO-ALLOCATION VERIFICATION
# ========================================================================
@testset "Zero-allocation design" begin
# Topology functions return tuples
@test reference_coordinates(Tri3()) isa NTuple
@test edges(Quad4()) isa NTuple
@test faces(Hex8()) isa NTuple
# Integration points return tuple
@test integration_points(Gauss{1}(), Tri3()) isa Tuple
# IntegrationPoint.ξ is tuple
ip = first(integration_points(Gauss{1}(), Tri3()))
@test ip.ξ isa NTuple
end
# ========================================================================
# API COMPLETENESS
# ========================================================================
@testset "All topology types exported" begin
@test isdefined(JuliaFEM, :Seg2)
@test isdefined(JuliaFEM, :Seg3)
@test isdefined(JuliaFEM, :Tri3)
@test isdefined(JuliaFEM, :Tri6)
@test isdefined(JuliaFEM, :Tri7)
@test isdefined(JuliaFEM, :Quad4)
@test isdefined(JuliaFEM, :Quad8)
@test isdefined(JuliaFEM, :Quad9)
@test isdefined(JuliaFEM, :Tet4)
@test isdefined(JuliaFEM, :Tet10)
@test isdefined(JuliaFEM, :Hex8)
@test isdefined(JuliaFEM, :Hex20)
@test isdefined(JuliaFEM, :Hex27)
@test isdefined(JuliaFEM, :Pyr5)
@test isdefined(JuliaFEM, :Wedge6)
@test isdefined(JuliaFEM, :Wedge15)
end
@testset "Integration types exported" begin
@test isdefined(JuliaFEM, :Gauss)
@test isdefined(JuliaFEM, :IntegrationPoint)
@test isdefined(JuliaFEM, :integration_points)
end
end