test(assemblers): add partitioning helper unit tests

This commit is contained in:
Jukka Aho
2026-05-09 18:37:46 +03:00
parent c6efee65ed
commit 656fa35c6f
@@ -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