Files
JuliaFEM.jl/docs/book/migration-guide-basis-api.md
T
Jukka Aho 6a94155a32 docs(book): Add basis function API migration guide
- get_basis_functions() and get_basis_derivatives() recommended
- Separation of concerns: topology and basis as separate parameters
- Side-by-side examples for all common element types
- Complete assembly example showing migration path
- Type-stable implementation with no performance penalty
- 302 lines: Comprehensive migration documentation
2025-11-12 01:07:24 +02:00

7.4 KiB
Raw Blame History

title, date, author, status, tags
title date author status tags
Migration Guide: Deprecated Basis Function API 2025-11-11 JuliaFEM Team Authoritative
migration
deprecation
basis-functions
api

Overview

The old basis function API (eval_basis! and eval_dbasis!) is DEPRECATED and will be removed in a future release. This guide helps you migrate to the new, recommended API.

Summary of Changes

Old API (DEPRECATED) New API (RECOMMENDED)
eval_basis!(Lagrange{Triangle,1}, Float64, ξ) get_basis_functions(Triangle(), Lagrange{1}(), ξ)
eval_dbasis!(Lagrange{Triangle,1}, ξ) get_basis_derivatives(Triangle(), Lagrange{1}(), ξ)

Why the Change?

Key Improvements:

  1. Separation of concerns - Topology and basis are separate parameters
  2. Clearer naming - get_basis_functions vs eval_basis! (no mutation despite !)
  3. Type parameter simplification - No redundant Float64 parameter
  4. Consistent API - Same pattern across all basis evaluation functions

Migration Examples

Example 1: Triangle Linear Element

OLD (deprecated):

# Element with Lagrange{Triangle,1} basis
xi = Vec(0.25, 0.25)

N = eval_basis!(Lagrange{Triangle,1}, Float64, xi)
dN = eval_dbasis!(Lagrange{Triangle,1}, xi)

NEW (recommended):

# Topology and basis passed separately
topology = Triangle()
basis = Lagrange{1}()
xi = Vec(0.25, 0.25)

N = get_basis_functions(topology, basis, xi)
dN = get_basis_derivatives(topology, basis, xi)

Example 2: Tetrahedron Quadratic Element

OLD (deprecated):

N = eval_basis!(Lagrange{Tetrahedron,2}, Float64, xi)
dN = eval_dbasis!(Lagrange{Tetrahedron,2}, xi)

NEW (recommended):

N = get_basis_functions(Tetrahedron(), Lagrange{2}(), xi)
dN = get_basis_derivatives(Tetrahedron(), Lagrange{2}(), xi)

Example 3: Assembly Loop with Integration Points

OLD (deprecated):

for (w, xi) in get_gauss_points!(Triangle, Gauss{2})
    N = eval_basis!(Lagrange{Triangle,1}, Float64, xi)
    dN = eval_dbasis!(Lagrange{Triangle,1}, xi)
    
    # Assembly...
end

NEW (recommended):

topology = Triangle()
basis = Lagrange{1}()

for (w, xi) in get_gauss_points!(Triangle, Gauss{2})
    N = get_basis_functions(topology, basis, xi)
    dN = get_basis_derivatives(topology, basis, xi)
    
    # Assembly...
end

Note: topology and basis are type-stable constants, so there's no performance penalty from creating them outside the loop.

Complete Assembly Example

OLD (deprecated):

function assemble_element_old(element::Element)
    K_local = zeros(9, 9)
    
    for ip in get_integration_points(element)
        w = ip.weight
        xi = Vec(ip.coords...)
        
        # DEPRECATED API
        N = eval_basis!(Lagrange{Triangle,2}, Float64, xi)
        dN = eval_dbasis!(Lagrange{Triangle,2}, xi)
        
        # Jacobian
        J = sum(dN[i]  X[i] for i in 1:6)
        detJ = det(J)
        invJ = inv(J)
        
        # Physical derivatives
        dN_phys = tuple((invJ  dN[i] for i in 1:6)...)
        
        # Accumulate stiffness
        for i in 1:6, j in 1:6
            K_local[i,j] += w * detJ * dot(dN_phys[i], dN_phys[j])
        end
    end
    
    return K_local
end

NEW (recommended):

