diff --git a/docs/contributor/coding_standards.md b/docs/contributor/coding_standards.md new file mode 100644 index 0000000..c7c4428 --- /dev/null +++ b/docs/contributor/coding_standards.md @@ -0,0 +1,499 @@ +--- +title: "JuliaFEM Coding Standards" +description: "Required coding conventions and style guide for all contributors" +date: 2025-11-09 +author: "Jukka Aho" +categories: ["development", "standards", "style guide"] +keywords: ["juliafem", "coding standards", "style", "conventions", "best practices"] +audience: "developers" +level: "required" +type: "standards" +status: "active" +--- + +This document defines the coding standards and conventions for JuliaFEM development. + +**Last Updated:** November 9, 2025 +**Status:** Active - all new code must follow these standards + +--- + +## Core Principles + +1. **Readability over cleverness** - Code should be understandable by FEM practitioners +2. **Type stability first** - Performance depends on it (100x difference) +3. **Zero allocations in hot paths** - Profiling required +4. **Explicit over implicit** - No magic, show what happens +5. **Composition over inheritance** - Structs and free functions, not OOP + +--- + +## Variable Naming Conventions + +### No Greek Letters in Code (Critical!) + +**Rule:** Never use Greek letters (ξ, η, ζ, α, β, γ, etc.) in code. + +**Rationale:** + +- **Keyboard accessibility** - Not all keyboards support Greek input +- **Editor compatibility** - Some editors struggle with Unicode math symbols +- **Copy-paste issues** - Greek letters cause encoding problems +- **Search/replace problems** - Text tools may not handle Unicode correctly +- **Terminal rendering** - SSH sessions may not display correctly +- **Internationalization** - Non-Western keyboards make editing difficult +- **Git diffs** - Unicode can cause merge conflicts or display issues +- **Accessibility** - Screen readers struggle with Greek letters + +**Correct:** + +```julia +# Reference element coordinates +function eval_basis(u::Float64, v::Float64, w::Float64) + # Natural coordinates u, v, w ∈ [-1, 1] + N1 = (1 - u) * (1 - v) * (1 - w) / 8 + return N1 +end + +# Physical coordinates +function map_to_physical(x::Vec, y::Vec, z::Vec, u::Float64, v::Float64, w::Float64) + # Map from natural (u,v,w) to physical (x,y,z) +end +``` + +**Incorrect:** + +```julia +# ❌ DON'T DO THIS +function eval_basis(ξ::Float64, η::Float64, ζ::Float64) + N1 = (1 - ξ) * (1 - η) * (1 - ζ) / 8 + return N1 +end +``` + +**Exception:** Greek letters are acceptable in: + +- **Comments** - Mathematical notation for clarity: `# Shape function: Nᵢ(ξ)` +- **Documentation** - Latex math blocks: `$\\xi \\in [-1, 1]$` +- **String literals** - Plot labels: `xlabel="ξ coordinate"` +- **Error messages** - User-facing text: `"Invalid ξ coordinate"` + +**Standard variable names:** + +- Reference coordinates: `u`, `v`, `w` (not ξ, η, ζ) +- Physical coordinates: `x`, `y`, `z` +- Derivatives: `du`, `dv`, `dw` or `dudx`, `dudy`, etc. +- Jacobian: `J` or `jac` (not ∂) +- Determinant: `detJ` (not |J|) +- Inverse: `invJ` or `Jinv` (not J⁻¹) + +--- + +## Type Naming + +### Structs and Types + +- **PascalCase** - All type names: `Element`, `Problem`, `Material` +- **No abbreviations** - `Quadrilateral` not `Quad` (unless established convention) +- **Descriptive suffixes** - Purpose clear from name + +**Basis types** - Append "Basis" suffix to distinguish from topology: + +```julia +# ✅ Correct - No name collision +struct Tri3Basis <: AbstractBasis{2} end # Interpolation scheme +struct Tri3 <: AbstractTopology end # Element geometry + +# ❌ Wrong - Name collision! +struct Tri3 <: AbstractBasis{2} end +struct Tri3 <: AbstractTopology end # ERROR: type Tri3 already defined +``` + +### Functions + +- **snake_case** - All function names: `assemble_element`, `solve_static` +- **Verb-noun pattern** - Action clear: `compute_stiffness`, `evaluate_basis` +- **Boolean predicates** - `is_*` or `has_*`: `is_converged`, `has_contact` + +### Constants + +- **UPPER_CASE** - Module-level constants: `MAX_ITERATIONS`, `TOLERANCE` +- **Type-stable** - Always specify type: `const MAX_ITER::Int = 100` + +### Internal/Private + +- **Underscore prefix** - Not exported: `_compute_internal_forces` +- **Not API** - Can change between versions + +--- + +## Code Organization + +### File Structure + +```julia +# Standard file header +# This file is a part of JuliaFEM. +# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE + +""" +Brief one-line description of file purpose. + +Extended description if needed. +""" + +# Imports (grouped) +using LinearAlgebra +using SparseArrays + +# Local imports +using ..JuliaFEM: AbstractElement, AbstractBasis + +# Type definitions +struct MyType + # ... +end + +# Function implementations +function my_function(args) + # ... +end +``` + +### Import Style + +```julia +# ✅ Correct - Explicit imports +using LinearAlgebra: norm, dot, cross +using SparseArrays: sparse, spzeros + +# ❌ Avoid - Blanket imports (pollutes namespace) +using LinearAlgebra +using SparseArrays +``` + +--- + +## Performance Guidelines + +### Type Stability + +```julia +# ✅ Type-stable - Return type inferrable +function compute_mass(element::Quad4, density::Float64) + m::Float64 = 0.0 + # ... + return m +end + +# ❌ Type-unstable - Return type changes! +function compute_mass(element, density) + if density > 0 + return 1.0 # Float64 + else + return nothing # Nothing - type-unstable! + end +end +``` + +### Zero Allocations + +```julia +# ✅ Pre-allocated cache +struct AssemblyCache + K_local::Matrix{Float64} + f_local::Vector{Float64} +end + +function assemble!(cache::AssemblyCache, element) + fill!(cache.K_local, 0.0) + # Reuse cache.K_local - no allocations +end + +# ❌ Allocates every call +function assemble(element) + K_local = zeros(8, 8) # Allocates! + return K_local +end +``` + +### Tuple Returns (Zero Allocation) + +```julia +# ✅ Tuple return - no allocation +function eval_basis(element::Tri3, u::Float64, v::Float64) + N1 = 1 - u - v + N2 = u + N3 = v + return (N1, N2, N3) # NTuple{3,Float64} - stack allocated +end + +# ❌ Vector return - allocates! +function eval_basis(element::Tri3, u::Float64, v::Float64) + return [1 - u - v, u, v] # Vector{Float64} - heap allocation +end +``` + +--- + +## Documentation Style + +### Docstrings + +Use Julia's docstring format with standard sections: + +`````markdown +""" + assemble_element(element::Quad4, u::Vector{Float64}) -> Matrix{Float64} + +Assemble element stiffness matrix for 4-node quadrilateral element. + +# Arguments +- `element::Quad4`: Quadrilateral element with nodal connectivity +- `u::Vector{Float64}`: Nodal displacement vector (8 DOFs: u1,v1,u2,v2,...) + +# Returns +- `K::Matrix{Float64}`: 8×8 element stiffness matrix in global DOFs + +# Theory +Uses 2×2 Gauss quadrature with bilinear shape functions: +```math +K_{ij} = \\int_{\\Omega_e} B_i^T D B_j \\, d\\Omega +``` + +# Example +```julia +nodes = [Node(0,0), Node(1,0), Node(1,1), Node(0,1)] +element = Quad4(nodes) +u = zeros(8) +K = assemble_element(element, u) +``` + +# Performance +This function allocates. For zero-allocation assembly, use `assemble_element!` +with pre-allocated cache. +""" +function assemble_element(element::Quad4, u::Vector{Float64}) + # Implementation +end +````` + +### Comments + +```julia +# ✅ Good comments - Why, not what +# Use RCM ordering to minimize bandwidth (10x faster solve) +perm = rcm_permutation(mesh) + +# Check convergence: ||Δu|| < ε||u|| +# Relative norm prevents scale-dependent tolerance +if norm(Δu) < tol * norm(u) + break +end + +# ❌ Bad comments - Obvious from code +# Loop over elements +for element in elements + # Add to global matrix + K_global += K_local +end +``` + +--- + +## Testing Standards + +### Test Organization + +```julia +@testset "Quad4 element" begin + @testset "Stiffness matrix" begin + # Unit square, E=1, ν=0.3 + element = Quad4([Node(0,0), Node(1,0), Node(1,1), Node(0,1)]) + K = stiffness_matrix(element, E=1.0, ν=0.3) + + # Symmetry + @test issymmetric(K) + + # Positive definite (after BC) + @test all(eigvals(K[3:end, 3:end]) .> 0) + end + + @testset "Patch test" begin + # Linear displacement field must be exact + # ... validation test ... + end +end +``` + +### Floating Point Comparisons + +```julia +# ✅ Use tolerances +@test result ≈ expected atol=1e-10 +@test isapprox(result, expected, rtol=1e-6) + +# ❌ Never exact equality for floats +@test result == expected # Fragile! +``` + +--- + +## Anti-Patterns (Don't Do This!) + +### 1. Dict Without Type Parameters + +```julia +# ❌ Type-unstable Dict (100x slower!) +fields = Dict("displacement" => u, "velocity" => v) + +# ✅ Type-stable alternative +fields = (displacement=u, velocity=v) # NamedTuple +# or +struct Fields + displacement::Vector{Float64} + velocity::Vector{Float64} +end +``` + +### 2. Abstract Types in Structs + +```julia +# ❌ Type-unstable struct +struct Element + nodes::AbstractVector # Type-unstable! +end + +# ✅ Parametric struct +struct Element{N} + nodes::NTuple{N, Node} # Type-stable, zero-allocation +end +``` + +### 3. Global Variables + +```julia +# ❌ Global mutable state +global_stiffness = zeros(1000, 1000) + +function assemble!(element) + global_stiffness .+= K_local # Spooky action at a distance! +end + +# ✅ Explicit parameters +function assemble!(K_global::Matrix, element) + K_global .+= K_local # Clear data flow +end +``` + +### 4. Type Piracy + +```julia +# ❌ Extending methods on types you don't own +Base.+(a::Vector, b::Matrix) = ... # DON'T! + +# ✅ Wrapper type or different function +struct MyVector + data::Vector +end +Base.+(a::MyVector, b::Matrix) = ... # OK - our type +``` + +--- + +## Git Commit Style + +### Commit Messages + +```text +feat(topology): Add Pyr5 pyramid element topology + +- Implement 5-node pyramid reference element +- Add connectivity information (faces, edges) +- Zero-allocation tuple interface +- Tests: reference coordinates, edge/face queries + +Closes #123 +``` + +**Format:** `(): ` + +**Types:** + +- `feat`: New feature +- `fix`: Bug fix +- `docs`: Documentation only +- `refactor`: Code restructuring (no behavior change) +- `perf`: Performance improvement +- `test`: Adding tests +- `chore`: Tooling, dependencies + +**Scope:** Module or component (topology, assembly, solver, etc.) + +**Subject:** + +- Imperative mood: "Add feature" not "Added feature" +- No period at end +- Max 72 characters + +--- + +## Editor Configuration + +### Recommended Settings + +```julia +# .editorconfig +[*.jl] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true +insert_final_newline = true +``` + +### JuliaFormatter.jl + +```julia +# .JuliaFormatter.toml +indent = 4 +margin = 92 +always_for_in = true +whitespace_typedefs = true +whitespace_ops_in_indices = true +remove_extra_newlines = true +``` + +--- + +## Summary Checklist + +Before submitting code, verify: + +- [ ] No Greek letters in variable names (use u, v, w) +- [ ] All types are PascalCase +- [ ] All functions are snake_case +- [ ] Type-stable (check with `@code_warntype`) +- [ ] Zero allocations in hot paths (check with `@allocations` or `@btime`) +- [ ] Docstrings for exported functions +- [ ] Tests pass locally +- [ ] Comments explain "why" not "what" +- [ ] No type piracy +- [ ] No global mutable state + +--- + +## References + +- **Performance tips:** https://docs.julialang.org/en/v1/manual/performance-tips/ +- **Style guide:** https://docs.julialang.org/en/v1/manual/style-guide/ +- **JuliaFEM vision:** `llm/VISION_2.0.md` +- **Architecture:** `llm/ARCHITECTURE.md` +- **Technical lessons:** `llm/TECHNICAL_VISION.md` + +--- + +**Enforcement:** These standards are enforced through code review. All PRs must follow these conventions. + +**Evolution:** This document evolves with the project. Propose changes via pull request.