mirror of
https://github.com/JuliaFEM/JuliaFEM.jl.git
synced 2026-08-06 04:21:33 +00:00
0e5641a081
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.
293 lines
9.3 KiB
Julia
293 lines
9.3 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 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
|