mirror of
https://github.com/JuliaFEM/JuliaFEM.jl.git
synced 2026-09-24 19:19:54 +00:00
5a07b3ab21
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
196 lines
6.1 KiB
Julia
196 lines
6.1 KiB
Julia
# # Tutorial 2: Reading Meshes with Gmsh
|
||
#
|
||
# Real FEM problems need meshes with many elements. Creating them manually
|
||
# would be tedious! This tutorial shows how to:
|
||
# - Read mesh files generated by Gmsh
|
||
# - Extract nodes and elements
|
||
# - Organize elements by physical groups (for boundary conditions)
|
||
#
|
||
# ## Prerequisites
|
||
#
|
||
# - Tutorial 1 (Creating Elements and Fields)
|
||
# - Basic understanding of mesh concepts (nodes, elements, connectivity)
|
||
|
||
using JuliaFEM
|
||
using Gmsh # Mesh generation and reading
|
||
using Test
|
||
|
||
# ## The Mesh File
|
||
#
|
||
# We're reading a pre-generated mesh file: `reading_gmsh_meshes.msh`
|
||
#
|
||
# This mesh was created using the Gmsh.jl recipe in `reading_gmsh_meshes_recipe.jl`.
|
||
# The recipe shows how to programmatically generate meshes - very useful for
|
||
# parametric studies!
|
||
#
|
||
# **Mesh specifications:**
|
||
# - Domain: Unit square [0,1] × [0,1]
|
||
# - Elements: 10 Quad4 elements (2×5 structured mesh)
|
||
# - Nodes: 18 nodes
|
||
# - Physical groups: DOMAIN (volume), BOTTOM/RIGHT/TOP/LEFT (boundaries)
|
||
|
||
mesh_file = joinpath(@__DIR__, "reading_gmsh_meshes.msh")
|
||
|
||
# ## Reading the Mesh with Gmsh.jl
|
||
#
|
||
# Gmsh.jl provides a Julia interface to the Gmsh API. We can read mesh files
|
||
# and extract all the data we need.
|
||
|
||
# Initialize Gmsh
|
||
gmsh.initialize()
|
||
|
||
# Open and read the mesh file
|
||
gmsh.open(mesh_file)
|
||
|
||
# ## Extracting Nodes
|
||
#
|
||
# Nodes are the points in space where we'll compute field values.
|
||
# In Gmsh, nodes are returned as:
|
||
# - `nodeTags`: Node IDs (integers)
|
||
# - `coord`: Coordinates (x, y, z) as a flat vector
|
||
|
||
nodeTags, coord, parametricCoord = gmsh.model.mesh.getNodes()
|
||
|
||
# The `coord` vector is flat: [x1, y1, z1, x2, y2, z2, ...]
|
||
# Let's reshape it into a more convenient format:
|
||
|
||
num_nodes = length(nodeTags)
|
||
nodes = Dict{Int,Vector{Float64}}()
|
||
|
||
for i in 1:num_nodes
|
||
node_id = nodeTags[i]
|
||
x = coord[3*(i-1)+1]
|
||
y = coord[3*(i-1)+2]
|
||
z = coord[3*(i-1)+3]
|
||
nodes[node_id] = [x, y, z]
|
||
end
|
||
|
||
# Now we have a dictionary mapping node IDs to coordinates!
|
||
|
||
# ## Extracting Elements
|
||
#
|
||
# Elements connect nodes and define the interpolation within each region.
|
||
# In Gmsh, we need to query elements by entity (surfaces, volumes, etc.).
|
||
|
||
# Get all 2D entities (surfaces in our case)
|
||
entities = gmsh.model.getEntities(2) # 2 = dimension for surfaces
|
||
|
||
# For each entity, get the elements
|
||
all_elements = Element[]
|
||
|
||
for entity in entities
|
||
dim, tag = entity
|
||
|
||
# Get element data for this entity
|
||
elemTypes, elemTags, nodeTags_elem = gmsh.model.mesh.getElements(dim, tag)
|
||
|
||
# Loop over element types (we have Quad4 = type 3)
|
||
for (elemType, elemTag, elemNodeTags) in zip(elemTypes, elemTags, nodeTags_elem)
|
||
|
||
# Get element properties
|
||
elemName, elemDim, order, numNodes, localNodeCoord, numPrimaryNodes =
|
||
gmsh.model.mesh.getElementProperties(elemType)
|
||
|
||
# Create JuliaFEM elements
|
||
for i in 1:length(elemTag)
|
||
# Extract connectivity for this element
|
||
start_idx = (i - 1) * numNodes + 1
|
||
end_idx = i * numNodes
|
||
# Convert UInt64 to Int for JuliaFEM compatibility
|
||
connectivity = Tuple(Int(tag) for tag in elemNodeTags[start_idx:end_idx])
|
||
|
||
# Map Gmsh element type to JuliaFEM element type
|
||
if elemType == 3 # Gmsh Quad4
|
||
local elem = Element(Quad4, connectivity)
|
||
else
|
||
@warn "Unknown element type: $elemType ($elemName)"
|
||
continue
|
||
end
|
||
|
||
# Add geometry field
|
||
update!(elem, "geometry", nodes)
|
||
|
||
push!(all_elements, elem)
|
||
end
|
||
end
|
||
end
|
||
|
||
# ## Physical Groups
|
||
#
|
||
# Physical groups let us organize elements by region (for materials)
|
||
# and boundaries (for boundary conditions).
|
||
|
||
# Get all physical groups
|
||
physicalGroups = gmsh.model.getPhysicalGroups()
|
||
|
||
# We can query which elements belong to each physical group
|
||
# (This would be used to create separate Problems for different regions)
|
||
|
||
# ## Cleanup
|
||
gmsh.finalize()
|
||
|
||
# ## Validation Tests
|
||
#
|
||
# Let's verify the mesh was read correctly:
|
||
|
||
@testset "Gmsh Mesh Reading" begin
|
||
# Correct number of nodes (2×5 structured quad mesh has 3×6 nodes)
|
||
@test length(nodes) == 18
|
||
|
||
# Correct number of elements (2 × 5 = 10 quads)
|
||
@test length(all_elements) == 10
|
||
|
||
# All elements should be Quad4
|
||
@test all(e -> typeof(e.properties) == Quad4, all_elements)
|
||
|
||
# Node coordinates should be in [0,1] × [0,1]
|
||
for (node_id, coord) in nodes
|
||
@test 0.0 <= coord[1] <= 1.0 # x ∈ [0,1]
|
||
@test 0.0 <= coord[2] <= 1.0 # y ∈ [0,1]
|
||
@test coord[3] == 0.0 # z = 0 (2D mesh)
|
||
end
|
||
|
||
# Each element should have 4 nodes
|
||
for element in all_elements
|
||
@test length(element.connectivity) == 4
|
||
end
|
||
end
|
||
|
||
# ## What We Learned
|
||
#
|
||
# ✅ How to read Gmsh .msh files using Gmsh.jl
|
||
# ✅ Extract nodes (IDs and coordinates)
|
||
# ✅ Extract elements (type, connectivity)
|
||
# ✅ Map Gmsh element types to JuliaFEM types
|
||
# ✅ Physical groups organize elements for BCs and materials
|
||
# ✅ Mesh generation recipes document mesh creation
|
||
#
|
||
# ## Why This Approach?
|
||
#
|
||
# **Pre-generated mesh files** (not generating in tests):
|
||
# - Tests are reliable (don't fail due to mesh generation)
|
||
# - Tests run faster (no mesh generation overhead)
|
||
# - Mesh is version-controlled (reproducible)
|
||
#
|
||
# **Recipe files** (showing how mesh was made):
|
||
# - Educational (teach Gmsh.jl)
|
||
# - Documented (can regenerate if needed)
|
||
# - Transparent (know exactly what the mesh is)
|
||
#
|
||
# ## Next Steps
|
||
#
|
||
# - **Tutorial 3:** Basis functions and shape function evaluation
|
||
# - **Tutorial 4:** Solving 1D elasticity (1-element validation)
|
||
# - **Tutorial 5:** Solving 2D elasticity (realistic 10-element problem)
|
||
#
|
||
# ## Real-World Impact
|
||
#
|
||
# JuliaFEM is being used to **validate other FEM software**!
|
||
# See [Issue #265](https://github.com/JuliaFEM/JuliaFEM.jl/issues/265) where
|
||
# a user validated their FEM code against JuliaFEM results. This is beautiful -
|
||
# we're useful as a reference implementation even after years!
|
||
#
|
||
# That's why we include **1-element validation tests** with analytical solutions
|
||
# and **nice round numbers** (parameters chosen so results are integers). Makes
|
||
# it easy to verify by hand calculation or compare with other codes.
|