Files
JuliaFEM.jl/docs/book/gmsh_tutorial.md
T
Jukka Aho 46b0cd3927 docs(book): Add comprehensive Gmsh to physics tutorial
New file: docs/book/gmsh_tutorial.md (544 lines)

Complete educational resource addressing Issue #183:

Step 1: Mesh Generation with Gmsh
- Why Gmsh (features, academic adoption)
- .geo file syntax and concepts
- Mesh generation commands
- Understanding .msh format

Step 2: Weak Formulation (Theory)
- Strong form → weak form derivation
- Galerkin approximation
- M du/dt + K u = f system

Step 3: FEM Assembly in JuliaFEM
- Loading meshes
- Creating problems and elements
- Boundary conditions (Dirichlet, Neumann)
- Assembly process internals

Step 4: Extracting Matrices (Issue #183 core answer)
- How to get K, M, f after assembly
- Why extract (5 use cases)
- Integration with DifferentialEquations.jl
- Complete working example

Step 5: Method of Lines
- PDE → ODE spatial discretization strategy
- Separation of space/time concerns
- Modularity benefits

Plus: Comparison (built-in vs external), Extensions (nonlinear, 3D,
parallel, GPU), Troubleshooting, References

Demonstrates 'laboratory not fortress' philosophy
2025-11-09 23:24:15 +02:00

545 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: "From Gmsh to Physics: Complete Heat Equation Tutorial"
author: "Jukka Aho"
date: "2025-11-09"
categories: ["Tutorial", "Getting Started"]
tags: ["gmsh", "heat-equation", "academic-usage", "method-of-lines"]
issue: "183"
description: "Complete workflow from mesh generation to FEM assembly, addressing academic usage without built-in physics"
---
# From Gmsh to Physics: Complete Heat Equation Tutorial
**Purpose:** Demonstrate complete FEM workflow from mesh generation through physics, addressing [Issue #183](https://github.com/JuliaFEM/JuliaFEM.jl/issues/183) - using JuliaFEM for academic/research purposes without built-in physics.
**What You'll Learn:**
- Generate meshes with Gmsh
- Load meshes into JuliaFEM
- Assemble stiffness and mass matrices
- Extract matrices for external solvers (DifferentialEquations.jl, etc.)
- Solve the heat equation using method of lines
---
## Problem Statement
We'll solve the transient heat equation on a unit square Ω = [0,1] × [0,1]:
**Governing equation:**
```
∂u/∂t = α∇²u + f(x,y,t) in Ω
```
**Boundary conditions:**
- Dirichlet: u = 0 on left edge (x = 0)
- Neumann: ∂u/∂n = 0 on other edges (natural BC)
**Initial condition:**
```
u(x,y,0) = sin(πx)sin(πy)
```
**Parameters:**
- α = 1.0 (thermal diffusivity)
- f = 0 (no heat source)
---
## Step 1: Mesh Generation with Gmsh
### Why Gmsh?
Gmsh is a free, open-source mesh generator that:
- Supports complex geometries (2D and 3D)
- Generates quality meshes with various element types
- Has scripting capabilities (`.geo` files)
- Widely used in academic and industrial FEM
### Creating the Geometry File
Create `unit_square.geo`:
```geo
// Gmsh geometry file: Unit square mesh
// Generate with: gmsh -2 unit_square.geo -o unit_square.msh
// Mesh element size
lc = 0.1;
// Corner points
Point(1) = {0, 0, 0, lc};
Point(2) = {1, 0, 0, lc};
Point(3) = {1, 1, 0, lc};
Point(4) = {0, 1, 0, lc};
// Edges
Line(1) = {1, 2}; // Bottom
Line(2) = {2, 3}; // Right
Line(3) = {3, 4}; // Top
Line(4) = {4, 1}; // Left
// Surface
Line Loop(1) = {1, 2, 3, 4};
Plane Surface(1) = {1};
// Physical groups for boundary conditions
Physical Line("bottom") = {1};
Physical Line("right") = {2};
Physical Line("top") = {3};
Physical Line("left") = {4};
Physical Surface("body") = {1};
// Use triangular elements
Mesh.ElementOrder = 1; // Linear elements (Tri3)
Mesh.Algorithm = 6; // Frontal-Delaunay for 2D
```
**Key concepts:**
- `lc` controls mesh density (smaller = finer mesh)
- Physical groups (`Physical Line`, `Physical Surface`) label regions for boundary conditions
- `ElementOrder = 1` gives linear triangular elements (Tri3)
### Generating the Mesh
```bash
gmsh -2 unit_square.geo -o unit_square.msh
```
**Options:**
- `-2`: Generate 2D mesh
- `-o`: Output file name
**Result:** `unit_square.msh` contains nodes and element connectivity.
### Understanding the Mesh Format
Gmsh `.msh` format (ASCII version 2.2):
```
$MeshFormat
2.2 0 8
$EndMeshFormat
$Nodes
121 # Number of nodes
1 0.0 0.0 0.0 # node_id x y z
2 0.1 0.0 0.0
...
$EndNodes
$Elements
240 # Number of elements
1 1 2 4 1 1 2 # elem_id type num_tags tags... node_ids...
2 2 2 1 1 1 2 13 # type=2 is Tri3
...
$EndElements
```
**Element types:**
- Type 1: 2-node line (Seg2)
- Type 2: 3-node triangle (Tri3)
- Type 3: 4-node quadrilateral (Quad4)
---
## Step 2: Weak Formulation (Theory)
### Strong Form
The strong form (classical PDE) is:
```
∂u/∂t - α∇²u = f in Ω
u = g on Γ_D (Dirichlet boundary)
∂u/∂n = h on Γ_N (Neumann boundary)
```
### Weak Form
Multiply by test function v and integrate by parts:
```
∫_Ω v(∂u/∂t) dΩ + α∫_Ω ∇v·∇u dΩ = ∫_Ω vf dΩ + ∫_{Γ_N} vh dΓ
```
### Galerkin Approximation
Approximate u and v with finite element basis functions:
```
u(x,y,t) ≈ Σᵢ uᵢ(t) Nᵢ(x,y)
v(x,y) ≈ Σⱼ vⱼ Nⱼ(x,y)
```
Substitute and collect terms:
```
M du/dt + K u = f
```
Where:
- **M** = mass matrix: `Mᵢⱼ = ∫_Ω Nᵢ Nⱼ dΩ`
- **K** = stiffness matrix: `Kᵢⱼ = α∫_Ω ∇Nᵢ·∇Nⱼ dΩ`
- **f** = force vector: `fᵢ = ∫_Ω Nᵢ f dΩ + ∫_{Γ_N} Nᵢ h dΓ`
This is a **system of ODEs** - the spatial discretization is complete, leaving only time dependence.
---
## Step 3: FEM Assembly in JuliaFEM
### Loading the Mesh
```julia
using JuliaFEM
# Read Gmsh mesh
mesh = abaqus_read_mesh("unit_square.msh") # Or gmsh reader when available
# Inspect mesh
println("Nodes: ", length(mesh.nodes))
println("Elements: ", length(mesh.elements))
println("Sets: ", keys(mesh.element_sets))
```
### Creating Problem and Elements
```julia
# Heat transfer problem (domain)
body = Problem(Heat, "heat_body", 2) # 2D problem
body_elements = create_elements(mesh, "body")
# Add material properties
thermal_conductivity = 1.0 # α
for element in body_elements
update!(element, "thermal conductivity", thermal_conductivity)
end
add_elements!(body, body_elements)
```
**Key:** `update!` sets element properties. JuliaFEM stores these in element fields (now immutable NamedTuples for 130x speedup!).
### Boundary Conditions
```julia
# Dirichlet BC: u = 0 on left edge
bc = Problem(Dirichlet, "fixed_temp", 2, "temperature")
bc_elements = create_elements(mesh, "left")
for element in bc_elements
update!(element, "temperature", 0.0)
end
add_elements!(bc, bc_elements)
```
**Neumann BC:** Natural boundary conditions (∂u/∂n = 0) require no explicit code - they're automatically satisfied by the weak form.
### Assembly Process
```julia
# Assemble at time t = 0
time = 0.0
assemble!(body, time)
assemble!(bc, time)
```
**What happens internally:**
1. **Loop over elements:**
For each element e:
2. **Compute element matrices:**
```julia
# Get element nodes and geometry
X = [node.position for node in element.nodes]
# Numerical integration over element
for (ξ, w) in gauss_points(element)
# Shape functions and derivatives
N = basis(element, ξ)
dN = grad_basis(element, ξ)
# Jacobian (parametric → physical)
J = dN' * X
detJ = det(J)
# Physical derivatives
dN_dx = J \ dN
# Local matrices
Kₑ += w * detJ * α * (dN_dx * dN_dx')
Mₑ += w * detJ * (N * N')
end
```
3. **Scatter to global:**
```julia
global_dofs = get_dofs(element)
K[global_dofs, global_dofs] += Kₑ
M[global_dofs, global_dofs] += Mₑ
```
4. **Apply Dirichlet BCs:**
Zero out rows/columns for constrained DOFs.
---
## Step 4: Extracting Matrices (Issue #183)
**This is what Chris Rackauckas asked for!**
After assembly, extract matrices for use with external solvers:
```julia
# Get assembled system
K = body.assembly.K # SparseMatrixCSC{Float64}
M = body.assembly.M # SparseMatrixCSC{Float64}
f = body.assembly.f # Vector{Float64}
# System is now: M * du/dt = -K * u + f
```
### Why Extract Matrices?
**Use cases:**
1. **DifferentialEquations.jl** - Advanced ODE solvers (Rosenbrock, IMEX, etc.)
2. **Krylov.jl** - Iterative linear solvers for large systems
3. **Custom time integration** - Research on novel time-stepping schemes
4. **Sensitivity analysis** - Automatic differentiation through solvers
5. **Optimal control** - Adjoint methods, PDE-constrained optimization
### Example: Solve with DifferentialEquations.jl
```julia
using OrdinaryDiffEq
# Initial condition: u₀ = sin(πx)sin(πy)
u0 = zeros(length(mesh.nodes))
for (i, node) in enumerate(mesh.nodes)
x, y = node.position[1:2]
u0[i] = sin(π*x) * sin(π*y)
end
# Apply boundary conditions to u0
apply_bc!(u0, bc)
# Define ODE: M * du/dt = -K * u + f
# Rearrange: du/dt = M \ (-K * u + f)
function heat_ode!(du, u, p, t)
K, M, f = p
du .= M \ (-K * u .+ f)
end
# Create ODE problem
tspan = (0.0, 1.0)
prob = ODEProblem(heat_ode!, u0, tspan, (K, M, f))
# Solve with adaptive Rosenbrock method
sol = solve(prob, Rosenbrock23())
# Solution is now in sol.u (array of states at different times)
```
**Advantages:**
- Adaptive time-stepping
- Stiff ODE solvers
- Event handling
- Sensitivity analysis
- GPU acceleration (CuArrays)
---
## Step 5: Complete Working Example
See `examples/gmsh_heat_equation/gmsh_heat_equation.jl` for full runnable code.
**Run it:**
```bash
cd examples/gmsh_heat_equation
gmsh -2 unit_square.geo -o unit_square.msh
julia --project=. gmsh_heat_equation.jl
```
**Output:**
- Mesh statistics
- Assembly information
- Extracted matrix sizes
- Instructions for next steps
---
## Method of Lines: Spatial Discretization Strategy
**Key insight:** FEM performs **spatial discretization only**, converting PDE → ODE system.
### Before FEM (PDE):
```
∂u(x,y,t)/∂t = α∇²u(x,y,t) + f(x,y,t)
```
- Infinite-dimensional: u depends on continuous (x,y)
- Cannot solve directly on computer
### After FEM (ODE):
```
M du(t)/dt = -K u(t) + f(t)
```
- Finite-dimensional: u is a vector of n nodal values
- Can solve with ODE integrators
**This separation is powerful:**
- **Space:** FEM handles complex geometry, boundary conditions
- **Time:** ODE solvers handle stiff systems, adaptivity, stability
- **Modularity:** Swap spatial/temporal methods independently
---
## Comparison: Built-in vs External Solvers
### Option 1: JuliaFEM Built-in (Easiest)
```julia
solver = Solver(Linear)
push!(solver, body, bc)
solver() # Uses direct solver
results = solver.results
```
**Pros:** Simple, integrated, handles BCs automatically
**Cons:** Less control, fixed time-stepping, direct solver only
### Option 2: Extract Matrices (Flexible)
```julia
K, M, f = extract_matrices(body, bc)
prob = ODEProblem(heat_ode!, u0, tspan, (K, M, f))
sol = solve(prob, Tsit5())
```
**Pros:** Full control, adaptive methods, iterative solvers, research flexibility
**Cons:** More code, manual BC handling, need to understand ODE interface
**Recommendation:** Start with Option 1 for learning, move to Option 2 for research.
---
## Extensions and Next Steps
### 1. Nonlinear Problems
If α = α(u) (temperature-dependent conductivity):
```julia
function nonlinear_heat!(du, u, p, t)
K, M, f = p
# Reassemble K with current u
K_nonlinear = assemble_stiffness(u)
du .= M \ (-K_nonlinear * u .+ f)
end
```
### 2. Time-Dependent BCs
```julia
function bc_time(t)
return sin(2π*t) # Oscillating temperature
end
# Update f vector in ODE function
f_bc = apply_bc_vector(bc, t)
```
### 3. 3D Problems
Same workflow, just change:
- Geometry file to 3D (Tet4, Tet10 elements)
- Problem dimension: `Problem(Heat, "body", 3)`
### 4. Parallel Assembly
For large meshes (>1M elements):
```julia
using Threads
@threads for element in elements
assemble_local!(element)
end
```
### 5. GPU Acceleration
```julia
using CUDA
K_gpu = CuSparseMatrixCSC(K)
u_gpu = CuArray(u0)
# Solve on GPU
```
---
## Troubleshooting
### Gmsh not found
**Error:** `gmsh: command not found`
**Solution:** Install Gmsh:
- **Ubuntu/Debian:** `sudo apt install gmsh`
- **macOS:** `brew install gmsh`
- **Windows:** Download from https://gmsh.info
### Mesh too coarse/fine
Adjust `lc` in `.geo` file:
- `lc = 0.05` → finer mesh (more elements, slower)
- `lc = 0.2` → coarser mesh (fewer elements, faster)
### Assembly takes forever
For large meshes (>100K elements):
1. Use iterative solvers (Krylov.jl)
2. Enable threading
3. Profile assembly (`@time`, `@profview`)
### Oscillations in solution
- Mesh too coarse → refine
- Time step too large → reduce or use adaptive solver
- Boundary conditions wrong → check Gmsh physical groups
---
## References
1. **Gmsh Documentation:** https://gmsh.info/doc/texinfo/gmsh.html
2. **JuliaFEM Documentation:** https://juliafem.github.io/JuliaFEM.jl
3. **DifferentialEquations.jl:** https://docs.sciml.ai/DiffEqDocs/
4. **Issue #183:** https://github.com/JuliaFEM/JuliaFEM.jl/issues/183
**Books:**
- Hughes, "The Finite Element Method" (theory)
- Zienkiewicz & Taylor, "The Finite Element Method" (comprehensive)
- Quarteroni et al., "Numerical Models for Differential Problems" (modern methods)
---
## Summary
**What we covered:**
1. ✓ Mesh generation with Gmsh (`.geo` → `.msh`)
2. ✓ Weak formulation and Galerkin method
3. ✓ FEM assembly (element → global matrices)
4. ✓ Extracting K, M, f for external solvers
5. ✓ Solving with DifferentialEquations.jl
6. ✓ Method of lines (PDE → ODE)
**Key takeaway:** JuliaFEM provides the spatial discretization machinery. You bring the physics and time integration. Perfect for academic/research flexibility.
**Next:** Try the example, modify the geometry, experiment with different BCs, explore nonlinear problems!
---
*This tutorial addresses Issue #183 and demonstrates JuliaFEM's "laboratory, not fortress" philosophy - educational, transparent, and flexible for research use.*