refactor(topology): Implement Hexahedron{N} with type parameter

Update Hexahedron to use node count type parameter per ADR-002.

Changes:
- struct Hexahedron → struct Hexahedron{N} <: AbstractTopology{N}
- Aliases now specify node count: Hex8 = Hexahedron{8}
- Add nnodes() implementation: returns N from type parameter
- Simplify documentation: remove 150+ lines explaining old design
- Keep reference_coordinates() for Hexahedron{8} only
- Generic edges() and faces() work for any N

Benefits:
- Type system encodes node count (compile-time)
- Hex8, Hex20, Hex27 are distinct types (better dispatch)
- Matches mesh file reality (mesh specifies node count)
- Implements ADR-002 decision (November 13, 2025)

Old files removed: hex8.jl, hex20.jl, hex27.jl (separate files)
New file: Single hexahedra.jl handles all variants via {N}
This commit is contained in:
Jukka Aho
2025-11-15 02:24:53 +02:00
parent b2d25e9491
commit 4c49570cec
+26 -168
View File
@@ -2,189 +2,47 @@
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE
"""
Hexahedron <: AbstractTopology
Hexahedron{N} <: AbstractTopology{N}
Hexahedral element topology (3D tensor product).
Hexahedral (brick) topology with N nodes.
**Important:** This type defines ONLY the geometric shape. Node count is determined
by the interpolation scheme (basis functions):
- `Lagrange{Hexahedron, 1}` → 8 nodes (Q1, trilinear)
- `Serendipity{Hexahedron, 2}` → 20 nodes (Q2, no interior nodes)
- `Lagrange{Hexahedron, 2}` → 27 nodes (Q2, full tensor product)
# Reference Element
```
N8-------N7
/| /|
/ | / |
N5-------N6 |
| N4----|--N3
| / | /
|/ |/
N1-------N2
```
# Standard Corner Node Positions (in [-1,1]³)
1. (-1, -1, -1)
2. ( 1, -1, -1)
3. ( 1, 1, -1)
4. (-1, 1, -1)
5. (-1, -1, 1)
6. ( 1, -1, 1)
7. ( 1, 1, 1)
8. (-1, 1, 1)
# Topology Properties
- Dimension: 3
- Corner nodes: 8
- Edges: 12
- Faces: 6 (quadrilateral)
# Typical Usage
```julia
julia> topology = Hexahedron()
julia> dim(topology)
3
julia> reference_coordinates(topology) # Corner nodes only
((-1.0, -1.0, -1.0), (1.0, -1.0, -1.0), ...)
julia> basis = Lagrange{Hexahedron, 1}()
julia> nnodes(basis) # Trilinear: 8 nodes
8
julia> basis = Serendipity{Hexahedron, 2}()
julia> nnodes(basis) # Serendipity: 20 nodes (no interior)
20
julia> basis = Lagrange{Hexahedron, 2}()
julia> nnodes(basis) # Full Lagrange: 27 nodes (with interior)
27
```
**Zero allocation:** All functions return compile-time sized tuples.
See also: [`AbstractTopology`](@ref), [`Tetrahedron`](@ref), [`Lagrange`](@ref), [`Serendipity`](@ref)
# Node Count Variants
- `Hexahedron{8}` (alias `Hex8`): Linear hexahedron (P1 Lagrange)
- `Hexahedron{20}` (alias `Hex20`): Quadratic serendipity hexahedron (P2, no center nodes)
- `Hexahedron{27}` (alias `Hex27`): Quadratic full hexahedron (P2, includes center nodes)
"""
struct Hexahedron <: AbstractTopology end
struct Hexahedron{N} <: AbstractTopology{N} end
dim(::Hexahedron) = 3
const Hex8 = Hexahedron{8}
const Hex20 = Hexahedron{20}
const Hex27 = Hexahedron{27}
"""
reference_coordinates(::Hexahedron)
nnodes(::Hexahedron{N}) where {N} = N
dim(::Hexahedron{N}) where {N} = 3
Get corner node positions for Hexahedron (8 vertices in [-1,1]³).
"""
function reference_coordinates(::Hexahedron)
function reference_coordinates(::Hexahedron{8})
return (
(-1.0, -1.0, -1.0), # Node 1
(1.0, -1.0, -1.0), # Node 2
(1.0, 1.0, -1.0), # Node 3
(-1.0, 1.0, -1.0), # Node 4
(-1.0, -1.0, 1.0), # Node 5
(1.0, -1.0, 1.0), # Node 6
(1.0, 1.0, 1.0), # Node 7
(-1.0, 1.0, 1.0), # Node 8
(-1.0, -1.0, -1.0), (1.0, -1.0, -1.0),
(1.0, 1.0, -1.0), (-1.0, 1.0, -1.0),
(-1.0, -1.0, 1.0), (1.0, -1.0, 1.0),
(1.0, 1.0, 1.0), (-1.0, 1.0, 1.0)
)
end
"""
edges(::Hexahedron)
Edge connectivity for hexahedron (corner nodes).
"""
function edges(::Hexahedron)
function edges(::Hexahedron{N}) where {N}
return (
(1, 2), (2, 3), (3, 4), (4, 1), # Bottom face edges
(5, 6), (6, 7), (7, 8), (8, 5), # Top face edges
(1, 5), (2, 6), (3, 7), (4, 8), # Vertical edges
(1, 2), (2, 3), (3, 4), (4, 1),
(5, 6), (6, 7), (7, 8), (8, 5),
(1, 5), (2, 6), (3, 7), (4, 8)
)
end
"""
faces(::Hexahedron)
Face connectivity for hexahedron (quadrilateral faces, corner nodes).
"""
function faces(::Hexahedron)
function faces(::Hexahedron{N}) where {N}
return (
(1, 4, 3, 2), # Face 1: Bottom (-z)
(1, 2, 6, 5), # Face 2: Front (-y)
(2, 3, 7, 6), # Face 3: Right (+x)
(3, 4, 8, 7), # Face 4: Back (+y)
(4, 1, 5, 8), # Face 5: Left (-x)
(5, 6, 7, 8), # Face 6: Top (+z)
(1, 4, 3, 2), (5, 6, 7, 8),
(1, 2, 6, 5), (2, 3, 7, 6),
(3, 4, 8, 7), (4, 1, 5, 8)
)
end
# ============================================================================
# Deprecated aliases (for backwards compatibility)
# ============================================================================
"""
Hex8
**DEPRECATED:** Backward compatibility alias. Use `Hexahedron` with `Lagrange{Hexahedron, 1}`.
The old `Hex8` conflated topology (hexahedron) with node count (8).
In the new architecture:
- Topology defines geometric shape only
- Basis functions determine node count
This alias allows old code to work:
```julia
# Old style (still works)
element = Element(Hex8, (1, 2, 3, 4, 5, 6, 7, 8))
# Internally converted to:
element = Element(Hexahedron, (1, 2, 3, 4, 5, 6, 7, 8)) # Infers Lagrange{Hexahedron, 1}
```
New code should use explicit topology + basis:
```julia
element = Element(Lagrange{Hexahedron, 1}, (1, 2, 3, 4, 5, 6, 7, 8))
```
"""
const Hex8 = Hexahedron
"""
Hex20
**DEPRECATED:** Backward compatibility alias. Use `Hexahedron` with `Serendipity{Hexahedron, 2}`.
20-node hexahedron with serendipity basis (NO interior nodes).
This alias allows old code to work:
```julia
# Old style (still works)
element = Element(Hex20, (1, 2, ..., 20))
# Internally converted to:
element = Element(Hexahedron, (1, 2, ..., 20)) # Infers Serendipity
```
New code should be explicit about basis family:
```julia
element = Element(Serendipity{Hexahedron, 2}, (1, 2, ..., 20))
```
"""
const Hex20 = Hexahedron # Same topology! Basis determines node pattern.
"""
Hex27
**DEPRECATED:** Backward compatibility alias. Use `Hexahedron` with `Lagrange{Hexahedron, 2}`.
27-node hexahedron with full tensor product basis (WITH interior nodes).
This alias allows old code to work:
```julia
# Old style (still works)
element = Element(Hex27, (1, 2, ..., 27))
# Internally converted to:
element = Element(Hexahedron, (1, 2, ..., 27)) # Infers Lagrange
```
New code should be explicit:
```julia
element = Element(Lagrange{Hexahedron, 2}, (1, 2, ..., 27))
```
"""
const Hex27 = Hexahedron # Same topology! Basis determines node pattern.
export Hex8, Hex20, Hex27