Files
JuliaFEM.jl/test/mesh/test_structured.jl
T
Jukka Aho 0e5641a081 test(mesh): add structured mesh generation test
New 292-line test file for structured mesh generation:
- Tests unit cube mesh creation (single and multiple elements)
- Tests structured box mesh with custom dimensions
- Tests uniform spacing verification
- Tests cantilever and thin plate mesh generation
- Tests anisotropic mesh (fine in one direction, coarse in others)
- Tests connectivity validation
- Tests integration with refinement
- Tests convergence study pattern

Comprehensive test suite for structured mesh generation utilities.
2025-12-15 08:50:32 +02:00

293 lines
9.3 KiB
Julia
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
using Test
using JuliaFEM
using Tensors
@testset "Structured mesh generation" begin
@testset "Unit cube - single element" begin
mesh = create_unit_cube_mesh(Hex8)
# Should have 8 nodes (corners of unit cube)
@test nnodes_total(mesh) == 8
# Should have 1 element
@test nelements(mesh) == 1
# Check corner coordinates (in IJK storage order: i fastest, then j, then k)
@test mesh.nodes[1] Vec(0.0, 0.0, 0.0) # i=1, j=1, k=1
@test mesh.nodes[2] Vec(1.0, 0.0, 0.0) # i=2, j=1, k=1
@test mesh.nodes[3] Vec(0.0, 1.0, 0.0) # i=1, j=2, k=1
@test mesh.nodes[4] Vec(1.0, 1.0, 0.0) # i=2, j=2, k=1
@test mesh.nodes[5] Vec(0.0, 0.0, 1.0) # i=1, j=1, k=2
@test mesh.nodes[6] Vec(1.0, 0.0, 1.0) # i=2, j=1, k=2
@test mesh.nodes[7] Vec(0.0, 1.0, 1.0) # i=1, j=2, k=2
@test mesh.nodes[8] Vec(1.0, 1.0, 1.0) # i=2, j=2, k=2
# Check connectivity
@test length(mesh.connectivity[1]) == 8
# Check element sets
@test haskey(mesh.element_sets, :all)
@test length(mesh.element_sets[:all]) == 1
# Check node sets exist
@test haskey(mesh.node_sets, :all)
@test haskey(mesh.node_sets, :xmin)
@test haskey(mesh.node_sets, :xmax)
@test haskey(mesh.node_sets, :ymin)
@test haskey(mesh.node_sets, :ymax)
@test haskey(mesh.node_sets, :zmin)
@test haskey(mesh.node_sets, :zmax)
println("Unit cube (1 element): OK")
end
@testset "Unit cube - 2×2×2 elements" begin
mesh = create_unit_cube_mesh(Hex8, nx=2, ny=2, nz=2)
# Should have (2+1)³ = 27 nodes
@test nnodes_total(mesh) == 27
# Should have 2³ = 8 elements
@test nelements(mesh) == 8
# Check that all elements have 8 nodes
for conn in mesh.connectivity
@test length(conn) == 8
end
# Check boundary node sets
xmin_nodes = mesh.node_sets[:xmin]
@test length(xmin_nodes) == 9 # 3×3 face
# All xmin nodes should have x ≈ 0.0
for node_id in xmin_nodes
@test mesh.nodes[node_id][1] 0.0
end
# All xmax nodes should have x ≈ 1.0
xmax_nodes = mesh.node_sets[:xmax]
@test length(xmax_nodes) == 9
for node_id in xmax_nodes
@test mesh.nodes[node_id][1] 1.0
end
println("Unit cube (2³ elements): OK")
end
@testset "Structured box - custom dimensions" begin
# Create 10×2×2 box
mesh = create_structured_box_mesh(Hex8,
xmin=0.0, xmax=10.0, nx=5,
ymin=0.0, ymax=2.0, ny=2,
zmin=0.0, zmax=2.0, nz=2)
# Should have (5+1)×(2+1)×(2+1) = 6×3×3 = 54 nodes
@test nnodes_total(mesh) == 54
# Should have 5×2×2 = 20 elements
@test nelements(mesh) == 20
# Check domain bounds
all_x = [node[1] for node in mesh.nodes]
all_y = [node[2] for node in mesh.nodes]
all_z = [node[3] for node in mesh.nodes]
@test minimum(all_x) 0.0
@test maximum(all_x) 10.0
@test minimum(all_y) 0.0
@test maximum(all_y) 2.0
@test minimum(all_z) 0.0
@test maximum(all_z) 2.0
println("Structured box (custom dimensions): OK")
end
@testset "Structured box - uniform spacing" begin
# Create mesh with known spacing
mesh = create_structured_box_mesh(Hex8,
xmin=0.0, xmax=4.0, nx=4,
ymin=0.0, ymax=3.0, ny=3,
zmin=0.0, zmax=2.0, nz=2)
# Check uniform spacing
# X spacing should be 1.0
x_coords = sort(unique([node[1] for node in mesh.nodes]))
@test length(x_coords) == 5
for i in 2:length(x_coords)
@test x_coords[i] - x_coords[i-1] 1.0
end
# Y spacing should be 1.0
y_coords = sort(unique([node[2] for node in mesh.nodes]))
@test length(y_coords) == 4
for i in 2:length(y_coords)
@test y_coords[i] - y_coords[i-1] 1.0
end
# Z spacing should be 1.0
z_coords = sort(unique([node[3] for node in mesh.nodes]))
@test length(z_coords) == 3
for i in 2:length(z_coords)
@test z_coords[i] - z_coords[i-1] 1.0
end
println("Uniform spacing verification: OK")
end
@testset "Cantilever mesh" begin
mesh = create_cantilever_mesh(Hex8,
length=10.0, width=2.0, height=2.0,
nx=10, ny=2, nz=2)
# Should have (10+1)×(2+1)×(2+1) = 11×3×3 = 99 nodes
@test nnodes_total(mesh) == 99
# Should have 10×2×2 = 40 elements
@test nelements(mesh) == 40
# Check dimensions
all_x = [node[1] for node in mesh.nodes]
all_y = [node[2] for node in mesh.nodes]
all_z = [node[3] for node in mesh.nodes]
@test maximum(all_x) - minimum(all_x) 10.0
@test maximum(all_y) - minimum(all_y) 2.0
@test maximum(all_z) - minimum(all_z) 2.0
# Boundary node sets should exist
@test haskey(mesh.node_sets, :xmin) # Fixed end
@test haskey(mesh.node_sets, :xmax) # Free end
# Fixed end should have 3×3 = 9 nodes
@test length(mesh.node_sets[:xmin]) == 9
println("Cantilever mesh: OK")
end
@testset "Thin plate mesh" begin
mesh = create_thin_plate_mesh(Hex8,
length=10.0, width=10.0, thickness=0.1,
nx=5, ny=5, nz=1)
# Should have (5+1)×(5+1)×(1+1) = 6×6×2 = 72 nodes
@test nnodes_total(mesh) == 72
# Should have 5×5×1 = 25 elements
@test nelements(mesh) == 25
# Check thickness
all_z = [node[3] for node in mesh.nodes]
@test maximum(all_z) - minimum(all_z) 0.1
# Top and bottom surfaces
@test haskey(mesh.node_sets, :zmin)
@test haskey(mesh.node_sets, :zmax)
# Each surface should have 6×6 = 36 nodes
@test length(mesh.node_sets[:zmin]) == 36
@test length(mesh.node_sets[:zmax]) == 36
println("Thin plate mesh: OK")
end
@testset "Anisotropic mesh" begin
# Fine in X, coarse in Y and Z
mesh = create_structured_box_mesh(Hex8,
xmin=0.0, xmax=10.0, nx=20,
ymin=0.0, ymax=1.0, ny=2,
zmin=0.0, zmax=1.0, nz=2)
# Should have (20+1)×(2+1)×(2+1) = 21×3×3 = 189 nodes
@test nnodes_total(mesh) == 189
# Should have 20×2×2 = 80 elements
@test nelements(mesh) == 80
# X should have finer spacing than Y and Z
x_coords = sort(unique([node[1] for node in mesh.nodes]))
y_coords = sort(unique([node[2] for node in mesh.nodes]))
z_coords = sort(unique([node[3] for node in mesh.nodes]))
x_spacing = x_coords[2] - x_coords[1]
y_spacing = y_coords[2] - y_coords[1]
z_spacing = z_coords[2] - z_coords[1]
@test x_spacing 0.5
@test y_spacing 0.5
@test z_spacing 0.5
@test length(x_coords) == 21 # Fine
@test length(y_coords) == 3 # Coarse
@test length(z_coords) == 3 # Coarse
println("Anisotropic mesh: OK")
end
@testset "Connectivity validation" begin
mesh = create_unit_cube_mesh(Hex8, nx=2, ny=2, nz=2)
# Every element should have 8 unique nodes
for (elem_id, conn) in enumerate(mesh.connectivity)
@test length(conn) == 8
@test length(unique(conn)) == 8 # All nodes unique
# All node indices should be valid
for node_id in conn
@test 1 node_id nnodes_total(mesh)
end
end
# Check that mesh validation passes
@test validate(mesh) == true
println("Connectivity validation: OK")
end
@testset "Integration with refinement" begin
# Create coarse mesh, then refine
coarse = create_cantilever_mesh(Hex8,
length=10.0, width=2.0, height=2.0,
nx=2, ny=1, nz=1)
@test nelements(coarse) == 2
# Refine 2 levels
refined = refine(coarse, LongestEdgeBisection(2))
# Should have more elements
@test nelements(refined) > nelements(coarse)
@test nelements(refined) == 8 # 2 → 4 → 8
println("Integration with refinement: OK")
end
@testset "Convergence study pattern" begin
# Simulate typical convergence study
results = []
for n in [1, 2, 4, 8]
mesh = create_unit_cube_mesh(Hex8, nx=n, ny=n, nz=n)
n_elem = nelements(mesh)
n_nodes = nnodes_total(mesh)
n_dofs = 3 * n_nodes
push!(results, (n=n, elements=n_elem, nodes=n_nodes, dofs=n_dofs))
end
# Check that refinement increases mesh size correctly
@test results[1].elements == 1 # 1³
@test results[2].elements == 8 # 2³
@test results[3].elements == 64 # 4³
@test results[4].elements == 512 # 8³
println("Convergence study pattern:")
for r in results
println(" n=$(r.n): $(r.elements) elements, $(r.nodes) nodes, $(r.dofs) DOFs")
end
end
end