diff --git a/src/basis/api.jl b/src/basis/api.jl index 5610a39..37be460 100644 --- a/src/basis/api.jl +++ b/src/basis/api.jl @@ -120,6 +120,79 @@ Equivalent to `get_basis_derivatives(topology, basis, xi)[i]`. return get_basis_derivatives(topology, basis, xi)[i] end +""" + nbasis(topology::AbstractTopology, basis::AbstractBasis) -> Int + +Return the number of basis functions for the given basis on the given topology. + +**Zero-cost:** This function compiles to a constant integer for concrete types. +The return value is compile-time known, enabling type-stable allocations and +constant propagation throughout assembly code. + +# Arguments +- `topology::AbstractTopology`: Element topology instance (e.g., `Triangle{3}()`, `Tetrahedron{10}()`) +- `basis::AbstractBasis`: Basis instance (e.g., `Lagrange{1}()`, `Lagrange{2}()`) + +# Returns +- `Int`: Number of basis functions (compile-time constant for generated bases) + +# Examples +```julia +# Standard nodal bases +nbasis(Triangle{3}(), Lagrange{1}()) # 3 (linear triangle) +nbasis(Triangle{6}(), Lagrange{2}()) # 6 (quadratic triangle) +nbasis(Tetrahedron{10}(), Lagrange{2}()) # 10 (quadratic tetrahedron) + +# Exotic elements (nodes ≠ basis functions) +nbasis(Triangle{3}(), DKT()) # 9 (3 nodes but 9 DOFs!) + +# Serendipity families +nbasis(Quadrilateral{8}(), Serendipity{2}()) # 8 (reduced quadratic quad) +``` + +# Implementation Notes + +For generated bases (Lagrange, Serendipity), `nbasis` functions are automatically +generated by `basis_generator.jl` alongside the basis function implementations. +This ensures consistency: the generator knows exactly how many basis functions +it produced, so `nbasis` always returns the correct value. + +For custom/special bases (DKT, hierarchical, etc.), implement manually: +```julia +@inline nbasis(::Triangle{3}, ::DKT) = 9 +``` + +For truly dynamic bases (adaptive refinement), use instance-based dispatch: +```julia +@inline nbasis(::K, basis::HierarchicalBasis) where {K<:AbstractTopology} = basis.active_modes +``` + +# Compile-Time Verification + +You can verify zero-cost compilation: +```julia +@code_llvm nbasis(Triangle{3}(), Lagrange{1}()) +# Should show: ret i64 3 (direct constant return) + +@code_native nbasis(Triangle{3}(), Lagrange{1}()) +# Should show: mov rax, 3; ret (single instruction) +``` + +# Usage in Validation + +The primary use case is validating the Ciarlet triplet (K, P, Σ): +```julia +function validate_dof_consistency(dof, K, P) + n_dofs = ndofs(dof, K) + n_basis = nbasis(K(), P()) # ← Zero-cost! + @assert n_dofs == n_basis "Inconsistent element: \$n_basis basis ≠ \$n_dofs DOFs" +end +``` + +See also: [`get_basis_functions`](@ref), [`validate_dof_consistency`](@ref) +""" +function nbasis end + # --------------------------------------------------------------------------- # Deprecated bridge (old API names) # --------------------------------------------------------------------------- @@ -140,25 +213,7 @@ Provided temporarily for migration. """ function eval_dbasis! end -# --------------------------------------------------------------------------- -# Degrees of freedom utility -# --------------------------------------------------------------------------- - -""" - ndofs(basis::AbstractBasis) - ndofs(::Type{<:AbstractBasis}) - -Total degrees of freedom for this basis. For standard nodal bases, this is -usually equal to the number of basis functions; specialized bases (e.g., plates) -can override to return multiple DOFs per node. -""" -function ndofs end - -# Default: basis implementations should override; no assumption about nnodes here. -ndofs(::AbstractBasis) = error("ndofs not implemented for this basis") -ndofs(::Type{<:AbstractBasis}) = error("ndofs not implemented for this basis type") - export AbstractBasis, Lagrange, Serendipity -export ndofs export get_basis_functions, get_basis_derivatives, get_basis_function, get_basis_derivative +export nbasis export eval_basis!, eval_dbasis!