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

303 lines
7.4 KiB
Markdown
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.
---
title: "Migration Guide: Deprecated Basis Function API"
date: 2025-11-11
author: "JuliaFEM Team"
status: "Authoritative"
tags: ["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):**
```julia
# 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):**
```julia
# 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):**
```julia
N = eval_basis!(Lagrange{Tetrahedron,2}, Float64, xi)
dN = eval_dbasis!(Lagrange{Tetrahedron,2}, xi)
```
**NEW (recommended):**
```julia
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):**
```julia
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):**
```julia
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):**
```julia
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):**
```julia
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:
```bash
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:
```julia
# 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:
```julia
# 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:**
```julia
# This won't work!
N = get_basis_functions(Lagrange{Triangle,1}(), xi)
```
**Solution:**
```julia
# Topology and basis are separate!
N = get_basis_functions(Triangle(), Lagrange{1}(), xi)
```
### Issue 2: Integration Point Coordinates
**Problem:**
```julia
# Old IP struct had .coords field
xi = Vec(ip.coords...) # Tuple to Vec conversion
```
**Solution:**
```julia
# 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:
```julia
# 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!