function assemble_element_new(element::Element)
    K_local = zeros(9, 9)
    
    # Define topology and basis (type-stable constants)
    topology = Triangle()
    basis = Lagrange{2}()
    
    # NEW: Zero-allocation integration points
    for (w, ξ) in get_gauss_points!(Triangle, Gauss{2})
        # NEW API: Separate topology and basis
        N = get_basis_functions(topology, basis, ξ)
        dN = get_basis_derivatives(topology, basis, ξ)
        
        # Jacobian (using new tuple-based API)
        J = sum(dN[i]  X[i] for i in 1:6)
        detJ = det(J)
        invJ = inv(J)
        
        # Physical derivatives
        dN_phys = tuple((invJ  dN[i] for i in 1:6)...)
        
        # Accumulate stiffness
        for i in 1:6, j in 1:6
            K_local[i,j] += w * detJ * dot(dN_phys[i], dN_phys[j])
        end
    end
    
    return K_local
end

Performance: The new version is ~50× faster due to:

  • Zero-allocation integration points
  • Type-stable basis evaluation
  • Compile-time optimizations

Topology Types Reference

Old Basis Type New Topology New Basis
Lagrange{Segment,1} Segment() Lagrange{1}()
Lagrange{Triangle,1} Triangle() Lagrange{1}()
Lagrange{Triangle,2} Triangle() Lagrange{2}()
Lagrange{Quadrilateral,1} Quadrilateral() Lagrange{1}()
Lagrange{Quadrilateral,2} Quadrilateral() Lagrange{2}()
Lagrange{Tetrahedron,1} Tetrahedron() Lagrange{1}()
Lagrange{Tetrahedron,2} Tetrahedron() Lagrange{2}()
Lagrange{Hexahedron,1} Hexahedron() Lagrange{1}()
Lagrange{Hexahedron,2} Hexahedron() Lagrange{2}()
Lagrange{Wedge,1} Wedge() Lagrange{1}()
Lagrange{Pyramid,1} Pyramid() Lagrange{1}()

Migration Checklist

Step 1: Find All Uses of Old API

Search your codebase for:

grep -r "eval_basis!" src/
grep -r "eval_dbasis!" src/

Step 2: Update Function Calls

For each occurrence:

  1. Identify the topology (e.g., Triangle, Tetrahedron)

  2. Identify the polynomial order (e.g., 1, 2)

  3. Replace with new API:

    # OLD: eval_basis!(Lagrange{Triangle,1}, Float64, xi)
    # NEW: get_basis_functions(Triangle(), Lagrange{1}(), xi)
    

Step 3: Update Integration Loops

If using old integration point API:

# OLD: for ip in get_integration_points(element)
# NEW: for (w, ξ) in get_gauss_points!(Triangle, Gauss{2})

Step 4: Test

Run your tests to ensure:

  • All functionality works
  • Performance is maintained or improved
  • No deprecation warnings appear

Common Issues

Issue 1: Type Parameter Confusion

Problem:

# This won't work!
N = get_basis_functions(Lagrange{Triangle,1}(), xi)

Solution:

# Topology and basis are separate!
N = get_basis_functions(Triangle(), Lagrange{1}(), xi)

Issue 2: Integration Point Coordinates

Problem:

# Old IP struct had .coords field
xi = Vec(ip.coords...)  # Tuple to Vec conversion

Solution:

# New API returns Vec{D} directly
for (w, ξ) in get_gauss_points!(Triangle, Gauss{2})
    # ξ is already Vec{2}!

Issue 3: Performance Regression

Problem: New code is slower than old code.

Solution: Make sure you're using the new integration points API:

# WRONG (old API, allocates):
for ip in get_integration_points(element)
    ...

# RIGHT (new API, zero allocation):
for (w, ξ) in get_gauss_points!(Triangle, Gauss{2})
    ...

Further Reading

  • ADR-002: Basis Function API Design
  • ADR-004: Integration Points API Design
  • Performance Benchmarks: benchmarks/basis_function_access_patterns.jl
  • Tests: test/test_integration_points_api.jl

Support

If you encounter issues during migration:

  1. Check this guide for common issues
  2. Review the ADR documents for design rationale
  3. Open an issue on GitHub with a minimal example

Timeline

  • v0.6.0: Deprecation warnings added
  • v0.7.0: Old API will be removed (planned)

Migrate now to avoid breaking changes in future releases!