- New API: get_basis_functions() returns tuple of functions
- New API: get_basis_derivatives() returns tuple of gradient functions
- basis_api.jl: 210 lines implementing modern functional API
- Re-generated lagrange_generated.jl with 242 new lines
- abstract.jl: Add nnodes() method for Lagrange type
- Backward compatible: old eval_basis! API unchanged
- See ADR-003 for design rationale
- Implement update() that returns new element (immutable pattern)
- Supports keyword arguments for ergonomic field updates
- Preserves backward compatibility with update!() (legacy)
- Dual-API approach: modern immutable + legacy mutable both supported
- 82 lines including documentation and examples
- See docs/book/fundamentals_element_creation.md for usage guide
- Add new basis API exports: get_basis_functions, get_basis_derivatives
- Export both update() (immutable) and update!() (legacy)
- Re-enable assemble! and postprocess! exports
- Document new basis API with ADR-003 reference
- Include basis_api.jl for dual-API support (modern + legacy)
- Implement ElasticityPhysics struct with nodal assembly
- Two-phase assembly: element contributions then nodal accumulation
- Matrix-free CG solver using IterativeSolvers.jl
- Support for pressure boundary conditions
- Complete test: 190 nodes, 434 elements, converges in 430 iterations
- Max displacement 4.1 cm (cantilever beam validation)
- 476 lines including full documentation
Changes to src/elements/elements_lagrange.jl:
- Changed Poi1 from AbstractBasis{0} to AbstractBasis (non-parametric)
- Added nnodes(::Type{Poi1}) = 1 method
- Added nnodes(::Poi1) = 1 instance method
- Added comment explaining Poi1 as 0D point element
- Resolves type parameter mismatch with new AbstractBasis definition
Changes to src/JuliaFEM.jl:
- Uncommented problems_dirichlet.jl include and Dirichlet export (lines 288-289)
- Uncommented elements_lagrange.jl include (line 261)
- Uncommented aster_read_mesh export (line 340)
- Fixed indentation in jacobian function (spaces → consistent spacing)
- Fixed spacing in J_data array indexing (J_data[i,j] → J_data[i, j])
Purpose: Enable more problem types and mesh readers for testing
Rewrote test_elasticity_1d.jl to follow immutable element pattern.
This is the first fully working test with the new architecture!
Changes:
1. test/test_elasticity_1d.jl:
- Convert Dict node data to element-local tuple format
- Wrap data in DVTI field objects (Discrete, Variable, Time-Invariant)
- Create element with fields at construction: Element(Seg2, conn; fields=(...))
- Fix Jacobian shape expectation (3×1 not 1×3 for 1D in 3D)
2. src/JuliaFEM.jl:
- Add minimal jacobian() function for AbstractBasis (non-parametric)
- Handles embedding (1D element in 3D space) correctly
- Returns Matrix instead of Tensor for flexibility
3. src/elements/elements.jl:
- Fix Jacobian computation to handle both Tuple and IntegrationPoint
- Fix detJ calculation logic for embedded elements (check m not size(JT,2))
- Correctly handle 1D elements: detJ = ||∂X/∂ξ||
Result: test_elasticity_1d.jl passes! ✓
This validates the immutable architecture:
- Element created with fields at construction
- No mutation needed during test
- Field system integration working (DVTI fields)
- Jacobian computation working for embedded elements
Implements compatibility layer to allow old test code to run with new
immutable element design (though fields won't actually update).
src/elements/elements.jl:
- Replaced has_dfield/get_dfield to work with new fields API
- Fixed get_sfield/get_dfield to handle empty Tuple{} fields
- All dfield functions now map to element.fields (immutable NamedTuple)
src/topology/*.jl (seg2, tri3, quad4, tet4, hex8):
- Added nnodes() implementation for each topology type
- Returns corner node count (backwards compatibility)
- Example: nnodes(::Triangle) = 3, nnodes(::Hexahedron) = 8
- Note: Actual node count depends on basis degree in new architecture
Test Results:
- test_topology_standalone.jl: 36/36 tests passing ✓
- Full test suite: 43 errors (same as before)
- Error breakdown:
* 40+ tests: Problem types not defined (Elasticity, Heat, Mortar)
* 2 tests: Mesh readers not defined (aster_read_mesh)
* 1 test: Tries to mutate empty element (test_elasticity_1d)
Next Steps:
- Tests that create empty elements then mutate need rewriting
- Pattern: Element(Seg2, (1,2)) + update!() → not compatible
- New pattern: Element(..., fields=(geometry=X, displacement=u))
- See docs/design/IMMUTABILITY.md for migration guide
Generated by: julia --project=. src/basis/lagrange_generator.jl
Changes:
- All 15 element types now use Lagrange{T,P} parametric type
- Functions: get_reference_element_coordinates(), eval_basis!(), eval_dbasis!()
- Reference coordinates now return tuples (zero-allocation)
- Removed old Seg2Basis, Tri3Basis, Quad4Basis, etc. struct definitions
- All methods work with both Type{Lagrange{T,P}} and Lagrange{T,P} instances
Validated:
- Triangle: Kronecker delta property holds (N_i(x_j) = δ_ij)
- Quadrilateral, Tetrahedron, Hexahedron: First node evaluates to (1,0,0,...)
- Derivatives: Correct gradients at reference coordinates
- Comment out Base.getproperty(element::Element, :fields) redirection
- Old code redirected element.fields → element.dfields (Dict-based fields)
- New Element has fields::F directly (type-stable NamedTuple or struct)
- No redirection needed with new architecture
- Rationale: New Element{N,NIP,F,B} has fields as direct struct member
- Commented out assemble_mass_matrix! specialization for Element{Tet10}
- Tet10 is now a topology type, not a basis type (name conflict)
- Needs refactoring to use Tet10Basis or new parametric architecture
- Fixed code formatting (spacing around operators, indentation)
- Added TODO comment explaining the issue
- Removed nnodes from topology exports (now in basis module)
- Added exports for new topology names (Triangle, Quadrilateral, etc.)
- Kept old names as exports (they're aliases for backwards compatibility)
- Added explanatory comments about topology vs basis separation
- Added examples showing how node count comes from basis now
- Organized exports by dimension (0D/1D/2D/3D)
- Commented out topology_to_basis() helper and old Element constructors
- These mapped old names (Tri3→Tri3Basis) which no longer exist
- Need to update for new Lagrange{Topology, P} parametric architecture
- Added TODO comment explaining migration needed
- Temporary measure until new Element constructors are implemented
- Changed struct name from Wedge6 to Wedge
- Removed nnodes() method (node count now determined by basis)
- Updated all function signatures to use Wedge
- Added Wedge6 as backwards compatibility alias
- Added note explaining basis determines node count (P1=6, P2=15 nodes)
- Changed struct name from Pyr5 to Pyramid
- Removed nnodes() method (node count now determined by basis)
- Updated all function signatures to use Pyramid
- Added Pyr5 as backwards compatibility alias
- Added note explaining basis determines node count (P1=5, P2=13, P3=29)
- Changed struct name from Hex8 to Hexahedron
- Removed nnodes() method (node count now determined by basis)
- Updated all function signatures to use Hexahedron
- Added Hex8 as backwards compatibility alias
- Added note explaining basis determines node count (Q1=8, Q2=27 nodes)
- Changed struct name from Tet4 to Tetrahedron
- Removed nnodes() method (node count now determined by basis)
- Updated all function signatures to use Tetrahedron
- Added Tet4 as backwards compatibility alias
- Added note explaining basis determines node count (P1=4, P2=10 nodes)
- Changed struct name from Seg2 to Segment
- Removed nnodes() method (node count now determined by basis)
- Updated all function signatures to use Segment instead of Seg2
- Added Seg2 as backwards compatibility alias
- Added note explaining basis determines node count
- Changed struct name from Quad4 to Quadrilateral
- Removed nnodes() method (node count now determined by basis)
- Updated documentation to explain topology vs basis separation
- Added examples showing Lagrange and Serendipity differences
- Added Quad4 as deprecated alias for backwards compatibility
- Clarified that topology defines geometry only, basis determines nodes
- Changed struct name from Tri3 to Triangle
- Removed nnodes() method (node count now determined by basis)
- Updated documentation to explain topology vs basis separation
- Added examples showing Lagrange{Triangle, P} for different degrees
- Added Tri3 as deprecated alias for backwards compatibility
- Clarified that topology defines geometry only, basis determines nodes
Consolidates scripts/generate_lagrange_basis.jl into src/basis/lagrange_generator.jl
Changes:
- Added Vecish type alias handling for standalone/included execution
- Added vandermonde_matrix() function (~40 lines) for polynomial basis construction
- Added ElementDescription struct with keyword constructor for readability
- Added 15 element definitions with reference coordinates and polynomial ansatz:
* 1D: Seg2, Seg3
* 2D triangles: Tri3, Tri6
* 2D quads: Quad4, Quad8, Quad9
* 3D tets: Tet4, Tet10
* 3D hexes: Hex8, Hex20, Hex27
* 3D pyramid: Pyr5
* 3D wedges: Wedge6, Wedge15
- Added generation script block (~550 lines) that runs when file executed directly
- Generator now appends "Basis" suffix to all types (Tri3Basis, Quad4Basis, etc.)
- Outputs to src/basis/lagrange_generated.jl with clean formatting
- Includes progress reporting and next steps guidance
Total: 254 → 813 lines (+559 lines)
Run as: julia --project=. src/basis/lagrange_generator.jl
**Added Gauss quadrature mappings for all 17 topology types**
Extended src/integration/gauss.jl to support all element types from 1D to 3D,
both linear and quadratic variants.
## Integration Rule Mappings
### 1D Segments (Seg2, Seg3)
- Tensor product rules: GLSEG1, GLSEG2, GLSEG3, GLSEG4, GLSEG5
- Support for Gauss{1} through Gauss{5}
### 2D Triangles (Tri3, Tri6, Tri7)
- Dedicated triangular rules: GLTRI1, GLTRI3, GLTRI4, GLTRI6, GLTRI7, GLTRI12
- Support for Gauss{1}, Gauss{3}, Gauss{4}, Gauss{6}, Gauss{7}, Gauss{12}
- Same rules used for linear (Tri3) and quadratic (Tri6, Tri7) topologies
### 2D Quadrilaterals (Quad4, Quad8, Quad9)
- Tensor product rules: GLQUAD1, GLQUAD4, GLQUAD9, GLQUAD16, GLQUAD25
- Support for Gauss{1} through Gauss{5}
- Same rules for linear (Quad4) and quadratic (Quad8, Quad9) variants
### 3D Tetrahedra (Tet4, Tet10)
- Dedicated tetrahedral rules: GLTET1, GLTET4, GLTET5, GLTET15
- Support for Gauss{1}, Gauss{4}, Gauss{5}, Gauss{15}
### 3D Hexahedra (Hex8, Hex20, Hex27)
- Tensor product rules: GLHEX1, GLHEX8, GLHEX27, GLHEX64, GLHEX125
- Support for Gauss{1} through Gauss{5}
- Same rules for linear (Hex8) and quadratic (Hex20, Hex27) variants
### 3D Wedges/Prisms (Wedge6, Wedge15)
- Dedicated wedge rules: GLWED6, GLWED21
- Support for Gauss{6}, Gauss{21}
### 3D Pyramids (Pyr5)
- Dedicated pyramid rules: GLPYR5
- Support for Gauss{5}
## Design Notes
**Quadrature rules from src/quadrature/**
All actual integration point data comes from src/quadrature/*.jl files
(consolidated from FEMQuad.jl). This file just maps high-level scheme + topology
to the appropriate low-level rule name.
**Tensor product elements:**
Segments, quads, and hexes use tensor product quadrature generated programmatically
in glquad.jl. Number follows pattern: N_points = N_per_dim^dimension
- GLSEG3 = 3 points in 1D
- GLQUAD9 = 3² = 9 points in 2D
- GLHEX27 = 3³ = 27 points in 3D
**Simplex elements:**
Triangles, tetrahedra use specialized rules (not tensor products) with optimized
point locations. Number roughly indicates integration order capability.
**Quadratic elements use same rules:**
Quadratic variants (Tri6, Quad8, Hex20, etc.) use same quadrature rules as
linear counterparts. User selects integration order via Gauss{N} parameter,
not topology type. Higher order topologies typically need higher N for exact
integration.
**Zero-allocation maintained:**
All functions return tuples, no heap allocation in integration point queries.
## Usage Examples
```julia
# Linear triangle with 1-point rule
ips = integration_points(Gauss{1}(), Tri3())
# Quadratic triangle with 6-point rule (more accurate)
ips = integration_points(Gauss{6}(), Tri6())
# Linear hex with 8-point rule (2³)
ips = integration_points(Gauss{2}(), Hex8())
# Quadratic hex with 27-point rule (3³)
ips = integration_points(Gauss{3}(), Hex27())
```
## Completeness
✅ All 17 topology types now supported
✅ Linear and quadratic variants covered
✅ 1D, 2D, and 3D elements complete
✅ Zero-allocation design maintained
## References
- src/quadrature/glquad.jl (tensor product generation)
- src/quadrature/gltri.jl (triangle rules)
- src/quadrature/gltet.jl (tetrahedron rules)
- src/quadrature/glwed.jl (wedge rules)
- src/quadrature/glpyr.jl (pyramid rules)
- Dunavant, "High degree efficient symmetrical Gaussian quadrature rules for the triangle"
- Abramowitz & Stegun, "Handbook of Mathematical Functions"
**Problem:** CI documentation build failing with:
UndefVarError: `create_basis_and_eval` not defined
**Root Cause:**
- Commented out include("basis/lagrange_generator.jl")
- But existing lagrange_*.jl files still use create_basis_and_eval()
- Generator must be loaded at runtime (for now)
**Solution:**
- Re-enable include("basis/lagrange_generator.jl")
- Add TODO comment explaining this is temporary
- Once we generate lagrange_generated.jl, we can:
1. Remove old lagrange_*.jl includes
2. Include lagrange_generated.jl instead
3. Remove lagrange_generator.jl from runtime
**Status:**
- ✓ Package loads successfully
- ✓ Should fix CI documentation build
**Next Steps:**
1. Run scripts/generate_lagrange_basis.jl
2. Create src/basis/lagrange_generated.jl
3. Replace dynamic includes with static generated file
4. Remove __precompile__(false) completely
**Three Manuals for Three Audiences:**
1. **User Manual** (docs/user/) - "Just Get It Done"
- For end users, engineers, students
- Simple, practical, step-by-step
- Quick start, tutorials, examples, troubleshooting
- Philosophy: Show me how to solve my problem
2. **Contributor Manual** (docs/contributor/) - "Show Me the Code"
- For developers, contributors, advanced users
- Technical, detailed, design rationale
- Testing, architecture, performance, CI/CD
- Philosophy: Explain HOW and WHY
3. **The JuliaFEM Book** (docs/book/) - "Let Me Show You How I Think"
- For researchers, theory nerds, and Jukka
- Comprehensive, educational, opinionated, personal
- Math foundations, design philosophy, history, research
- Philosophy: Mix theory, code, and personal experience
**Reorganization:**
- Moved: TESTING_PHILOSOPHY.md → contributor/testing_philosophy.md
- Moved: STATUS.md → contributor/status.md
- Moved: TEST_FIXES_NEEDED.md → contributor/test_fixes_needed.md
- Moved: lagrange_basis_functions.md → book/lagrange_basis_functions.md
- Moved: benchmarks/ → book/benchmarks/
- Created: docs/README.md (main index explaining structure)
- Created: README.md in each section explaining audience and contents
- Updated: All references in scripts and source files
**Naming:** All docs now lowercase (testing_philosophy not TESTING_PHILOSOPHY)
**Benefits:**
- Clear separation of concerns
- Users don't get overwhelmed with implementation details
- Contributors get technical depth
- Book preserves deep theory and personal insights
- Each manual optimized for its audience
**Next:** Populate each section with appropriate content
- Moved docs/theory/lagrange_basis_functions.md → src/lagrange_basis_functions.md
- Updated all references in scripts and source files
- Using lowercase for consistency (no uppercase in filenames)
- Documentation now under src/ for automated doc generation
Rationale: Documentation should be close to implementation and follow
consistent naming conventions (lowercase).
Changed Point from 'mutable struct' to 'struct'.
The Dict for fields remains a reference type, so field updates via setindex!
and update! still work correctly. This change improves type stability and
enables better compiler optimizations.
Benefits:
- Better compiler optimizations (immutable types)
- Type stability improvements
- Stack allocation when possible
- No breaking changes (Dict fields still mutable)
Tests: All 157 tests passing
MAJOR PERFORMANCE REFACTORING:
1. Shape functions return tuples instead of allocating vectors:
- eval_basis!(): Returns NTuple{N,T} directly (zero allocations)
- eval_dbasis!(): Returns NTuple{N,Vec{D}} directly (zero allocations)
- API boundary (get_basis/get_dbasis) still returns vectors for compat
2. Element is now immutable with compile-time known structure:
- connectivity: Vector{UInt} → NTuple{N,UInt}
- integration_points: Vector{IP} → NTuple{NIP,IP}
- Element{N,NIP,M,B} parametrized by connectivity/IP count
- Changed from 'mutable struct' to 'struct'
3. Helper function for immutability:
- with_integration_points(element, ips) returns new element
- get_integration_points() returns tuple directly
Benefits:
- Zero allocations in hot paths (basis evaluation)
- Compile-time sizes enable better optimization
- Type stability improvements
- Stack allocation instead of heap
Breaking changes:
- Element.connectivity is now tuple (use collect() for vector)
- Element is immutable (use with_integration_points for updates)
Tests: All 157 tests passing
Gmsh returns node and element IDs as UInt64, so we should use unsigned
integers consistently throughout JuliaFEM to avoid unnecessary conversions.
Changes:
- Point.id: Int → UInt
- Element.id: Int → UInt
- Element.connectivity: Vector{Int} → Vector{UInt}
- Element constructors: Accept Integer (converts to UInt internally)
- Default element_id: -1 → 0 (UInt has no negative values)
Benefits:
- Direct compatibility with Gmsh.jl (no Int/UInt conversions)
- Semantically correct (node/element IDs are never negative)
- Slightly more efficient (no sign checks)
Tests: All 156 tests passing
Closes#267
Major architectural decision: Use Tensors.jl consistently everywhere
for geometric vectors, integration points, and coordinates.
Changes to src/elements/elements.jl:
- get_basis(): Convert ip to Vec, use Vector (not Matrix) for eval_basis!
- get_dbasis(): Convert ip to Vec
- jacobian evaluation: Convert geometry and ip.coords to Vec properly
- Handle both raw coordinates (Tuple) and IP struct transparently
New Tutorial 3: Numerical Integration and Jacobian (49 tests)
- Integration point structure and weights
- Jacobian determinant and matrix evaluation
- Numerical integration (constant, linear, quadratic functions)
- Multiple element types (Quad4, Seg2, Tri3)
Tests: 107 → 156 passing (49 new)
Runtime: ~7 seconds
Closes architectural standardization on Tensors.jl.
Related to Issue #250 (merge conflict resolution).
Why Tensors.jl:
- Type stability (100× performance vs Dict-based)
- Material science compatibility (stress tensors)
- Zero-cost abstractions
- Consistent API across all geometric calculations
Current state discovery:
- Element basis function evaluation broken (eval_basis! signature mismatch)
- Field interpolation at integration points broken (same root cause)
- Jacobian evaluation at integration points broken
- These are fundamental API issues affecting multiple test paths
Impact:
- Tutorial 3 (basis functions) deferred until API fixed
- Affects any code trying to evaluate fields at integration points
- Related to Quad4 assembly issues discovered in Tutorial 4
Working tutorials (107/107 tests passing):
- Tutorial 1: Element creation (5 tests)
- Tutorial 2: Gmsh mesh reading (72 tests)
- Tutorial 4: 1-element validation (35 tests)
Next: Focus on tutorials using working APIs only
Infrastructure improvements:
1. GitHub Actions CI workflow:
- Test on Julia 1.10 (LTS) and latest stable
- Ubuntu Linux runner
- Code coverage via Codecov
2. Documentation build workflow:
- Builds on push to master/main and PRs
- Uses Documenter.jl with GitHub Pages deployment
3. Fix missing exports for tests:
- Add Statistics to test dependencies
Tests still have API mismatches (49 failures) but infrastructure is now