Files
JuliaFEM.jl/test/assemblers/test_dof_based_coo_simple.jl
T
Jukka Aho 8c6e91b1ef test(assemblers): add simple DOF-based COO test
Simple test case for DOF-based COO assembler.
Basic validation of DOF-based assembly workflow.
2025-12-15 07:46:05 +02:00

354 lines
14 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
"""
Simple unit tests for DOF-based assembler to debug correctness issues.
These tests are designed to be minimal and focused on specific aspects:
1. Single element correctness
2. Two-element correctness (shared node)
3. DOF mapping verification
4. Entry-by-entry comparison
"""
using Test
using JuliaFEM
using JuliaFEM: DOFBasedCOOAssembler, DOFBasedCOOCache
using JuliaFEM: COOAssembler, COOCache, create_cache as create_coo_cache
using JuliaFEM: DOFManager, create_elements!, @DOFSet, DOF, Displacement, Vertex
using LinearAlgebra
using SparseArrays
@testset "DOF-Based Assembler - Simple Debug Tests" begin
println("\n" * "="^70)
println("DOF-BASED ASSEMBLER - SIMPLE DEBUG TESTS")
println("="^70)
# ========================================================================
# Test 1: Single Tetrahedron - Full Matrix Comparison
# ========================================================================
@testset "Test 1: Single Tetrahedron" begin
println("\n[Test 1] Single tetrahedron element...")
# Create single tetrahedron
nodes = Vec{3,Float64}[
Vec{3}((0.0, 0.0, 0.0)),
Vec{3}((1.0, 0.0, 0.0)),
Vec{3}((0.5, 1.0, 0.0)),
Vec{3}((0.5, 0.5, 1.0)),
]
connectivity = [(UInt32(1), UInt32(2), UInt32(3), UInt32(4))]
mesh = Mesh{Tetrahedron{4}}(nodes, connectivity)
# Material and kernel
material = LinearElastic(E=210e9, ν=0.3)
kernel = ContinuumKernel(
ContinuumFormulation{FullThreeD}(),
material,
Displacement{3}()
)
# Create elements
S = @DOFSet{u::DOF{Displacement{3}, Vertex}}
elements, dof_mgr = create_elements!(mesh, Element{Tetrahedron{4}, Lagrange{1}, S})
# DOF-based assembly
assembler_dof = DOFBasedCOOAssembler()
cache_dof = DOFBasedCOOCache(elements, dof_mgr, mesh, kernel)
assemble!(cache_dof, assembler_dof, kernel, mesh)
K_dof, f_dof = extract_system(cache_dof)
# Element-based assembly
assembler_elem = COOAssembler()
cache_elem = create_coo_cache(assembler_elem, mesh, kernel)
assemble!(cache_elem, assembler_elem, kernel, mesh)
K_elem, f_elem = extract_system(cache_elem)
# Compare
K_dof_dense = Matrix(K_dof)
K_elem_dense = Matrix(K_elem)
diff = K_dof_dense - K_elem_dense
max_diff = maximum(abs.(diff))
max_val = maximum(abs.(K_elem_dense))
rel_diff = max_diff / (max_val + 1e-10)
println(" Matrix size: $(size(K_dof))")
println(" Max absolute difference: $max_diff")
println(" Max matrix value: $max_val")
println(" Max relative difference: $rel_diff")
# Print first few entries if they differ
if max_diff > 1e-6
println(" First differing entries:")
count = 0
for i in 1:min(12, size(K_dof, 1))
for j in 1:min(12, size(K_dof, 2))
if abs(diff[i, j]) > 1e-6
println(" K[$i,$j]: elem=$(K_elem_dense[i,j]), dof=$(K_dof_dense[i,j]), diff=$(diff[i,j])")
count += 1
if count >= 10
break
end
end
end
count >= 10 && break
end
end
@test size(K_dof) == size(K_elem)
@test nnz(K_dof) == nnz(K_elem)
@test max_diff < 1e-6 || rel_diff < 1e-9
@test norm(f_dof - f_elem) < 1e-10
end
# ========================================================================
# Test 2: Two Tetrahedra Sharing a Face
# ========================================================================
@testset "Test 2: Two Tetrahedra (Shared Face)" begin
println("\n[Test 2] Two tetrahedra sharing a face...")
# Two tetrahedra sharing face (1,2,3)
nodes = Vec{3,Float64}[
Vec{3}((0.0, 0.0, 0.0)), # 1
Vec{3}((1.0, 0.0, 0.0)), # 2
Vec{3}((0.5, 1.0, 0.0)), # 3
Vec{3}((0.5, 0.5, 1.0)), # 4
Vec{3}((0.5, 0.5, -1.0)), # 5
]
connectivity = [
(UInt32(1), UInt32(2), UInt32(3), UInt32(4)), # Element 1
(UInt32(1), UInt32(2), UInt32(3), UInt32(5)), # Element 2
]
mesh = Mesh{Tetrahedron{4}}(nodes, connectivity)
# Material and kernel
material = LinearElastic(E=210e9, ν=0.3)
kernel = ContinuumKernel(
ContinuumFormulation{FullThreeD}(),
material,
Displacement{3}()
)
# Create elements
S = @DOFSet{u::DOF{Displacement{3}, Vertex}}
elements, dof_mgr = create_elements!(mesh, Element{Tetrahedron{4}, Lagrange{1}, S})
println(" Elements: $(length(elements))")
println(" Total DOFs: $(dof_mgr.total_dofs)")
println(" Element 1 DOFs: $(element_dofs(elements[1]))")
println(" Element 2 DOFs: $(element_dofs(elements[2]))")
# DOF-based assembly
assembler_dof = DOFBasedCOOAssembler()
cache_dof = DOFBasedCOOCache(elements, dof_mgr, mesh, kernel)
assemble!(cache_dof, assembler_dof, kernel, mesh)
K_dof, f_dof = extract_system(cache_dof)
# Element-based assembly
assembler_elem = COOAssembler()
cache_elem = create_coo_cache(assembler_elem, mesh, kernel)
assemble!(cache_elem, assembler_elem, kernel, mesh)
K_elem, f_elem = extract_system(cache_elem)
# Compare
K_dof_dense = Matrix(K_dof)
K_elem_dense = Matrix(K_elem)
diff = K_dof_dense - K_elem_dense
max_diff = maximum(abs.(diff))
max_val = maximum(abs.(K_elem_dense))
rel_diff = max_diff / (max_val + 1e-10)
println(" Matrix size: $(size(K_dof))")
println(" Max absolute difference: $max_diff")
println(" Max relative difference: $rel_diff")
# Check shared node entries (nodes 1, 2, 3 are shared)
# These should have contributions from both elements
shared_node_dofs = [1, 2, 3, 4, 5, 6, 7, 8, 9] # DOFs for nodes 1, 2, 3
println(" Checking shared node entries (DOFs 1-9):")
max_shared_diff = 0.0
for i in shared_node_dofs, j in shared_node_dofs
d = abs(diff[i, j])
if d > max_shared_diff
max_shared_diff = d
end
if d > 1e-6
println(" K[$i,$j]: elem=$(K_elem_dense[i,j]), dof=$(K_dof_dense[i,j]), diff=$d")
end
end
println(" Max difference in shared node block: $max_shared_diff")
@test size(K_dof) == size(K_elem)
@test nnz(K_dof) == nnz(K_elem)
@test max_diff < 1e-6 || rel_diff < 1e-9
end
# ========================================================================
# Test 3: Single Hex8 Element
# ========================================================================
@testset "Test 3: Single Hex8 Element" begin
println("\n[Test 3] Single Hex8 element...")
# Create single Hex8 element
nodes = Vec{3,Float64}[
Vec{3}((0.0, 0.0, 0.0)), # 1
Vec{3}((1.0, 0.0, 0.0)), # 2
Vec{3}((1.0, 1.0, 0.0)), # 3
Vec{3}((0.0, 1.0, 0.0)), # 4
Vec{3}((0.0, 0.0, 1.0)), # 5
Vec{3}((1.0, 0.0, 1.0)), # 6
Vec{3}((1.0, 1.0, 1.0)), # 7
Vec{3}((0.0, 1.0, 1.0)), # 8
]
connectivity = [(UInt32(1), UInt32(2), UInt32(3), UInt32(4),
UInt32(5), UInt32(6), UInt32(7), UInt32(8))]
mesh = Mesh{8,Hexahedron{8}}(nodes, connectivity)
# Material and kernel
material = LinearElastic(E=210e9, ν=0.3)
kernel = ContinuumKernel(
ContinuumFormulation{FullThreeD}(),
material,
Displacement{3}()
)
# Create elements
S = @DOFSet{u::DOF{Displacement{3}, Vertex}}
elements, dof_mgr = create_elements!(mesh, Element{Hexahedron{8}, Lagrange{1}, S})
println(" Elements: $(length(elements))")
println(" Total DOFs: $(dof_mgr.total_dofs)")
println(" Element DOFs: $(element_dofs(elements[1]))")
# DOF-based assembly
assembler_dof = DOFBasedCOOAssembler()
cache_dof = DOFBasedCOOCache(elements, dof_mgr, mesh, kernel)
assemble!(cache_dof, assembler_dof, kernel, mesh)
K_dof, f_dof = extract_system(cache_dof)
# Element-based assembly
assembler_elem = COOAssembler()
cache_elem = create_coo_cache(assembler_elem, mesh, kernel)
assemble!(cache_elem, assembler_elem, kernel, mesh)
K_elem, f_elem = extract_system(cache_elem)
# Compare
K_dof_dense = Matrix(K_dof)
K_elem_dense = Matrix(K_elem)
diff = K_dof_dense - K_elem_dense
max_diff = maximum(abs.(diff))
max_val = maximum(abs.(K_elem_dense))
rel_diff = max_diff / (max_val + 1e-10)
println(" Matrix size: $(size(K_dof))")
println(" Max absolute difference: $max_diff")
println(" Max relative difference: $rel_diff")
@test size(K_dof) == size(K_elem)
@test nnz(K_dof) == nnz(K_elem)
@test max_diff < 1e-6 || rel_diff < 1e-9
end
# ========================================================================
# Test 4: Two Hex8 Elements (2×1×1 mesh)
# ========================================================================
@testset "Test 4: Two Hex8 Elements (2×1×1)" begin
println("\n[Test 4] Two Hex8 elements (2×1×1 mesh)...")
# Create 2×1×1 mesh (2 elements along X)
nodes = Vec{3,Float64}[]
for iz in 0:1, iy in 0:1, ix in 0:2
push!(nodes, Vec{3}((Float64(ix), Float64(iy), Float64(iz))))
end
connectivity = NTuple{8,UInt32}[]
for iz in 0:0, iy in 0:0, ix in 0:1
# Bottom face
n1 = ix + iy * 3 + iz * 3 * 2 + 1
n2 = (ix + 1) + iy * 3 + iz * 3 * 2 + 1
n3 = (ix + 1) + (iy + 1) * 3 + iz * 3 * 2 + 1
n4 = ix + (iy + 1) * 3 + iz * 3 * 2 + 1
# Top face
n5 = ix + iy * 3 + (iz + 1) * 3 * 2 + 1
n6 = (ix + 1) + iy * 3 + (iz + 1) * 3 * 2 + 1
n7 = (ix + 1) + (iy + 1) * 3 + (iz + 1) * 3 * 2 + 1
n8 = ix + (iy + 1) * 3 + (iz + 1) * 3 * 2 + 1
push!(connectivity, (n1, n2, n3, n4, n5, n6, n7, n8))
end
element_sets = Dict{Symbol,Set{UInt32}}(:all => Set(UInt32(1):UInt32(2)))
mesh = Mesh{8,Hexahedron{8}}(nodes, connectivity, element_sets)
# Material and kernel
material = LinearElastic(E=210e9, ν=0.3)
kernel = ContinuumKernel(
ContinuumFormulation{FullThreeD}(),
material,
Displacement{3}()
)
# Create elements
S = @DOFSet{u::DOF{Displacement{3}, Vertex}}
elements, dof_mgr = create_elements!(mesh, Element{Hexahedron{8}, Lagrange{1}, S})
println(" Nodes: $(length(nodes))")
println(" Elements: $(length(elements))")
println(" Total DOFs: $(dof_mgr.total_dofs)")
# DOF-based assembly
assembler_dof = DOFBasedCOOAssembler()
cache_dof = DOFBasedCOOCache(elements, dof_mgr, mesh, kernel)
assemble!(cache_dof, assembler_dof, kernel, mesh)
K_dof, f_dof = extract_system(cache_dof)
# Element-based assembly
assembler_elem = COOAssembler()
cache_elem = create_coo_cache(assembler_elem, mesh, kernel)
assemble!(cache_elem, assembler_elem, kernel, mesh)
K_elem, f_elem = extract_system(cache_elem)
# Compare
K_dof_dense = Matrix(K_dof)
K_elem_dense = Matrix(K_elem)
diff = K_dof_dense - K_elem_dense
max_diff = maximum(abs.(diff))
max_val = maximum(abs.(K_elem_dense))
rel_diff = max_diff / (max_val + 1e-10)
println(" Matrix size: $(size(K_dof))")
println(" Max absolute difference: $max_diff")
println(" Max relative difference: $rel_diff")
# Check shared face entries (nodes 2, 3, 6, 7 are shared between elements)
# Node 2: DOFs 4,5,6; Node 3: DOFs 7,8,9; Node 6: DOFs 16,17,18; Node 7: DOFs 19,20,21
shared_dofs = [4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, 21]
println(" Checking shared face entries:")
max_shared_diff = 0.0
count = 0
for i in shared_dofs, j in shared_dofs
d = abs(diff[i, j])
if d > max_shared_diff
max_shared_diff = d
end
if d > 1e-6 && count < 5
println(" K[$i,$j]: elem=$(K_elem_dense[i,j]), dof=$(K_dof_dense[i,j]), diff=$d")
count += 1
end
end
println(" Max difference in shared face block: $max_shared_diff")
@test size(K_dof) == size(K_elem)
@test nnz(K_dof) == nnz(K_elem)
@test max_diff < 1e-6 || rel_diff < 1e-9
end
println("\n" * "="^70)
println("ALL SIMPLE DEBUG TESTS COMPLETE")
println("="^70)
end