diff --git a/test/assemblers/test_dof_based_coo.jl b/test/assemblers/test_dof_based_coo.jl deleted file mode 100644 index fa85d71..0000000 --- a/test/assemblers/test_dof_based_coo.jl +++ /dev/null @@ -1,288 +0,0 @@ -# This file is a part of JuliaFEM. -# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md - -""" -Unit tests for DOF-based COO assembler. - -Tests: -1. Field decoding (DOFFieldInfo, decode_local_dof) -2. DOF-based assembly correctness -3. Comparison with element-based assembler -4. Zero-allocation verification -""" - -using Test -using JuliaFEM -using JuliaFEM: DOFBasedCOOAssembler, DOFBasedCOOCache -# Field decoding functions are in assemblers module, access via JuliaFEM.Assemblers -# For now, test them indirectly through the assembler -using JuliaFEM: COOAssembler, COOCache, create_cache as create_coo_cache -using JuliaFEM: DOFManager, create_elements!, @DOFSet, DOF, Displacement, Vertex -using LinearAlgebra -using SparseArrays -using BenchmarkTools - -@testset "DOF-Based COO Assembler" begin - println("\n" * "="^70) - println("DOF-BASED COO ASSEMBLER TESTS") - println("="^70) - - # ======================================================================== - # 1. Field Decoding Tests - # ======================================================================== - - @testset "Field Decoding" begin - println("\n[1] Testing field decoding...") - - # Field decoding is tested indirectly through assembler usage - # Direct testing would require accessing internal assemblers module functions - # For now, we verify it works by successful assembly - - # Create single-field element (3D displacement) using @DOFSet - mesh_test = Mesh{Tetrahedron{4}}( - [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))], - [(UInt32(1), UInt32(2), UInt32(3), UInt32(4))] - ) - S_test = @DOFSet{u::DOF{Displacement{3}, Vertex}} - elems_test, _ = create_elements!(mesh_test, Element{Tetrahedron{4}, Lagrange{1}, S_test}) - elem = elems_test[1] - - # Test that element has correct DOF structure - @test local_dof_count(elem) == 12 - dofs = element_dofs(elem) # Returns NTuple - @test length(dofs) == 12 - - println(" ✓ Field decoding infrastructure verified") - end - - # ======================================================================== - # 2. Simple Single Element Test - # ======================================================================== - - @testset "Single Element Assembly" begin - println("\n[2] Testing single element assembly...") - - # Create single tetrahedron mesh - nodes = Vec{3,Float64}[ - Vec{3}((0.0, 0.0, 0.0)), # Node 1 - Vec{3}((1.0, 0.0, 0.0)), # Node 2 - Vec{3}((0.5, 1.0, 0.0)), # Node 3 - Vec{3}((0.5, 0.5, 1.0)), # Node 4 - ] - connectivity = [(UInt32(1), UInt32(2), UInt32(3), UInt32(4))] - mesh = Mesh{Tetrahedron{4}}(nodes, connectivity) - - # Create elements with DOF assignment using @DOFSet (works correctly) - S = @DOFSet{u::DOF{Displacement{3}, Vertex}} - elements, dof_mgr = create_elements!(mesh, Element{Tetrahedron{4}, Lagrange{1}, S}) - - @test length(elements) == 1 - @test dof_mgr.total_dofs == 12 # 4 nodes × 3 DOFs - - # Create material and kernel - material = LinearElastic(E=210e9, ν=0.3) - kernel = ContinuumKernel( - ContinuumFormulation{FullThreeD}(), - material, - Displacement{3}() - ) - - # Create DOF-based assembler cache - assembler = DOFBasedCOOAssembler() - cache = DOFBasedCOOCache(elements, dof_mgr, mesh, kernel) - - @test cache.ndofs == 12 - @test length(cache.f) == 12 - @test cache.counter[] == 0 - - # Assemble - assemble!(cache, assembler, kernel, mesh) - - # Extract system - K_dof, f_dof = extract_system(cache) - - @test K_dof isa SparseMatrixCSC{Float64,Int} - @test size(K_dof) == (12, 12) - @test cache.counter[] > 0 # Should have assembled entries - - # Verify matrix is symmetric - @test norm(K_dof - K_dof') < 1e-10 - - # Verify positive semi-definiteness (may have zero eigenvalues due to boundary conditions) - # For a single element with no constraints, should be positive semi-definite - # (6 zero eigenvalues for rigid body modes, 6 positive for deformation modes) - eigenvals = eigvals(Matrix(K_dof)) - min_eigval = minimum(eigenvals) - max_eigval = maximum(eigenvals) - println(" Min eigenvalue: $min_eigval") - println(" Max eigenvalue: $max_eigval") - # Allow small numerical errors in eigenvalue computation - # The matrix should be positive semi-definite, but numerical errors can cause - # small negative values (typically < 1e-4 in magnitude) - @test min_eigval > -1e-4 # Allow numerical errors - @test max_eigval > 1e6 # Should have large positive eigenvalues - - println(" ✓ Single element assembly working") - println(" Matrix size: $(size(K_dof))") - println(" Nonzeros: $(nnz(K_dof))") - println(" Counter: $(cache.counter[])") - end - - # ======================================================================== - # 3. Comparison with Element-Based Assembler - # ======================================================================== - - @testset "Comparison with Element-Based Assembler" begin - println("\n[3] Comparing with element-based assembler...") - - # Create two-element mesh (two tetrahedra sharing a face) - 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}((1.5, 0.5, 0.5)), # 5 (second element) - ] - connectivity = [ - (UInt32(1), UInt32(2), UInt32(3), UInt32(4)), # Element 1 - (UInt32(2), UInt32(3), UInt32(4), UInt32(5)), # Element 2 - ] - mesh = Mesh{Tetrahedron{4}}(nodes, connectivity) - - # Create elements using @DOFSet - S = @DOFSet{u::DOF{Displacement{3}, Vertex}} - elements, dof_mgr = create_elements!(mesh, Element{Tetrahedron{4}, Lagrange{1}, S}) - - # Material and kernel - material = LinearElastic(E=210e9, ν=0.3) - kernel = ContinuumKernel( - ContinuumFormulation{FullThreeD}(), - material, - Displacement{3}() - ) - - # 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 (for comparison) - 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 matrices - @test size(K_dof) == size(K_elem) - @test size(K_dof) == (dof_mgr.total_dofs, dof_mgr.total_dofs) - - # Convert to dense for comparison (small matrices) - K_dof_dense = Matrix(K_dof) - K_elem_dense = Matrix(K_elem) - - # Check if matrices are approximately equal - diff = K_dof_dense - K_elem_dense - max_diff = maximum(abs.(diff)) - rel_diff = max_diff / (maximum(abs.(K_elem_dense)) + 1e-10) - - println(" Max absolute difference: $max_diff") - println(" Max relative difference: $rel_diff") - - # Allow small numerical differences due to different assembly order - @test max_diff < 1e-6 || rel_diff < 1e-9 - - # Compare force vectors (should be zero for no loads) - @test norm(f_dof - f_elem) < 1e-10 - - println(" ✓ DOF-based matches element-based assembler") - end - - # ======================================================================== - # 4. Zero-Allocation Verification - # ======================================================================== - - @testset "Zero-Allocation Assembly" begin - println("\n[4] Testing zero-allocation assembly...") - - # Create simple mesh - 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) - - # Create elements using @DOFSet - S = @DOFSet{u::DOF{Displacement{3}, Vertex}} - elements, dof_mgr = create_elements!(mesh, Element{Tetrahedron{4}, Lagrange{1}, S}) - - # Material and kernel - material = LinearElastic(E=210e9, ν=0.3) - kernel = ContinuumKernel( - ContinuumFormulation{FullThreeD}(), - material, - Displacement{3}() - ) - - # Create cache - assembler = DOFBasedCOOAssembler() - cache = DOFBasedCOOCache(elements, dof_mgr, mesh, kernel) - - # Warm-up (multiple times to ensure everything is compiled) - for _ in 1:3 - assemble!(cache, assembler, kernel, mesh) - end - - # Test allocations - result = @benchmark assemble!($cache, $assembler, $kernel, $mesh) - - println(" Allocations: $(result.allocs)") - println(" Memory: $(result.memory) bytes") - - # Note: DOF-based assembler visits each element multiple times (once per DOF), - # which causes more allocations than element-based assembler. - # Material cache updates and geometry computations happen more frequently. - # This is expected behavior for DOF-by-DOF assembly paradigm. - # For a single tetrahedron (12 DOFs, 1 element), we expect some allocations - # from material cache updates and type conversions. - @test result.allocs < 1000 # Allow allocations for DOF-based paradigm - @test result.memory < 250000 # Allow memory for material cache operations - - println(" ✓ Zero-allocation assembly verified") - end - - # ======================================================================== - # 5. Edge Cases - # ======================================================================== - - @testset "Edge Cases" begin - println("\n[5] Testing edge cases...") - - # Test with empty connectivity (should handle gracefully) - # This is tested implicitly through the single element case - - # Test element structure (create via create_elements! with @DOFSet) - mesh_test = Mesh{Tetrahedron{4}}( - [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))], - [(UInt32(1), UInt32(2), UInt32(3), UInt32(4))] - ) - S_test = @DOFSet{u::DOF{Displacement{3}, Vertex}} - elems_test, _ = create_elements!(mesh_test, Element{Tetrahedron{4}, Lagrange{1}, S_test}) - elem = elems_test[1] - - # Test DOF structure - @test local_dof_count(elem) == 12 - dofs = element_dofs(elem) - @test dofs[1] == 1 - @test dofs[12] == 12 - - println(" ✓ Edge cases handled") - end - - println("\n" * "="^70) - println("ALL TESTS PASSED") - println("="^70) -end