- NodeToElementsMap: Inverse connectivity (node → elements touching it)
- ElementNodeInfo: Tracks element ID and local node index
- get_node_spider() finds all nodes coupling with given node
- NodalStiffnessContribution: Storage for 3×3 blocks per node
- matrix_vector_product_nodal() computes K_i*u at single node
- print_spider_info() debugging diagnostics
- 234 lines: Infrastructure for node-by-node assembly
- ElementAssemblyData: Global sparse matrix and force vectors
- ElementContribution: Local element contributions before scatter
- scatter_to_global!() adds element matrices to global system
- compute_residual!() calculates r = f_int - f_ext
- apply_dirichlet_bc!() penalty method for essential BCs
- get_dof_indices() node connectivity to global DOF mapping
- matrix_vector_product() sparse K*v multiplication
- 341 lines: Traditional element-by-element assembly infrastructure
- ElasticityDataCPU struct wraps ElementAssemblyData
- initialize_backend() assembles global system from immutable Elements
- compute_element_stiffness() uses Tensors.jl (blocked by get_basis_derivatives)
- cg_solve() implements Conjugate Gradient iterative solver
- Supports Dirichlet boundary conditions from Physics API
- 228 lines: Traditional element assembly approach for CPU
New file src/backend/abstract.jl defining backend abstraction:
- AbstractBackend base type for computation backend
- Auto() automatic backend selection (GPU if available, else CPU)
- GPU() force GPU backend (errors if CUDA unavailable)
- CPU(nthreads) force CPU backend with thread count
- select_backend() chooses concrete backend based on hardware
- AbstractElasticityData for backend-specific data structures
- ElasticitySolution struct for solve results
- solve!() dispatch point with backend parameter
- 241 lines with comprehensive API documentation
New file src/materials/finite_strain_plasticity.jl implementing J2 plasticity for large deformations:
- FiniteStrainPlasticityState storing F_p (plastic deformation gradient), α_bar (backstress), κ
- FiniteStrainPlasticity struct with E, ν, σ_y, H parameters
- Hyperelastic stress response using Neo-Hookean
- Exponential map integration for plastic flow
- Pull-back/push-forward operations for intermediate configuration
- Consistent algorithmic tangent for Newton convergence
- 293 lines with comprehensive finite deformation theory
New file src/materials/neo_hookean.jl implementing simplest hyperelasticity:
- NeoHookean struct with shear modulus μ and Lamé parameter λ
- Convenience constructor from E and ν engineering constants
- strain_energy() computes ψ = μ/2·(I₁-3) - μ·ln(J) + λ/2·ln²(J)
- Stress S = 2·∂ψ/∂C via automatic differentiation
- Tangent 𝔻 = 4·∂²ψ/∂C² via automatic differentiation
- Uses Tensors.jl built-in AD (no ForwardDiff dependency)
- Total Lagrangian formulation with 2nd PK stress
- 253 lines with comprehensive theory documentation
New file src/materials/abstract_material.jl defining material model architecture:
- AbstractMaterial base type for all materials
- AbstractElasticMaterial for stateless materials (no history)
- AbstractPlasticMaterial for stateful materials (plastic strain, etc.)
- compute_stress() interface: (material, ε, state_old, Δt) → (σ, 𝔻, state_new)
- State management convention for Newton iterations
- Thread-safe and GPU-compatible design principles
- 229 lines with comprehensive documentation and examples
New file src/physics_api.jl defining user-facing elasticity API:
- ElasticityPhysicsType (alias Elasticity) for problem configuration
- DirichletBC struct for prescribed displacements
- NeumannBC struct for surface tractions/pressures
- Physics{P} container for problem with elements and BCs
- Works with both CPU and GPU backends
- 183 lines with comprehensive examples
New file src/physics/deformation_gradient.jl:
- compute_deformation_gradient() computes F = I + ∇u at integration points
- StrainFormulation types: FiniteStrain() and SmallStrain()
- Uses Tensors.jl for all tensor operations (Vec, Tensor)
- Zero-allocation design with @inline functions
- GPU-ready immutable operations
- Comprehensive mathematical documentation with references
- 243 lines including commented high-level API for future integration
New file src/physics/assembly_helpers.jl with FEM assembly utilities:
- shape_function_gradients() computes ∇N in current configuration
- compute_strain_from_gradients() small strain ε = sym(∇u)
- compute_green_lagrange_strain() finite strain E = ½(C-I)
- accumulate_stiffness!() adds element stiffness contributions
- accumulate_internal_forces!() computes f_int = ∫σ·∇N dV
- accumulate_external_forces!() computes f_ext = ∫N·b dV
- Zero-allocation design with Tensors.jl Vec and SymmetricTensor
- 331 lines with comprehensive performance documentation
New file src/physics/abstract.jl defining physics system architecture:
- AbstractPhysics base type for all physics implementations
- get_unknown_field_name() returns primary field (displacement, temperature, etc.)
- get_formulation_type() returns :incremental, :total, or :rate
- get_unknown_field_dimension() returns DOFs per node
- assemble!() dispatch point for physics-specific assembly
- Comprehensive docstrings covering multi-physics coupling and GPU compatibility
- 138 lines documenting design philosophy and future extension
New file src/geometry/jacobian.jl implementing geometric transformations:
- compute_jacobian(X, dN_dξ) computes J = ∂x/∂ξ using tensor products
- physical_derivatives(J, dN_dξ) transforms derivatives to physical space
- Full Tensors.jl integration with Vec and Tensor types
- Zero-allocation tuple-based API for performance
- AbstractVector overloads for compatibility
- Comprehensive docstrings with 2D/3D examples
- 169 lines with mathematical definitions and usage patterns
Modified src/integration/gauss.jl to fix IntegrationPoint creation:
- Changed from generator expression to ntuple for proper type inference
- Collect quad_data first (was zip iterator, cannot be indexed)
- Remove explicit type parameter {D} - let Julia infer from arguments
- Fixes type stability issue in integration point generation
- Maintains zero-allocation design with tuple return
New file implementing Gauss quadrature point generation:
- get_gauss_points!(topology, scheme) returns tuple of (weight, Vec{D}) pairs
- Supports all 7 topologies: Segment, Triangle, Quadrilateral, Tetrahedron, Hexahedron, Wedge, Pyramid
- Orders 1-3 for each topology (exact integration up to quintic/cubic)
- Uses Tensors.jl Vec types for coordinates (GPU-friendly, zero-allocation)
- Fully inlined (@inline) for compile-time optimization
- 300 lines of quadrature rules from standard FEM references
Modified src/topology/topology.jl to reflect new architecture:
- Clarify topology defines geometric shape only, not node count
- Document that node count comes from basis functions
- Add examples showing same topology with different bases (Quad4/8/9)
- Update docstring to reference new topology types (Segment, Triangle, etc.)
- Emphasize corner nodes only in topology API
- Remove references to old node-count-baked types (Tri3, Quad4, etc.)
- 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)