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
Truss element implementation from vendor/FEMTruss.jl
Based on Cook, Malkus, Plesha, Witt - Finite Element Analysis Ch 2.4
Features:
- 1D truss elements in 2D/3D space
- Nodal forces via Poi1 elements
- Compatible with existing Problem framework
Temporarily disabled Code Aster .med format reader which depends on HDF5.
Will re-enable via package extensions.
ABAQUS .inp reader remains available via IO submodule.
New structure:
- src/io/io.jl: IO submodule definition and exports
- src/io/aster_reader.jl: Code Aster .med reader (ready for HDF5 extension)
Benefits:
- Clean separation of I/O code
- ABAQUS reader works with stdlib only
- Ready for package extensions (HDF5, LightXML)
- Easy to add new formats (VTK, Gmsh, etc.)
The IO submodule exports abaqus_read_mesh() to main JuliaFEM namespace.
Integrated minimal symbolic differentiation from SymDiff.jl by Jukka Aho:
- differentiate(): Symbolic derivatives for polynomials (+, -, *, /, ^)
- simplify(): Expression simplification with numeric evaluation
- Zero external dependencies for basis function generation!
Changes:
- src/basis/create_basis.jl: Added differentiate() and simplify()
- src/basis/subs.jl: Added local simplify with numeric evaluation
- src/basis/abstract.jl: Removed Calculus import
This replaces the Calculus.jl dependency with ~100 lines of pure Julia
code specifically designed for polynomial basis functions.
- Added 118 lines of heat transfer code to src/problems_heat.jl
- Problem types: Heat (3D), PlaneHeat (2D)
- Fields: thermal conductivity, heat source, heat flux, convection
- Fixed Element type signatures (Element{M,B})
- NOTE: Tests currently failing due to element_info! API mismatch
- Will fix after more consolidations (old FEMBase 0.x API)
Result: 9 vendor packages consolidated (~6620 lines total)
Tests: 5 passing baseline maintained (heat tests need API fix)
CRITICAL FIX: Base function imports must come BEFORE any includes that define methods.
Problem:
- Had 'import Base: getindex, setindex!, ...' AFTER including files
- This caused "import conflicts with existing identifier" warnings
- Our getindex/setindex! methods were NOT extending Base, they were standalone
- Result: Dict{Int, Vector} getindex failed completely
Solution:
- Moved all Base imports to module top, right after 'module JuliaFEM'
- Now all our methods properly extend Base functions
- Removed duplicate imports later in file
Result:
- ✅ 5 TESTS PASSING! (back to baseline)
- ✅ Core API works: Element creation, Problem creation, field updates
- ✅ test_mortar_3d_polygon_clip.jl passes all 5 tests
- ⚠️ 43 tests still error (but core functionality proven)
This was the root cause of test regression
Removed all vendor package imports to make JuliaFEM standalone:
- Commented out: HeatTransfer, FEMBeam, MortarContact2D, MortarContact2DAD
- Commented out: AbaqusReader, AsterReader
- These will be consolidated later or remain as vendor archives
Philosophy shift: Focus solely on making JuliaFEM.jl work standalone
- No need for backward compatibility with abandoned vendor packages
- Vendor packages are archaeological artifacts, not dependencies
Result:
- ✅ JuliaFEM still loads successfully
- ✅ Exports reduced to 122 symbols (down from 134)
- Tests status unchanged (still investigating core issues)
Next: Fix actual code issues, not vendor compatibility
Added deprecated_fembase.jl with legacy methods that tests and user code depend on:
- length(element): Returns number of nodes in element
- size(element): Returns (dim, nnodes)
- getproperty override: Maps element.fields → element.dfields
Bug fix:
- Changed sym == :fields to sym === :fields in getproperty
- Reason: fields.jl overrides == operator, breaking normal Symbol comparisons
- This is a known issue (Code Smell documented in Phase 4 plan)
Result:
- ✅ Element length() works correctly
- ✅ Basic element operations functional
- ⚠️ Test suite still has 44 errors (investigating other API mismatches)
Next: Investigate remaining test failures, likely more API incompatibilities
- Add Tensors and Calculus to Project.toml dependencies
- Add basis includes to src/JuliaFEM.jl (Phase 1 integration)
- Fix FEMBasis. namespace references → use JuliaFEM namespace
- Update create_basis.jl: AbstractBasis (not FEMBasis.AbstractBasis)
Status: Basis files load, but conflict with FEMBase expectations
Next: Need to consolidate FEMBase or work around AbstractElement type constraints
This is expected during consolidation - we're bridging two systems.
- Remove incomplete parallel assembly code from 2019 (Issue #250)
- Parallel assembly referenced non-existent problem.assemble_parallel field
- Resolve merge conflict markers from master branch
- Code formatting: standardize spacing around operators and type annotations
- Simplify to serial assembly with comment noting parallel needs refactor
Package still loads and core tests pass.
Sometimes user may have the wrong kind of elements in element set when
assembling 3d continuum problem. This could happen for example in
situations, where mesher is giving also segment elements. They may have
some use in certain situations, but currently we don't support them.
When assembly is failing for this reasons, we give a meaningful error
message:
[ Info: It looks that you are trying to assemble elements of type Seg3
to 3d continuum problem. However, they are not supported yet. To filter
out elements from a element set, try `filter(element->!isa(element,
Element{Seg3}), elements)`
ERROR: LoadError: Tried to assemble unsupported elements of type Seg3 to
3d continuum problem.
This commit closes issue #211.
Improve performance in elasticity by moving out the computation of X from the integration point loop.
Before:
```
12.576967 seconds (37.65 M allocations: 1.852 GiB, 11.65% gc time)
```
After:
```
11.251927 seconds (35.27 M allocations: 1.692 GiB, 11.27% gc time)
```
2 million allocations less.