From b44f7b6d893938f4376c06506b680413eb0aa5b4 Mon Sep 17 00:00:00 2001 From: Jukka Aho Date: Mon, 15 Dec 2025 07:46:36 +0200 Subject: [PATCH] test(basis): add zero-cost basis test Tests zero-allocation basis function evaluation. Validates performance characteristics of basis functions. --- test/basis/test_nbasis_zerocost.jl | 93 ++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 test/basis/test_nbasis_zerocost.jl diff --git a/test/basis/test_nbasis_zerocost.jl b/test/basis/test_nbasis_zerocost.jl new file mode 100644 index 0000000..b60e02d --- /dev/null +++ b/test/basis/test_nbasis_zerocost.jl @@ -0,0 +1,93 @@ +# Test that nbasis functions are zero-cost (compile to constants) + +using Test +using JuliaFEM + +@testset "nbasis API" begin + # Test that nbasis is exported and works + @test nbasis(Triangle{3}(), Lagrange{1}()) == 3 + @test nbasis(Triangle{6}(), Lagrange{2}()) == 6 + @test nbasis(Tetrahedron{4}(), Lagrange{1}()) == 4 + @test nbasis(Tetrahedron{10}(), Lagrange{2}()) == 10 + @test nbasis(Hexahedron{8}(), Lagrange{1}()) == 8 + @test nbasis(Hexahedron{27}(), Lagrange{2}()) == 27 + + # Serendipity families + @test nbasis(Quadrilateral{8}(), Serendipity{2}()) == 8 + @test nbasis(Hexahedron{20}(), Serendipity{2}()) == 20 +end + +@testset "nbasis is zero-cost" begin + # Verify that nbasis compiles to a constant + # We use @allocated to check - should be 0 bytes + + @test (@allocated nbasis(Triangle{3}(), Lagrange{1}())) == 0 + @test (@allocated nbasis(Tetrahedron{10}(), Lagrange{2}())) == 0 + @test (@allocated nbasis(Quadrilateral{8}(), Serendipity{2}())) == 0 + + # Also verify with @code_llvm that it returns a constant + # (This would be done manually during development) + # @code_llvm nbasis(Triangle{3}(), Lagrange{1}()) # Should show: ret i64 3 +end + +@testset "validate_dof_consistency" begin + # Test that validation works correctly + + # Valid cases (should not throw) + @test validate_dof_consistency(ScalarDOF(), Triangle{3}, Lagrange{1}) === nothing + + # Invalid cases: wrong number of DOFs (should throw ArgumentError) + @test_throws ArgumentError validate_dof_consistency( + VectorDOF{2}(), # 2 components per node × 3 nodes = 6 DOFs + Triangle{3}, # 3 nodes + Lagrange{1} # 3 basis functions + # Mismatch: 6 DOFs ≠ 3 basis functions + ) + + @test_throws ArgumentError validate_dof_consistency( + VectorDOF{3}(), # 3 components per node × 10 nodes = 30 DOFs + Tetrahedron{10}, # 10 nodes + Lagrange{2} # 10 basis functions + # Mismatch: 30 DOFs ≠ 10 basis functions + ) + + # Test error message content for one case + ex = try + validate_dof_consistency(VectorDOF{2}(), Triangle{3}, Lagrange{1}) + nothing + catch e + e + end + @test ex isa ArgumentError + @test occursin("3 basis functions", ex.msg) + @test occursin("6 DOFs", ex.msg) + @test occursin("Ciarlet triplet", ex.msg) +end + +@testset "nbasis consistency with get_basis_functions" begin + # Verify that nbasis returns the same count as actual basis functions + + using StaticArrays: SVector + using Tensors: Vec + + test_cases = [ + (Lagrange{1}, Triangle{3}, Vec(0.25, 0.25)), + (Lagrange{2}, Triangle{6}, Vec(1/3, 1/3)), + (Lagrange{1}, Tetrahedron{4}, Vec(0.25, 0.25, 0.25)), + (Lagrange{1}, Hexahedron{8}, Vec(0.0, 0.0, 0.0)), + ] + + for (basis_type, topo_type, xi) in test_cases + topo = topo_type() + basis = basis_type() + + # Get actual basis function count + N_actual = get_basis_functions(topo, basis, xi) + n_actual = length(N_actual) + + # Get count from nbasis + n_expected = nbasis(topo, basis) + + @test n_actual == n_expected + end +end