# This file is a part of JuliaFEM. # License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md """ DOF Type - Type-Level Field Specification `DOF{T, E}` is an abstract type used purely for type-level field specifications. # Philosophy DOF types are NEVER instantiated - they exist purely at the type level to specify field structure in a clean, readable way. # Usage Field specifications are written with the `@DOFSet` macro and `DOF{Quantity, Entity}` type syntax: ```julia S = @DOFSet{u::DOF{Displacement{3}, Vertex}, p::DOF{Pressure, Cell}} ``` Single-field specifications use the same form: ```julia S = @DOFSet{u::DOF{Displacement{3}, Vertex}} ``` # Connection to Assembly The DOF specification `S` is a type parameter in Element: ```julia Element{Tetrahedron{4}, Lagrange{1}, S} ``` The assembler uses `S` to: 1. Compute total DOF count: `ndofs(S, Tetrahedron{4})`. 2. Extract field information: `fieldnames(S)`, `fieldtypes(S)`. 3. Count DOFs per field: `field_ndofs(field_type, topology)`. 4. Access element DOFs via `element_dofs(elem, :u)` (the `dof_indices` field itself is a flat `NTuple` for zero-allocation assembly). All type-level, compile-time resolved; no runtime overhead. # Compatibility `DOFSet` is currently implemented as `NamedTuple`, so a plain `@NamedTuple{u::DOF{...}, ...}` literal also works as long as each field type is a `DOF{Quantity, Entity}`. The bare `Tuple{Quantity, Entity}` form that older drafts used is no longer accepted by the DOFHandler; new code should use `@DOFSet` with `DOF{T,E}`. """ # ============================================================================ # DOF Size Function (utility for counting DOF components) # ============================================================================ """ dof_size(::Type{T}) → Int Number of DOF components for a quantity type. # Examples ```julia dof_size(Float64) # 1 (scalar) dof_size(Vec{2}) # 2 (2D vector) dof_size(Vec{3}) # 3 (3D vector) dof_size(Tensor{2,2}) # 4 (2×2 tensor) dof_size(Tensor{2,3}) # 9 (3×3 tensor) dof_size(SymmetricTensor{2,3}) # 6 (symmetric 3×3) ``` """ Base.@pure dof_size(::Type{Float64}) = 1 Base.@pure dof_size(::Type{<:Vec{D}}) where {D} = D Base.@pure dof_size(::Type{<:Tensor{2,D}}) where {D} = D * D Base.@pure dof_size(::Type{<:SymmetricTensor{2,D}}) where {D} = div(D * (D + 1), 2) # ============================================================================ # Abstract DOF Type (Base Type for All Field Specifications) # ============================================================================ """ abstract type AbstractDOF Abstract base type for all field specifications. # Purpose Base type for `DOF{T,E}` which is used inside `DOFSet` specifications: ```julia S = @DOFSet{u::DOF{Displacement{3}, Vertex}} # DOF{T,E} <: AbstractDOF Element{K, P, S} # where S <: DOFSet ``` # Subtypes - `DOF{T,E}`: Type-level field specifications (never instantiated) # Interface Subtypes should support: - `Base.fieldnames(::Type{<:AbstractDOF})` - field names as tuple - `ndofs(::Type{<:AbstractDOF}, ::Type{<:AbstractTopology})` - total DOF count """ abstract type AbstractDOF end # ============================================================================ # DOFSet - Multi-Field DOF Specification # ============================================================================ """ DOFSet Type alias for multi-field DOF specifications. Current implementation: NamedTuple (but this is an implementation detail!) Use `@DOFSet` macro to create multi-field specifications: ```julia S = @DOFSet{T::DOF{Temperature, Vertex}, u::DOF{Displacement{3}, Vertex}} ``` # Purpose Hides implementation detail so we can change from NamedTuple to custom struct later without breaking user code. # Usage in Element Type ```julia # Single-field - always use @DOFSet S = @DOFSet{u::DOF{Displacement{3}, Vertex}} Element{K, P, S} # Multi-field - use @DOFSet S = @DOFSet{T::DOF{Temperature, Vertex}, u::DOF{Displacement{3}, Vertex}} Element{K, P, S} ``` # Compatibility Note `NamedTuple` also works (since `DOFSet = NamedTuple`), but `DOFSet` is the preferred interface. Using `DOFSet` ensures your code will continue to work if we change the internal representation in the future. """ const DOFSet = NamedTuple """ @DOFSet{field1::Type1, field2::Type2, ...} Create a multi-field DOF specification. # Syntax Each field is specified using `DOF{FieldType, EntityType}`: ```julia field_name::DOF{FieldType, EntityType} ``` Where: - `FieldType`: AbstractField type (Displacement{3}, Temperature, etc.) - `EntityType`: Topological entity (Vertex, Edge, Face, Cell) # Examples ```julia # Thermo-mechanical coupling S = @DOFSet{T::DOF{Temperature, Vertex}, u::DOF{Displacement{3}, Vertex}} # THM-E (full multi-physics) S = @DOFSet{ T::DOF{Temperature, Vertex}, # Temperature u::DOF{Displacement{3}, Vertex}, # Displacement p::DOF{Pressure, Cell}, # Pressure φ::DOF{ElectricPotential, Edge} # Electric potential } # Create element with multi-field spec element = Element{Tetrahedron{4}, Lagrange{1}, S}(id, dof_indices) ``` # Implementation Note Currently wraps `@NamedTuple` but this is hidden from users. Can be changed to custom struct later without breaking code! # Compatibility You can also use `@NamedTuple` directly, but `@DOFSet` is preferred for future compatibility. """ macro DOFSet(expr) return :(@NamedTuple($expr)) end # ============================================================================ # DOF Type (Abstract - Type-Level Only) # ============================================================================ """ abstract type DOF{T, E<:TopologicalEntity} <: AbstractDOF Abstract type for type-level field specifications. NEVER instantiated. Used purely for clean syntax inside `@DOFSet`: ```julia S = @DOFSet{u::DOF{Vec{3,Float64}, Vertex}, p::DOF{Float64, Cell}} ``` # Type Parameters - `T`: Quantity type (Float64, Vec{D}, Tensor{2,D}, etc.) - `E`: Topological entity (Vertex, Edge, Face, Cell) # Examples ```julia DOF{Float64, Vertex} # Scalar at vertices DOF{Vec{3,Float64}, Vertex} # 3D vector at vertices DOF{Tensor{2,3,Float64}, Cell} # 3×3 tensor at cells DOF{SymmetricTensor{2,2}, Edge} # 2×2 symmetric tensor on edges ``` """ abstract type DOF{T, E<:TopologicalEntity} <: AbstractDOF end # DOF size for DOF type (type-level query) Base.@pure function dof_size(::Type{<:DOF{T,E}}) where {T,E} return dof_size(T) end # Fieldnames for single-field DOF types (default field name is :dof) Base.@pure Base.fieldnames(::Type{<:DOF}) = (:dof,)