docs: Move user manual to docs/src/user/

- Relocate docs/user/ to docs/src/user/
- Contains user-facing documentation:
  - README.md (user manual index)
  - system_architecture.md (system overview)
- Part of three-tier docs reorganization following Documenter.jl standard
- Completes migration to docs/src/ structure
This commit is contained in:
Jukka Aho
2025-11-10 22:22:00 +02:00
parent fb732efd1b
commit 8c1be1b5a8
2 changed files with 574 additions and 0 deletions
+52
View File
@@ -0,0 +1,52 @@
---
title: "JuliaFEM User Manual"
description: "Quick start guide and practical tutorials for end users"
date: 2025-11-09
author: "Jukka Aho"
categories: ["user guide", "tutorial"]
keywords: ["juliafem", "quick start", "tutorial", "examples"]
audience: "end users"
level: "beginner"
type: "manual"
---
# JuliaFEM User Manual
**Audience:** End users, engineers, students who want to run simulations and get results.
This manual is designed to be **simple and practical** - get you from zero to running simulations as quickly as possible.
## What's Here
- **Quick Start:** Installation and first simulation in 5 minutes
- **Tutorials:** Step-by-step guides for common problems
- **Examples:** Pre-built simulations you can run and modify
- **API Reference:** Function documentation (what does this do?)
- **Troubleshooting:** Common errors and how to fix them
## What's NOT Here
- Deep theory (see `docs/book/` for that)
- How to contribute code (see `docs/contributor/` for that)
- Internal architecture details
## Philosophy
**"Just show me how to solve my problem."**
We assume you:
- Have a problem to solve (heat transfer, elasticity, contact)
- Want working code, not lectures
- Will read theory when YOU need it, not when WE think you should
## Getting Help
1. Start with the Quick Start
2. Find an example similar to your problem
3. Modify it to fit your needs
4. If stuck, check Troubleshooting
5. Still stuck? Ask on GitHub Discussions
---
**Next:** Start with [Quick Start](quickstart.md) or browse [Examples](../examples/)
+574
View File
@@ -0,0 +1,574 @@
---
title: "JuliaFEM System Architecture: Core Concepts"
date: 2025-11-10
author: "JuliaFEM Team"
status: "Authoritative"
last_updated: 2025-11-10
tags: ["architecture", "design", "concepts", "user-guide"]
---
## Introduction
This document explains **why** JuliaFEM's core data structures exist and **how**
they work together. Every struct has a purpose, every abstraction has a reason.
**Target audience:** Users who want to understand the system deeply,
contributors implementing new physics, anyone asking "why is it designed this
way?"
---
## The Big Picture: Method of Lines
JuliaFEM follows the **Method of Lines** approach to FEM:
1. **Spatial discretization** (FEM) → System of ODEs
2. **Time discretization** (if transient) → Nonlinear algebraic equations
3. **Linearization** (Newton) → Linear system solve
4. **Repeat** until convergence
**Key insight:** Separate concerns cleanly at each level!
```text
Mesh + Physics → Element Assembly → Global System → Solver → Solution
↓ ↓ ↓ ↓ ↓
Geometry Material Models Sparse Matrix Krylov u(x,t)
```
---
## Core Abstractions
### 1. `Element` - The Geometric Container
**Role:** Holds geometric and connectivity information for a single finite element.
**What it knows:**
- Element type (Tri3, Quad4, Tet10, etc.)
- Node connectivity
- Basis functions (shape functions)
- Integration points
**What it does NOT know:**
- Physics equations
- Material properties
- Boundary conditions
**Why this design?**
- ✅ Element is **reusable** across different physics (same Tet10 for elasticity, heat, fluid)
- ✅ Geometry is **immutable** (connectivity doesn't change during analysis)
- ✅ **Type-stable dispatch** on element type enables compiler optimizations
**Example:**
```julia
# Create a 10-node tetrahedral element
nodes = [1, 5, 12, 23, 14, 8, 19, 27, 31, 16]
element = Element(Tet10, nodes)
# Element knows its topology
@assert nnodes(element) == 10
@assert dim(element) == 3
# But element doesn't know about stress, temperature, etc.
# That's the job of Physics!
```
---
### 2. `Physics` - The Equation Selector (RENAMED from "Problem")
**Role:** Multiple dispatch tag + configuration for physical equations.
**What it is:**
- A **type** that selects assembly methods via multiple dispatch
- A **struct** that holds physics-specific configuration
- A **name provider** for field names ("displacement", "temperature", etc.)
**What it is NOT:**
- ❌ Not the mesh (that's separate)
- ❌ Not the material models (those are parameters)
- ❌ Not the solver (that's a different layer)
**Why "Physics" instead of "Problem"?**
- ✅ **Positive connotation** ("solve physics" vs "solve problem")
- ✅ **Accurate description** (selecting physical equations)
- ✅ **Clear role** (what physics are we simulating?)
**Types of Physics:**
```julia
# Elasticity: Solves ∇⋅σ = ρü + b
struct ElasticityPhysics <: AbstractPhysics
formulation::Symbol # :plane_stress, :plane_strain, :continuum
finite_strain::Bool # Geometric nonlinearity
geometric_stiffness::Bool # σ-dependent stiffness for buckling
store_fields::Vector{Symbol} # Output fields to save
end
# Heat transfer: Solves ∇⋅(k∇T) = ρcₚ∂T/∂t + Q
struct HeatPhysics <: AbstractPhysics
formulation::Symbol # :steady_state, :transient
nonlinear::Bool # Temperature-dependent properties
store_fields::Vector{Symbol}
end
# Contact mechanics: Solves contact constraints
struct ContactPhysics <: AbstractPhysics
algorithm::Symbol # :penalty, :lagrange, :augmented_lagrange
friction_model::Symbol # :coulomb, :frictionless
# ...
end
```
**Multiple Dispatch in Action:**
```julia
# Compiler selects correct assembly method based on physics type!
function assemble!(assembly, physics::ElasticityPhysics, elements, time)
# Elasticity-specific assembly:
# - Compute strain from displacement
# - Call material model: ε → (σ, 𝔻)
# - Build stiffness matrix K and force vector f
end
function assemble!(assembly, physics::HeatPhysics, elements, time)
# Heat transfer-specific assembly:
# - Compute temperature gradient
# - Call thermal conductivity: ∇T → q
# - Build capacity matrix C and conductivity matrix K
end
function assemble!(assembly, physics::ContactPhysics, elements, time)
# Contact-specific assembly:
# - Detect penetration
# - Compute contact forces
# - Build constraint equations
end
```
**This is Julia's superpower!** No runtime type checks, no vtables, just fast compiled code for each physics type.
---
### 3. `Material` - The Constitutive Model
**Role:** Maps kinematic quantities to stress/flux/response.
**Interface (Elasticity example):**
```julia
abstract type AbstractMaterial end
abstract type AbstractMaterialState end
# Material computes: (ε, state_old, Δt) → (σ, 𝔻, state_new)
function compute_stress(
material::AbstractMaterial,
ε::SymmetricTensor{2,3},
state_old::AbstractMaterialState,
Δt::Float64
) -> Tuple{SymmetricTensor{2,3}, SymmetricTensor{4,3}, AbstractMaterialState}
# Returns: (stress, tangent, state_new)
end
```
**Why separate from Physics?**
- ✅ **Modularity:** Change material without touching assembly code
- ✅ **Testability:** Unit-test materials independently
- ✅ **Performance:** Compiler specializes on material type
- ✅ **Clarity:** Material logic isolated from kinematics
**Material examples:**
```julia
# Stateless material (no history)
struct LinearElastic <: AbstractMaterial
λ::Float64 # Lamé parameter
μ::Float64 # Shear modulus
end
# Stateful material (history-dependent)
struct PerfectPlasticity <: AbstractMaterial
E::Float64 # Young's modulus
ν::Float64 # Poisson's ratio
σ_y::Float64 # Yield stress
end
struct PlasticityState{T} <: AbstractMaterialState
εₚ::SymmetricTensor{2,3,T} # Plastic strain
α::T # Equivalent plastic strain
end
```
**Performance:** 20-70 ns per material evaluation (validated Nov 10, 2025)
---
### 4. `Assembly` - The Global System Builder
**Role:** Accumulate element contributions into global matrices/vectors.
**What it holds:**
- Global stiffness matrix `K` (sparse)
- Global force vector `f`
- (Optional) Mass matrix `M`, damping `C`, geometric stiffness `Kg`
**What it does:**
- Pre-allocates sparse matrix structure
- Accumulates element contributions: `K += Kₑ`, `f += fₑ`
- Handles DOF mapping: local element DOFs → global system DOFs
**Why separate from Physics?**
- ✅ **Reusability:** Same Assembly struct for all physics types
- ✅ **Optimization:** Pre-allocated structure, efficient COO→CSC conversion
- ✅ **Parallelism:** (Future) Thread-safe assembly with color-based locking
**Example:**
```julia
# Create assembly
assembly = Assembly()
# Loop over elements
for element in elements
# Compute element stiffness and force
Kₑ, fₑ = assemble_element(physics, element, time)
# Get global DOF indices
gdofs = get_gdofs(element)
# Add to global system
add!(assembly.K, gdofs, gdofs, Kₑ)
add!(assembly.f, gdofs, fₑ)
end
# Solve global system
u = assembly.K \ assembly.f
```
---
### 5. `IntegrationPoint` - The Quadrature Point
**Role:** Location and weight for numerical integration.
**What it knows:**
- Position in reference element: `ξ ∈ [-1,1]ᵈⁱᵐ`
- Integration weight: `w`
- Index/ID for state storage
**What it does NOT know:**
- Material state (that's stored per-element per-IP)
- Stress/strain (that's computed on-the-fly)
**Why this design?**
- ✅ **Immutable:** Integration points never change
- ✅ **Topology-specific:** Different rules for Tri3 vs Quad4
- ✅ **Pre-computed:** Created once, reused forever
**Example:**
```julia
# Get integration points for element type
ips = integration_points(Gauss{2}, Quad4)
# Each IP knows position and weight
for ip in ips
ξ = ip.ξ # Position: NTuple{2, Float64}
w = ip.weight # Weight: Float64
# Evaluate basis functions at this point
N, ∇N_ref = evaluate_basis(basis, ξ)
# Do integration: ∫f dΩ ≈ ∑ᵢ f(ξᵢ)⋅w(ξᵢ)
end
```
---
### 6. `BasisInfo` - The Shape Function Cache
**Role:** Pre-allocated workspace for basis function evaluation.
**What it caches:**
- Basis function values `N`
- Gradients in reference config `∇N_ref`
- Gradients in current config `∇N`
- Jacobian `J`, determinant `detJ`
**Why cache?**
- ✅ **Zero allocation:** Reuse same arrays for every element
- ✅ **Type stability:** All sizes known at compile time
- ✅ **Performance:** Avoid repeated memory allocation
**Example:**
```julia
# Create cache for Tet10 elements
bi = BasisInfo(Tet10)
# Reuse for every element
for element in elements
for ip in integration_points(element)
# Evaluate into pre-allocated cache
eval_basis!(bi, element.X, ip)
# Access cached results
N = bi.N # Shape functions
∇N = bi.grad # Gradients ∂N/∂x
w = ip.weight * bi.detJ # Integration weight
end
end
```
---
## Data Flow: From Mesh to Solution
### Step 1: Problem Setup
```julia
# Define physics
physics = ElasticityPhysics(
formulation = :continuum,
finite_strain = false,
store_fields = [:stress, :strain]
)
# Define material
material = LinearElastic(E=200e9, ν=0.3)
# Create elements with material
elements = [Element(Tet10, conn) for conn in connectivity]
for el in elements
el.material = material
el.states_old = [NoState() for _ in 1:n_integration_points]
el.states_new = [NoState() for _ in 1:n_integration_points]
end
```
### Step 2: Assembly Loop
```julia
assembly = Assembly()
for element in elements
# Get element data
X = element.geometry # Nodal coordinates
u = element.displacement # Nodal displacements
# Initialize element matrices
Kₑ = zeros(ndofs, ndofs)
fₑ = zeros(ndofs)
# Integration point loop
for (ip_idx, ip) in enumerate(element.integration_points)
# 1. KINEMATICS: u → ε
∇N = shape_function_gradients(element, ip)
ε = compute_strain_from_gradients(∇N, u)
# 2. MATERIAL: ε → (σ, 𝔻, state)
state_old = element.states_old[ip_idx]
σ, 𝔻, state_trial = compute_stress(material, ε, state_old, Δt)
# 3. ASSEMBLY: (∇N, σ, 𝔻) → (Kₑ, fₑ)
w = integration_weight(ip)
accumulate_stiffness!(Kₑ, ∇N, 𝔻, w)
accumulate_internal_forces!(fₑ, ∇N, σ, w)
# DON'T update states yet (Newton iterations!)
end
# 4. GLOBAL: Kₑ → K, fₑ → f
gdofs = get_gdofs(element)
add!(assembly.K, gdofs, gdofs, Kₑ)
add!(assembly.f, gdofs, fₑ)
end
```
### Step 3: Solve
```julia
# Linear solve: K⋅Δu = f
Δu = assembly.K \ assembly.f
# Newton iteration (if nonlinear)
while norm(residual) > tolerance
# Re-assemble with trial displacement
u_trial = u_old + Δu
# Solve linearized system
Δu = assembly.K \ assembly.f
# Update
u_trial += Δu
end
# Converged! Commit state
for element in elements
element.states_old .= element.states_new
end
```
---
## Design Principles
### 1. Separation of Concerns
**Each struct has ONE job:**
- `Element` → Geometry
- `Physics` → Equation selection
- `Material` → Constitutive model
- `Assembly` → Global system
- `Solver` → Linear algebra
**Benefits:**
- ✅ Easy to test (unit test each component)
- ✅ Easy to extend (add new material without touching assembly)
- ✅ Easy to optimize (profile each layer independently)
### 2. Type Stability
**Every function has concrete return type:**
```julia
# ✅ GOOD: Compiler knows return type
function compute_stress(m::LinearElastic, ε) -> Tuple{SymmetricTensor{2,3}, SymmetricTensor{4,3}, NoState}
# ...
end
# ❌ BAD: Compiler doesn't know (Dict lookup)
function compute_stress(element, ip)
stress = element.fields["stress"] # Unknown type!
# ...
end
```
**Performance impact:** 10-100× speedup from type stability alone!
### 3. Zero Allocation
**Hot paths allocate NOTHING:**
```julia
# Pre-allocate once
bi = BasisInfo(Tet10)
Kₑ = zeros(30, 30)
# Reuse in loop (zero allocations!)
for element in elements
fill!(Kₑ, 0.0)
for ip in integration_points(element)
eval_basis!(bi, X, ip) # Fills cache, no allocation
# ... assembly logic
end
end
```
**Validated:** All material models achieve 0 bytes allocation (Nov 10, 2025)
### 4. Multiple Dispatch
**Use Julia's type system:**
```julia
# Same function name, different implementations
assemble!(assembly, ::ElasticityPhysics, elements, time)
assemble!(assembly, ::HeatPhysics, elements, time)
assemble!(assembly, ::ContactPhysics, elements, time)
# Compiler generates specialized code for each!
```
**No runtime overhead, no vtables, just fast native code.**
---
## Common Questions
### Q: Why not use classes with methods?
**A:** Julia's multiple dispatch is more powerful than OOP:
```julia
# OOP way (single dispatch on first argument)
element.assemble(physics) # Only element type matters
# Julia way (multiple dispatch on ALL arguments)
assemble!(assembly, physics, element, time) # All types matter!
```
This enables:
- Compiler specialization on ALL argument types
- Adding new methods without modifying existing types
- True separation of concerns (no "god objects")
### Q: Why immutable structs?
**A:** Performance and safety:
- Structs with all concrete types are stack-allocated
- Immutability enables compiler optimizations
- No accidental mutation bugs
**Rule:** Use immutable structs unless you NEED mutability (like Assembly accumulation)
### Q: Why Tensors.jl instead of matrices?
**A:** Performance and clarity:
- `SymmetricTensor{2,3,Float64,6}` is **stack-allocated** (48 bytes on stack)
- Regular `Matrix{Float64}` is **heap-allocated** (pointer + malloc)
- Code looks like math: `σ = λ⋅tr(ε)⋅I + 2μ⋅ε`
- Type stability: Compiler knows exact size at compile time
---
## Summary: The JuliaFEM Way
**Core philosophy:**
1. **Separate concerns** - Each struct has ONE job
2. **Type stability** - Compiler knows ALL types
3. **Zero allocation** - Hot paths reuse memory
4. **Multiple dispatch** - Compiler specializes for each case
5. **Immutability** - Stack allocation + safety
6. **Tensors.jl** - Mathematical clarity + performance
**Result:** Fast, maintainable, extensible FEM code that looks like the math it implements.
---
## Further Reading
- **`docs/book/material_modeling.md`** - Material model implementation guide
- **`docs/book/elasticity_refactoring_plan.md`** - Elasticity system design
- **`docs/book/element_architecture.md`** - Element composition philosophy
- **`llm/ARCHITECTURE.md`** - Eight-layer system architecture
- **`llm/VISION_2.0.md`** - Project vision and philosophy
---
**Last Updated:** November 10, 2025
**Status:** Authoritative (reflects current design decisions and implementation)