Files
JuliaFEM.jl/test/mesh/test_parallel_features.jl
T
Jukka Aho fde2c6a4a8 test(mesh): add parallel features test
New 297-line test file for mesh parallel features:
- Tests node and element naming (industrial ID ranges, symbolic names)
- Tests node coloring for load balancing (MPI ranks)
- Tests element coloring for graph coloring (threading)
- Tests ghost nodes and elements (MPI domain decomposition)
- Tests node and element permutation (bandwidth minimization, cache optimization)
- Tests combined features in industrial workflow
- Validates forward/inverse permutation consistency

Comprehensive test suite for parallel computing features including
MPI domain decomposition and threading support.
2025-12-15 09:04:22 +02:00

298 lines
10 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
# Mock topology types
abstract type AbstractTopology end
struct Tet4 <: AbstractTopology end
nnodes(::Type{Tet4}) = 4
# Include mesh implementation
include("../src/mesh/mesh.jl")
@testset "Mesh Parallel Features - Naming, Coloring, Permutation, Ghosts" begin
# Setup: Create a simple two-element mesh
function make_test_mesh()
nodes = [
Vec(0.0, 0.0, 0.0), # 1
Vec(1.0, 0.0, 0.0), # 2
Vec(0.0, 1.0, 0.0), # 3
Vec(0.0, 0.0, 1.0), # 4
Vec(1.0, 1.0, 0.0) # 5
]
connectivity = [
(UInt32(1), UInt32(2), UInt32(3), UInt32(4)), # Element 1
(UInt32(2), UInt32(5), UInt32(3), UInt32(4)) # Element 2
]
return Mesh{Tet4}(nodes, connectivity)
end
@testset "Node Naming - Industrial ID Ranges" begin
mesh = make_test_mesh()
# Assign industrial ID ranges (part 1: 10M+, part 2: 20M+)
set_node_id!(mesh, UInt32(1), 10_000_001)
set_node_id!(mesh, UInt32(2), 10_000_002)
set_node_id!(mesh, UInt32(3), 20_000_001)
# Retrieve by ID
@test get_node_by_id(mesh, 10_000_001) == UInt32(1)
@test get_node_by_id(mesh, 10_000_002) == UInt32(2)
@test get_node_by_id(mesh, 20_000_001) == UInt32(3)
# Non-existent ID should error
@test_throws AssertionError get_node_by_id(mesh, 99999)
end
@testset "Node Naming - Symbolic Names (Code Aster Style)" begin
mesh = make_test_mesh()
# Assign symbolic names
set_node_id!(mesh, UInt32(1), :N1)
set_node_id!(mesh, UInt32(2), :N2)
set_node_id!(mesh, UInt32(5), :corner_node)
# Retrieve by symbol
@test get_node_by_id(mesh, :N1) == UInt32(1)
@test get_node_by_id(mesh, :N2) == UInt32(2)
@test get_node_by_id(mesh, :corner_node) == UInt32(5)
# Non-existent symbol should error
@test_throws AssertionError get_node_by_id(mesh, :nonexistent)
end
@testset "Element Naming - Industrial ID Ranges" begin
mesh = make_test_mesh()
# Assign element IDs
set_element_id!(mesh, UInt32(1), 30_000_001)
set_element_id!(mesh, UInt32(2), 30_000_002)
# Retrieve by ID
@test get_element_by_id(mesh, 30_000_001) == UInt32(1)
@test get_element_by_id(mesh, 30_000_002) == UInt32(2)
# Non-existent ID should error
@test_throws AssertionError get_element_by_id(mesh, 99999)
end
@testset "Element Naming - Symbolic Names" begin
mesh = make_test_mesh()
# Assign symbolic names
set_element_id!(mesh, UInt32(1), :E1)
set_element_id!(mesh, UInt32(2), :E2)
# Retrieve by symbol
@test get_element_by_id(mesh, :E1) == UInt32(1)
@test get_element_by_id(mesh, :E2) == UInt32(2)
end
@testset "Node Coloring - Load Balancing" begin
mesh = make_test_mesh()
# Initially all nodes uncolored (color = 0)
for i in 1:nnodes_total(mesh)
@test get_node_color(mesh, UInt32(i)) == UInt32(0)
end
# Assign nodes to MPI ranks (colors 1-4)
for i in 1:nnodes_total(mesh)
rank = mod(i - 1, 4) + 1 # Round-robin: 1,2,3,4,1
set_node_color!(mesh, UInt32(i), UInt32(rank))
end
# Verify colors
@test get_node_color(mesh, UInt32(1)) == UInt32(1)
@test get_node_color(mesh, UInt32(2)) == UInt32(2)
@test get_node_color(mesh, UInt32(3)) == UInt32(3)
@test get_node_color(mesh, UInt32(4)) == UInt32(4)
@test get_node_color(mesh, UInt32(5)) == UInt32(1)
end
@testset "Element Coloring - Graph Coloring for Threading" begin
mesh = make_test_mesh()
# Initially all elements uncolored
@test get_element_color(mesh, UInt32(1)) == UInt32(0)
@test get_element_color(mesh, UInt32(2)) == UInt32(0)
# Assign colors (elements sharing nodes get different colors)
set_element_color!(mesh, UInt32(1), UInt32(1))
set_element_color!(mesh, UInt32(2), UInt32(2)) # Shares nodes with elem 1
# Verify colors
@test get_element_color(mesh, UInt32(1)) == UInt32(1)
@test get_element_color(mesh, UInt32(2)) == UInt32(2)
# Get elements by color
color1_elems = get_elements_with_color(mesh, UInt32(1))
color2_elems = get_elements_with_color(mesh, UInt32(2))
@test UInt32(1) in color1_elems
@test UInt32(2) in color2_elems
@test length(color1_elems) == 1
@test length(color2_elems) == 1
end
@testset "Ghost Nodes - MPI Domain Decomposition" begin
mesh = make_test_mesh()
# Initially no ghost nodes
@test !is_ghost_node(mesh, UInt32(1))
@test !is_ghost_node(mesh, UInt32(2))
# Mark nodes 2,3,4 as ghosts (owned by another rank)
mark_ghost_node!(mesh, UInt32(2))
mark_ghost_node!(mesh, UInt32(3))
mark_ghost_node!(mesh, UInt32(4))
# Verify ghost status
@test !is_ghost_node(mesh, UInt32(1)) # Local
@test is_ghost_node(mesh, UInt32(2)) # Ghost
@test is_ghost_node(mesh, UInt32(3)) # Ghost
@test is_ghost_node(mesh, UInt32(4)) # Ghost
@test !is_ghost_node(mesh, UInt32(5)) # Local
# Get local nodes (non-ghost)
local_nodes = get_local_nodes(mesh)
@test UInt32(1) in local_nodes
@test UInt32(5) in local_nodes
@test !(UInt32(2) in local_nodes)
@test !(UInt32(3) in local_nodes)
@test !(UInt32(4) in local_nodes)
@test length(local_nodes) == 2
end
@testset "Ghost Elements - MPI Domain Decomposition" begin
mesh = make_test_mesh()
# Initially no ghost elements
@test !is_ghost_element(mesh, UInt32(1))
@test !is_ghost_element(mesh, UInt32(2))
# Mark element 2 as ghost
mark_ghost_element!(mesh, UInt32(2))
# Verify ghost status
@test !is_ghost_element(mesh, UInt32(1)) # Local
@test is_ghost_element(mesh, UInt32(2)) # Ghost
# Get local elements
local_elems = get_local_elements(mesh)
@test UInt32(1) in local_elems
@test !(UInt32(2) in local_elems)
@test length(local_elems) == 1
end
@testset "Node Permutation - Identity (Initial State)" begin
mesh = make_test_mesh()
# Initially identity permutation
for i in 1:nnodes_total(mesh)
@test mesh.node_permutation[i] == UInt32(i)
@test mesh.node_inverse_permutation[i] == UInt32(i)
end
# Forward and inverse should be consistent
for i in 1:nnodes_total(mesh)
j = get_reordered_node_index(mesh, UInt32(i))
@test get_original_node_index(mesh, j) == UInt32(i)
end
end
@testset "Node Permutation - Custom Reordering" begin
mesh = make_test_mesh()
# Apply custom permutation (reverse order for simplicity)
n = nnodes_total(mesh)
perm = UInt32[n, n-1, n-2, n-3, n-4] # [5, 4, 3, 2, 1]
apply_node_permutation!(mesh, perm)
# Verify permutation
@test mesh.node_permutation == perm
# Verify inverse permutation
@test mesh.node_inverse_permutation == UInt32[5, 4, 3, 2, 1]
# Check forward mapping: original 1 → reordered 5
@test get_reordered_node_index(mesh, UInt32(1)) == UInt32(5)
@test get_reordered_node_index(mesh, UInt32(5)) == UInt32(1)
# Check inverse mapping: reordered 1 → original 5
@test get_original_node_index(mesh, UInt32(1)) == UInt32(5)
@test get_original_node_index(mesh, UInt32(5)) == UInt32(1)
# Verify consistency
for i in 1:n
j = get_reordered_node_index(mesh, UInt32(i))
@test get_original_node_index(mesh, j) == UInt32(i)
end
end
@testset "Node Permutation - Invalid Permutation" begin
mesh = make_test_mesh()
# Wrong size
@test_throws AssertionError apply_node_permutation!(mesh, UInt32[1, 2])
# Invalid permutation (duplicate)
@test_throws AssertionError apply_node_permutation!(mesh, UInt32[1, 1, 2, 3, 4])
# Invalid permutation (out of range)
@test_throws AssertionError apply_node_permutation!(mesh, UInt32[1, 2, 3, 4, 6])
end
@testset "Element Permutation - Cache Optimization" begin
mesh = make_test_mesh()
# Initially identity
@test mesh.element_permutation == UInt32[1, 2]
# Apply custom permutation (swap elements)
perm = UInt32[2, 1]
apply_element_permutation!(mesh, perm)
# Verify permutation
@test mesh.element_permutation == perm
end
@testset "Combined Features - Industrial Workflow" begin
mesh = make_test_mesh()
# 1. Assign industrial IDs (multi-part assembly)
set_node_id!(mesh, UInt32(1), 10_000_001)
set_node_id!(mesh, UInt32(2), 10_000_002)
set_node_id!(mesh, UInt32(3), 20_000_001) # Part 2 starts here
set_element_id!(mesh, UInt32(1), 30_000_001)
set_element_id!(mesh, UInt32(2), 30_000_002)
# 2. Apply bandwidth minimization (RCM-like)
perm = UInt32[2, 1, 3, 4, 5] # Simulated RCM result
apply_node_permutation!(mesh, perm)
# 3. Color elements for parallel assembly
set_element_color!(mesh, UInt32(1), UInt32(1))
set_element_color!(mesh, UInt32(2), UInt32(2))
# 4. Mark ghost nodes (MPI partitioning)
mark_ghost_node!(mesh, UInt32(3))
mark_ghost_node!(mesh, UInt32(4))
# Verify everything works together
@test get_node_by_id(mesh, 10_000_001) == UInt32(1)
@test get_element_by_id(mesh, 30_000_001) == UInt32(1)
@test get_reordered_node_index(mesh, UInt32(1)) == UInt32(2)
@test get_element_color(mesh, UInt32(1)) == UInt32(1)
@test is_ghost_node(mesh, UInt32(3))
@test length(get_local_nodes(mesh)) == 3 # 5 nodes - 2 ghosts = 3 local
end
end
println("✅ All parallel features tests passed!")