mirror of
https://github.com/JuliaFEM/JuliaFEM.jl.git
synced 2026-08-28 15:21:02 +00:00
fde2c6a4a8
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.
298 lines
10 KiB
Julia
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!")
|