From 656fa35c6ffd6683ead84ea35cbc874b975be59c Mon Sep 17 00:00:00 2001 From: Jukka Aho Date: Sat, 9 May 2026 18:37:46 +0300 Subject: [PATCH] test(assemblers): add partitioning helper unit tests --- test/assemblers/test_partitioning_helpers.jl | 129 +++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 test/assemblers/test_partitioning_helpers.jl diff --git a/test/assemblers/test_partitioning_helpers.jl b/test/assemblers/test_partitioning_helpers.jl new file mode 100644 index 0000000..ecc372b --- /dev/null +++ b/test/assemblers/test_partitioning_helpers.jl @@ -0,0 +1,129 @@ +# This file is a part of JuliaFEM. +# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md + +""" +Pure partitioning helpers (`brick_hex_partition_slabs`, `referenced_global_dofs`, +`element_counts_by_part`) — correctness vs structured Hex8 ordering and DOF overlap +at slab interfaces. +""" + +using Test +using JuliaFEM +using JuliaFEM: DOFBasedCOOAssembler, DOFBasedCOOCache +using JuliaFEM: apply_K!, apply_K_contributions! +using JuliaFEM: create_elements!, @DOFSet, DOF, Displacement, Vertex +using JuliaFEM: element_indices_for_part, validate_partition +using JuliaFEM: brick_hex_slab_upper, brick_hex_partition_slabs +using JuliaFEM: element_counts_by_part, referenced_global_dofs +using JuliaFEM: sum_element_dof_slots, mark_referenced_dofs!, collect_true_indices! +using JuliaFEM: fill_referenced_dof_indices!, ghost_dof_mask!, node_partition_owner_min! +using JuliaFEM: mark_owned_vertex_field_dofs! +using Random +using Tensors + +@testset "brick_hex_partition_slabs rejects nparts > ncells on axis" begin + @test_throws ArgumentError brick_hex_partition_slabs(1, 1, 1, 4; axis = :x) + @test_throws ArgumentError brick_hex_partition_slabs(3, 2, 2, 4; axis = :y) +end + +@testset "brick_hex_slab_upper widths" begin + @test brick_hex_slab_upper(5, 3) == [2, 4, 5] + @test brick_hex_slab_upper(6, 3) == [2, 4, 6] + @test brick_hex_slab_upper(1, 4) == [1, 1, 1, 1] +end + +@testset "brick_hex_partition_slabs counts + ids" begin + nx, ny, nz = 4, 2, 2 + ne = nx * ny * nz + layout = brick_hex_partition_slabs(nx, ny, nz, 3; axis = :x) + validate_partition(layout, ne) + d = element_counts_by_part(layout) + @test sort!(collect(keys(d))) == [1, 2, 3] + @test sum(values(d)) == ne + # x-slab: part widths along i ≈ 2+1+1 on nx=4 + @test d[1] == 2 * ny * nz + @test d[2] == d[3] == 1 * ny * nz +end + +@testset "referenced_global_dofs overlap at x-slabs" begin + nx, ny, nz = 4, 2, 2 + mesh = create_structured_box_mesh(Hex8; nx = nx, ny = ny, nz = nz) + S = @DOFSet{u::DOF{Displacement{3}, Vertex}} + elements, _handler = create_elements!(mesh, Element{Hex8, Lagrange{1}, S}) + layout = brick_hex_partition_slabs(nx, ny, nz, 2; axis = :x) + e1 = element_indices_for_part(layout, 1) + e2 = element_indices_for_part(layout, 2) + @assert isempty(intersect(e1, e2)) + d1 = referenced_global_dofs(elements, e1) + d2 = referenced_global_dofs(elements, e2) + overlap = intersect(Set(d1), Set(d2)) + @test !isempty(overlap) +end + +@testset "slab-partitioned contributions sum to apply_K!" begin + Random.seed!(20260510) + nx, ny, nz = 3, 4, 2 + mesh = create_structured_box_mesh(Hex8; nx = nx, ny = ny, nz = nz) + material = LinearElastic(E = 210e9, ν = 0.3) + kernel = ContinuumKernel(ContinuumFormulation{FullThreeD}(), + material, Displacement{3}()) + S = @DOFSet{u::DOF{Displacement{3}, Vertex}} + elements, dof_mgr = create_elements!(mesh, Element{Hex8, Lagrange{1}, S}) + asm = DOFBasedCOOAssembler() + cache = DOFBasedCOOCache(elements, dof_mgr, mesh, kernel) + + layout = brick_hex_partition_slabs(nx, ny, nz, 3; axis = :y) # ny ≥ nparts + validate_partition(layout, length(elements)) + + n = cache.ndofs + x = randn(n) + y_full = zeros(n) + apply_K!(y_full, cache, asm, kernel, mesh, x) + + y_sum = zeros(n) + for part in 1:3 + ep = element_indices_for_part(layout, part) + apply_K_contributions!(y_sum, cache, asm, kernel, mesh, x, ep) + end + @test y_sum ≈ y_full rtol = 1e-11 atol = 1e-11 +end + +@testset "fill_referenced_dof_indices! agrees with referenced_global_dofs" begin + nx, ny, nz = 3, 2, 2 + mesh = create_structured_box_mesh(Hex8; nx = nx, ny = ny, nz = nz) + S = @DOFSet{u::DOF{Displacement{3}, Vertex}} + elements, handler = create_elements!(mesh, Element{Hex8, Lagrange{1}, S}) + layout = brick_hex_partition_slabs(nx, ny, nz, 2; axis = :x) + ep = element_indices_for_part(layout, 1) + ndof = handler.total_dofs + + ref_alloc = referenced_global_dofs(elements, ep) + + mask = falses(ndof) + buf = Vector{Int}(undef, ndof) + n = fill_referenced_dof_indices!(buf, mask, elements, ep, ndof) + @test sort(buf[1:n]) == ref_alloc +end + +@testset "ghost mask nonempty for higher partition (min-node ownership)" begin + nx, ny, nz = 4, 2, 2 + mesh = create_structured_box_mesh(Hex8; nx = nx, ny = ny, nz = nz) + S = @DOFSet{u::DOF{Displacement{3}, Vertex}} + elements, handler = create_elements!(mesh, Element{Hex8, Lagrange{1}, S}) + layout = brick_hex_partition_slabs(nx, ny, nz, 2; axis = :x) + ndof = handler.total_dofs + nnodes = length(mesh.nodes) + + node_own = Vector{Int}(undef, nnodes) + node_partition_owner_min!(node_own, layout, mesh) + + ep2 = element_indices_for_part(layout, 2) + refm = falses(ndof) + ownm = falses(ndof) + gh = falses(ndof) + + mark_referenced_dofs!(refm, elements, ep2, ndof) + mark_owned_vertex_field_dofs!(ownm, handler, node_own, 2) + ghost_dof_mask!(gh, refm, ownm) + @test any(gh) +end