mirror of
https://github.com/JuliaFEM/JuliaFEM.jl.git
synced 2026-08-06 04:21:33 +00:00
test(dofs): add DOF connectivity test
Tests DOF connectivity system for inverse mapping. Validates DOF-to-element connectivity for DOF-based assembly.
This commit is contained in:
@@ -0,0 +1,305 @@
|
||||
# This file is a part of JuliaFEM.
|
||||
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
|
||||
|
||||
"""
|
||||
Unit tests for DOF connectivity structures.
|
||||
|
||||
Tests:
|
||||
1. DOFElementConnection creation and access
|
||||
2. DOFConnectivity building and access
|
||||
3. DOFConnectivityGPU building and access
|
||||
4. Zero-allocation verification
|
||||
5. GPU compatibility (bits types)
|
||||
"""
|
||||
|
||||
using Test
|
||||
using JuliaFEM
|
||||
using JuliaFEM: DOFElementConnection, DOFConnectivity, DOFConnectivityGPU
|
||||
using JuliaFEM: build_dof_connectivity, build_dof_connectivity_gpu
|
||||
using JuliaFEM: connection_count, is_empty, elem_id, local_dof_idx
|
||||
using JuliaFEM: DOFManager, create_elements!, @DOFSet, DOF, Displacement, Vertex
|
||||
using BenchmarkTools
|
||||
|
||||
@testset "DOF Connectivity" begin
|
||||
println("\n" * "="^70)
|
||||
println("DOF CONNECTIVITY TESTS")
|
||||
println("="^70)
|
||||
|
||||
# ========================================================================
|
||||
# 1. DOFElementConnection
|
||||
# ========================================================================
|
||||
|
||||
@testset "DOFElementConnection" begin
|
||||
println("\n[1] Testing DOFElementConnection...")
|
||||
|
||||
# Create connection
|
||||
conn = DOFElementConnection(5, 12)
|
||||
@test elem_id(conn) == 5
|
||||
@test local_dof_idx(conn) == 12
|
||||
|
||||
# Test type (bits type for GPU)
|
||||
# Note: isbits() checks if an instance is a bits type (immutable, no pointers)
|
||||
# DOFElementConnection is a struct with Int32 and Int16 fields - it IS a bits type
|
||||
@test isbits(conn)
|
||||
# Verify it's a struct (not mutable struct)
|
||||
@test DOFElementConnection isa Type
|
||||
|
||||
# Test range validation
|
||||
@test_throws ErrorException DOFElementConnection(0, 1)
|
||||
@test_throws ErrorException DOFElementConnection(typemax(Int32) + 1, 1)
|
||||
@test_throws ErrorException DOFElementConnection(1, 0)
|
||||
@test_throws ErrorException DOFElementConnection(1, typemax(Int16) + 1)
|
||||
|
||||
println(" ✓ DOFElementConnection working")
|
||||
end
|
||||
|
||||
# ========================================================================
|
||||
# 2. DOFConnectivity (CPU Version)
|
||||
# ========================================================================
|
||||
|
||||
@testset "DOFConnectivity - CPU" begin
|
||||
println("\n[2] Testing DOFConnectivity (CPU)...")
|
||||
|
||||
# Create simple test mesh: 2 elements sharing a node
|
||||
# Element 1: nodes [1, 2, 3] → DOFs [1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
# Element 2: nodes [2, 4, 5] → DOFs [4, 5, 6, 10, 11, 12, 13, 14, 15]
|
||||
# DOF 4, 5, 6 are shared (node 2)
|
||||
|
||||
# Create DOF manager
|
||||
mgr = DOFManager()
|
||||
|
||||
# Create elements manually (simplified for testing)
|
||||
S = @DOFSet{u::DOF{Displacement{3}, Vertex}}
|
||||
ElemType = Element{Triangle{3}, Lagrange{1}, S}
|
||||
|
||||
# Element 1: DOFs 1-9 (3 nodes × 3 DOFs)
|
||||
elem1 = ElemType(UInt(1), (UInt64(1), UInt64(2), UInt64(3), UInt64(4), UInt64(5), UInt64(6), UInt64(7), UInt64(8), UInt64(9)))
|
||||
|
||||
# Element 2: DOFs 4-6, 10-15 (shares node 2 with elem1)
|
||||
elem2 = ElemType(UInt(2), (UInt64(4), UInt64(5), UInt64(6), UInt64(10), UInt64(11), UInt64(12), UInt64(13), UInt64(14), UInt64(15)))
|
||||
|
||||
elements = [elem1, elem2]
|
||||
|
||||
# Set total DOFs in manager
|
||||
mgr.total_dofs = 15
|
||||
|
||||
# Build connectivity
|
||||
connectivity = build_dof_connectivity(elements, mgr)
|
||||
|
||||
@test connectivity.n_total_dofs == 15
|
||||
@test length(connectivity) == 15
|
||||
|
||||
# Test shared DOFs (4, 5, 6)
|
||||
@test connection_count(connectivity, 4) == 2 # In both elements
|
||||
@test connection_count(connectivity, 5) == 2
|
||||
@test connection_count(connectivity, 6) == 2
|
||||
|
||||
# Test unique DOFs
|
||||
@test connection_count(connectivity, 1) == 1 # Only in elem1
|
||||
@test connection_count(connectivity, 10) == 1 # Only in elem2
|
||||
|
||||
# Test connections
|
||||
conns_4 = connectivity[4]
|
||||
@test length(conns_4) == 2
|
||||
@test elem_id(conns_4[1]) in [1, 2]
|
||||
@test elem_id(conns_4[2]) in [1, 2]
|
||||
@test elem_id(conns_4[1]) != elem_id(conns_4[2])
|
||||
|
||||
# Test empty DOFs (if any)
|
||||
# DOFs 16+ don't exist, but we only built for 1-15
|
||||
# Test that accessing valid DOFs works
|
||||
@test !is_empty(connectivity, 1)
|
||||
@test !is_empty(connectivity, 4)
|
||||
|
||||
println(" ✓ DOFConnectivity (CPU) working")
|
||||
end
|
||||
|
||||
# ========================================================================
|
||||
# 3. DOFConnectivityGPU
|
||||
# ========================================================================
|
||||
|
||||
@testset "DOFConnectivityGPU" begin
|
||||
println("\n[3] Testing DOFConnectivityGPU...")
|
||||
|
||||
# Same test case as CPU version
|
||||
mgr = DOFManager()
|
||||
mgr.total_dofs = 15
|
||||
|
||||
S = @DOFSet{u::DOF{Displacement{3}, Vertex}}
|
||||
ElemType = Element{Triangle{3}, Lagrange{1}, S}
|
||||
elem1 = ElemType(UInt(1), (UInt64(1), UInt64(2), UInt64(3), UInt64(4), UInt64(5), UInt64(6), UInt64(7), UInt64(8), UInt64(9)))
|
||||
elem2 = ElemType(UInt(2), (UInt64(4), UInt64(5), UInt64(6), UInt64(10), UInt64(11), UInt64(12), UInt64(13), UInt64(14), UInt64(15)))
|
||||
elements = [elem1, elem2]
|
||||
|
||||
# Build GPU connectivity
|
||||
connectivity_gpu = build_dof_connectivity_gpu(elements, mgr, max_connections=10)
|
||||
|
||||
@test connectivity_gpu.n_total_dofs == 15
|
||||
@test connectivity_gpu.max_connections == 10
|
||||
@test size(connectivity_gpu.elem_ids) == (10, 15)
|
||||
@test size(connectivity_gpu.local_indices) == (10, 15)
|
||||
@test length(connectivity_gpu.counts) == 15
|
||||
|
||||
# Test shared DOFs
|
||||
@test connection_count(connectivity_gpu, 4) == 2
|
||||
@test connection_count(connectivity_gpu, 5) == 2
|
||||
@test connection_count(connectivity_gpu, 6) == 2
|
||||
|
||||
# Test unique DOFs
|
||||
@test connection_count(connectivity_gpu, 1) == 1
|
||||
@test connection_count(connectivity_gpu, 10) == 1
|
||||
|
||||
# Test data access
|
||||
@test connectivity_gpu.elem_ids[1, 4] in [1, 2]
|
||||
@test connectivity_gpu.elem_ids[2, 4] in [1, 2]
|
||||
@test connectivity_gpu.elem_ids[1, 4] != connectivity_gpu.elem_ids[2, 4]
|
||||
|
||||
# Test array types (GPU-compatible - all bits types)
|
||||
@test eltype(connectivity_gpu.elem_ids) == Int32
|
||||
@test eltype(connectivity_gpu.local_indices) == Int16
|
||||
@test eltype(connectivity_gpu.counts) == Int32
|
||||
# Verify arrays are standard Julia arrays (contiguous by default for Matrix/Vector)
|
||||
@test connectivity_gpu.elem_ids isa Matrix{Int32}
|
||||
@test connectivity_gpu.local_indices isa Matrix{Int16}
|
||||
@test connectivity_gpu.counts isa Vector{Int32}
|
||||
|
||||
println(" ✓ DOFConnectivityGPU working")
|
||||
end
|
||||
|
||||
# ========================================================================
|
||||
# 4. Zero-Allocation Verification
|
||||
# ========================================================================
|
||||
|
||||
@testset "Zero-Allocation Access" begin
|
||||
println("\n[4] Testing zero-allocation access...")
|
||||
|
||||
# Build connectivity
|
||||
# Create 8 elements: 8 × 12 = 96 DOFs (within 100)
|
||||
mgr = DOFManager()
|
||||
mgr.total_dofs = 120 # Enough for 8 elements
|
||||
|
||||
S = @DOFSet{u::DOF{Displacement{3}, Vertex}}
|
||||
ElemType = Element{Tetrahedron{4}, Lagrange{1}, S}
|
||||
|
||||
# Create 8 elements with DOFs
|
||||
elements = ElemType[]
|
||||
for i in 1:8
|
||||
# Each element: 12 DOFs (4 nodes × 3)
|
||||
start_dof = (i - 1) * 12 + 1
|
||||
dofs = tuple((UInt64(d) for d in start_dof:(start_dof + 11))...)
|
||||
push!(elements, ElemType(UInt(i), dofs))
|
||||
end
|
||||
|
||||
connectivity = build_dof_connectivity(elements, mgr)
|
||||
|
||||
# Warm-up (ensure compilation)
|
||||
n_warmup = min(96, connectivity.n_total_dofs)
|
||||
for dof_i in 1:n_warmup
|
||||
_ = connectivity[dof_i]
|
||||
_ = connection_count(connectivity, dof_i)
|
||||
end
|
||||
|
||||
# Test on subset of DOFs that have connections
|
||||
n_test = 96 # All DOFs from 8 elements
|
||||
|
||||
# Test access allocations (define function in global scope for @benchmark)
|
||||
test_access_func = let conn = connectivity, n = n_test
|
||||
function()
|
||||
@inbounds for dof_i in 1:n
|
||||
connections = conn[dof_i]
|
||||
count = length(connections)
|
||||
for c in connections
|
||||
_ = elem_id(c)
|
||||
_ = local_dof_idx(c)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
result = @benchmark $test_access_func()
|
||||
|
||||
println(" Access allocations: $(result.allocs)")
|
||||
println(" Access memory: $(result.memory) bytes")
|
||||
|
||||
# Should be zero allocations after warmup
|
||||
@test result.allocs == 0
|
||||
@test result.memory == 0
|
||||
|
||||
println(" ✓ Zero-allocation access verified")
|
||||
end
|
||||
|
||||
# ========================================================================
|
||||
# 5. Edge Cases
|
||||
# ========================================================================
|
||||
|
||||
@testset "Edge Cases" begin
|
||||
println("\n[5] Testing edge cases...")
|
||||
|
||||
# Empty elements
|
||||
mgr = DOFManager()
|
||||
mgr.total_dofs = 0
|
||||
connectivity = build_dof_connectivity(Element[], mgr)
|
||||
@test connectivity.n_total_dofs == 0
|
||||
@test length(connectivity.dof_to_elements) == 0
|
||||
|
||||
# Single element
|
||||
mgr = DOFManager()
|
||||
mgr.total_dofs = 12
|
||||
S = @DOFSet{u::DOF{Displacement{3}, Vertex}}
|
||||
ElemType = Element{Tetrahedron{4}, Lagrange{1}, S}
|
||||
elem = ElemType(UInt(1), (UInt64(1), UInt64(2), UInt64(3), UInt64(4), UInt64(5), UInt64(6), UInt64(7), UInt64(8), UInt64(9), UInt64(10), UInt64(11), UInt64(12)))
|
||||
connectivity = build_dof_connectivity([elem], mgr)
|
||||
@test connectivity.n_total_dofs == 12
|
||||
@test all(count -> count == 1, [connection_count(connectivity, i) for i in 1:12])
|
||||
|
||||
# Invalid DOF index
|
||||
mgr2 = DOFManager()
|
||||
mgr2.total_dofs = 10
|
||||
elem_invalid = ElemType(UInt(1), (UInt64(1), UInt64(2), UInt64(3), UInt64(4), UInt64(5), UInt64(6), UInt64(7), UInt64(8), UInt64(9), UInt64(10), UInt64(11), UInt64(12))) # DOF 11, 12 invalid
|
||||
@test_throws ErrorException build_dof_connectivity([elem_invalid], mgr2)
|
||||
|
||||
println(" ✓ Edge cases handled")
|
||||
end
|
||||
|
||||
# ========================================================================
|
||||
# 6. GPU Compatibility
|
||||
# ========================================================================
|
||||
|
||||
@testset "GPU Compatibility" begin
|
||||
println("\n[6] Testing GPU compatibility...")
|
||||
|
||||
# Test that DOFElementConnection is a bits type (GPU-compatible)
|
||||
conn_test = DOFElementConnection(1, 1)
|
||||
@test isbits(conn_test)
|
||||
# Verify it's a struct type (not mutable)
|
||||
@test DOFElementConnection isa Type
|
||||
|
||||
# Test that GPU connectivity arrays are transferable
|
||||
mgr = DOFManager()
|
||||
mgr.total_dofs = 50
|
||||
S = @DOFSet{u::DOF{Displacement{3}, Vertex}}
|
||||
ElemType = Element{Tetrahedron{4}, Lagrange{1}, S}
|
||||
elements = [ElemType(UInt(i), tuple((UInt64(d) for d in 1:12)...)) for i in 1:5]
|
||||
|
||||
conn_gpu = build_dof_connectivity_gpu(elements, mgr, max_connections=10)
|
||||
|
||||
# All array element types should be bits types (for GPU transfer)
|
||||
# Test with instances, not types
|
||||
@test isbits(Int32(1))
|
||||
@test isbits(Int16(1))
|
||||
@test eltype(conn_gpu.elem_ids) == Int32
|
||||
@test eltype(conn_gpu.local_indices) == Int16
|
||||
@test eltype(conn_gpu.counts) == Int32
|
||||
|
||||
# Arrays should be standard Julia arrays (contiguous by default)
|
||||
@test conn_gpu.elem_ids isa Matrix{Int32}
|
||||
@test conn_gpu.local_indices isa Matrix{Int16}
|
||||
@test conn_gpu.counts isa Vector{Int32}
|
||||
|
||||
println(" ✓ GPU compatibility verified")
|
||||
end
|
||||
|
||||
println("\n" * "="^70)
|
||||
println("ALL TESTS PASSED")
|
||||
println("="^70)
|
||||
end
|
||||
Reference in New Issue
Block a user