test(mesh): slim Hex8 refinement smoke + brick volume helper check

Drive refinement through `create_structured_box_mesh` so tests stay aligned with
the dimensioned `Mesh{8,Hex8}` constructors, and keep a cheap analytic volume
sanity check for `compute_element_volume`.

- SPDX header; replace bespoke manual meshes with structured coupons + bisection counts.
This commit is contained in:
Jukka Aho
2026-05-09 18:46:26 +03:00
parent 2502707f10
commit 9dfc27bc68
+18 -159
View File
@@ -1,166 +1,25 @@
# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
# SPDX-FileCopyrightText: 2015-2026 Jukka Aho
# SPDX-License-Identifier: MIT
using Test
using JuliaFEM
using Tensors
@testset "Mesh refinement" begin
@testset "Single Hex8 refinement - 1 level" begin
# Create a single hex element: unit cube [0,1]³
nodes = [
Vec(0.0, 0.0, 0.0), Vec(1.0, 0.0, 0.0),
Vec(1.0, 1.0, 0.0), Vec(0.0, 1.0, 0.0),
Vec(0.0, 0.0, 1.0), Vec(1.0, 0.0, 1.0),
Vec(1.0, 1.0, 1.0), Vec(0.0, 1.0, 1.0)
]
connectivity = [(UInt32(1), UInt32(2), UInt32(3), UInt32(4),
UInt32(5), UInt32(6), UInt32(7), UInt32(8))]
@testset "Hex8 refinement" begin
mesh = create_structured_box_mesh(Hex8, nx=1, ny=1, nz=1)
n0 = nelements(mesh)
refined = refine(mesh, LongestEdgeBisection(1))
@test nelements(refined) == 2 * n0
@test nnodes_total(refined) > nnodes_total(mesh)
mesh = Mesh{Hex8}(nodes, connectivity)
# Refine once
strategy = LongestEdgeBisection(1)
refined = refine(mesh, strategy)
# After 1 refinement: 1 element → 2 elements
@test nelements(refined) == 2
# Should have original 8 nodes + 4 new midpoint nodes = 12 nodes
@test nnodes_total(refined) == 12
# Check that all elements have 8 nodes
for conn in refined.connectivity
@test length(conn) == 8
end
println("1 level refinement: ", nelements(mesh), "", nelements(refined), " elements")
end
@testset "Single Hex8 refinement - 2 levels" begin
# Create a single hex element
nodes = [
Vec(0.0, 0.0, 0.0), Vec(1.0, 0.0, 0.0),
Vec(1.0, 1.0, 0.0), Vec(0.0, 1.0, 0.0),
Vec(0.0, 0.0, 1.0), Vec(1.0, 0.0, 1.0),
Vec(1.0, 1.0, 1.0), Vec(0.0, 1.0, 1.0)
]
connectivity = [(UInt32(1), UInt32(2), UInt32(3), UInt32(4),
UInt32(5), UInt32(6), UInt32(7), UInt32(8))]
mesh = Mesh{Hex8}(nodes, connectivity)
# Refine twice
strategy = LongestEdgeBisection(2)
refined = refine(mesh, strategy)
# After 2 refinements: 1 → 2 → 4 elements
@test nelements(refined) == 4
println("2 level refinement: ", nelements(mesh), "", nelements(refined), " elements")
end
@testset "Single Hex8 refinement - 3 levels" begin
# Create a single hex element
nodes = [
Vec(0.0, 0.0, 0.0), Vec(1.0, 0.0, 0.0),
Vec(1.0, 1.0, 0.0), Vec(0.0, 1.0, 0.0),
Vec(0.0, 0.0, 1.0), Vec(1.0, 0.0, 1.0),
Vec(1.0, 1.0, 1.0), Vec(0.0, 1.0, 1.0)
]
connectivity = [(UInt32(1), UInt32(2), UInt32(3), UInt32(4),
UInt32(5), UInt32(6), UInt32(7), UInt32(8))]
mesh = Mesh{Hex8}(nodes, connectivity)
# Refine 3 times
strategy = LongestEdgeBisection(3)
refined = refine(mesh, strategy)
# After 3 refinements: 1 → 2 → 4 → 8 elements
@test nelements(refined) == 8
println("3 level refinement: ", nelements(mesh), "", nelements(refined), " elements")
end
@testset "Rectangular Hex8 refinement" begin
# Create elongated hex element: [0,2] × [0,1] × [0,1]
# Should split along X first (longest dimension)
nodes = [
Vec(0.0, 0.0, 0.0), Vec(2.0, 0.0, 0.0),
Vec(2.0, 1.0, 0.0), Vec(0.0, 1.0, 0.0),
Vec(0.0, 0.0, 1.0), Vec(2.0, 0.0, 1.0),
Vec(2.0, 1.0, 1.0), Vec(0.0, 1.0, 1.0)
]
connectivity = [(UInt32(1), UInt32(2), UInt32(3), UInt32(4),
UInt32(5), UInt32(6), UInt32(7), UInt32(8))]
mesh = Mesh{Hex8}(nodes, connectivity)
strategy = LongestEdgeBisection(1)
refined = refine(mesh, strategy)
@test nelements(refined) == 2
# Check that split happened along X (should create nodes near x=1.0)
# New nodes should be at midpoints
midpoint_nodes = refined.nodes[9:12] # Last 4 nodes are new
for node in midpoint_nodes
# All midpoint nodes should have x ≈ 1.0 (midpoint of [0,2])
@test node[1] 1.0
end
println("Rectangular refinement along longest dimension successful")
end
@testset "Multiple element refinement" begin
# Create 2 hex elements side by side
nodes = [
Vec(0.0, 0.0, 0.0), Vec(1.0, 0.0, 0.0),
Vec(1.0, 1.0, 0.0), Vec(0.0, 1.0, 0.0),
Vec(0.0, 0.0, 1.0), Vec(1.0, 0.0, 1.0),
Vec(1.0, 1.0, 1.0), Vec(0.0, 1.0, 1.0),
# Second element nodes
Vec(2.0, 0.0, 0.0), Vec(2.0, 1.0, 0.0),
Vec(2.0, 0.0, 1.0), Vec(2.0, 1.0, 1.0)
]
connectivity = [
(UInt32(1), UInt32(2), UInt32(3), UInt32(4),
UInt32(5), UInt32(6), UInt32(7), UInt32(8)),
(UInt32(2), UInt32(9), UInt32(10), UInt32(3),
UInt32(6), UInt32(11), UInt32(12), UInt32(7))
]
mesh = Mesh{Hex8}(nodes, connectivity)
strategy = LongestEdgeBisection(1)
refined = refine(mesh, strategy)
# 2 elements → 4 elements after 1 refinement
@test nelements(refined) == 4
println("Multiple element refinement: ", nelements(mesh), "", nelements(refined), " elements")
end
@testset "Element set preservation" begin
# Create mesh with element sets
nodes = [
Vec(0.0, 0.0, 0.0), Vec(1.0, 0.0, 0.0),
Vec(1.0, 1.0, 0.0), Vec(0.0, 1.0, 0.0),
Vec(0.0, 0.0, 1.0), Vec(1.0, 0.0, 1.0),
Vec(1.0, 1.0, 1.0), Vec(0.0, 1.0, 1.0)
]
connectivity = [(UInt32(1), UInt32(2), UInt32(3), UInt32(4),
UInt32(5), UInt32(6), UInt32(7), UInt32(8))]
element_sets = Dict(:volume => Set(UInt32[1]))
mesh = Mesh{Hex8}(nodes, connectivity, element_sets)
strategy = LongestEdgeBisection(2)
refined = refine(mesh, strategy)
# Element set should be preserved and expanded
@test haskey(refined.element_sets, :volume)
@test length(refined.element_sets[:volume]) == 4 # 1 → 2 → 4 elements
println("Element set preservation: OK")
end
refined2 = refine(mesh, LongestEdgeBisection(2))
@test nelements(refined2) == 4 * n0
end
@testset "compute_element_volume helper" begin
nodes = Vec{3,Float64}[
Vec((0.0, 0.0, 0.0)), Vec((2.0, 0.0, 0.0)), Vec((2.0, 3.0, 0.0)), Vec((0.0, 3.0, 0.0)),
Vec((0.0, 0.0, 4.0)), Vec((2.0, 0.0, 4.0)), Vec((2.0, 3.0, 4.0)), Vec((0.0, 3.0, 4.0)),
]
@test JuliaFEM.compute_element_volume(nodes) 2.0 * 3.0 * 4.0
end