Bring module prose in line with flat `dof_indices`, `@DOFSet`, and DOFHandler-era
helpers while tightening compile-time DOF arithmetic.
- Refresh docstrings: drop markdown bold noise; document `element_dofs` /
`field_dof_range` instead of NamedTuple-shaped `dof_indices` accessors.
- Rewrite examples to prefer `@DOFSet`; spell out NamedTuple equivalence and drop
Tuple-based migration snippets incompatible with DOFHandler.
- Implement `field_ndofs(::Type{<:DOF}, Topo)` / `ndofs(::DOFSet, Topo)` using
`quantity_type` / `entity_type` and generator-style sums over `fieldtypes(S)`.
- Extend `quantity_type(::Type{<:DOF})` for raw `Float64`, `Vec`, `Tensor`, and
`SymmetricTensor` fields alongside `AbstractField` wrappers.
- Allow `field_ndofs` on raw-quantity DOFs by sizing `T` directly (pressure on
cells/faces, etc.).
- Remove `single_field` compatibility helper and duplicate element accessors now
canonical in `src/elements/elements.jl`.
- Point LICENSE header at LICENSE.md.
src/dofs/
Type-stable degree-of-freedom infrastructure: the field specification
language (DOF{Q, E}, @DOFSet), the global DOF numbering
(DOFHandler), and the inverse DOF connectivity used by the matrix-free
assembler.
The previous Dict-based DOFManager/register_fields!/count_field_dofs
API has been removed. DOFManager is now an alias for DOFHandler for
backward compatibility.
Contract and vocabulary: AGENTS.md (repository root). Executable examples:
test/dofs/test_multifield_dof_system.jl and the DOF topic tests under
test/dofs/runtests.jl.
Files
api.jl—DOF{Quantity, Entity}abstract type,dof_sizefor the supported quantity types (Float64,Vec{D},Tensor{2,D}), and the entity types (Vertex,Edge,Face,Cell).fields.jl— the@DOFSetmacro (NamedTuple ofDOF{Q, E}) and field accessors (field_names,field_count,field_ndofs,field_dof_range,is_single_field).dof_handler.jl—DOFHandler{Mesh, S, NF}. One flatVector{Int}per field stores the starting global DOF for each entity ID;total_dofsis computed once.create_elements!(mesh, ET)walks the mesh, assigns DOFs, builds the element vector and the inverse DOF connectivity in one pass.dof_connectivity.jl—DOFConnectivity. Maps each DOF to the elements and local-DOF positions that contribute to its row, which is what the DOF-based assembler iterates over.
DOF specification
DOF{Quantity, Entity} is purely a type-level marker; instances are
never created. Quantity carries the per-entity DOF size via
dof_size, and Entity selects the topological entity that owns it.
# Single field
S = @DOFSet{u::DOF{Displacement{3}, Vertex}}
# Multi-field (e.g. thermo-mechanical)
S = @DOFSet{T::DOF{Temperature, Vertex},
u::DOF{Displacement{3}, Vertex}}
# `S` is a NamedTuple type with field types `DOF{Q, E}`.
@DOFSet is the preferred entry point. DOFSet is currently a type
alias for NamedTuple, but using @DOFSet insulates calling code from
that implementation detail.
Building elements and the handler
create_elements! is the only call most user code needs:
ET = Element{Hex8, Lagrange{1}, S}
elements, handler = create_elements!(mesh, ET)
# elements::Vector{Element{Hex8, Lagrange{1}, S, 24}}
# handler.dof_connectivity is already built.
DOFHandler exposes:
field_starts::NTuple{NF, Vector{Int}}— per-field starting DOF for each entity ID.total_dofs::Int.dof_connectivity::DOFConnectivityfor the matrix-free path.
The compile-time DOF layout for the element template is generated by
local_dof_layout(::Type{Element{K, P, S, N}}) (in src/elements/).
Element-level DOF utilities
These helpers operate on a single Element instance and are used inside
assembly hot paths:
element_dofs(elem)— flatNTuple{N, UInt64}.n_element_dofs(elem)—N.local_to_global_map(elem)— same as above, for clarity.field_dof_range(elem, :field)—UnitRangeof local indices for one field block (compile-time constant).extract_element_dofs(elem, u)— flatNamedTupleof scalars grouped by field.extract_element_dofs_structured(elem, u)— values reinterpreted into the field's quantity type (e.g.Vec{3}per node).interpolate_field,interpolate_fields,interpolate_field_value,interpolate_local_fields— point-evaluation utilities at quadrature points.
Multiple DOFs per Edge or Face
The topological facet id from AbstractFacetConnectivityMaps still
identifies one mesh edge or mesh face. If the field quantity has
dof_size(Q) > 1 (for example DOF{Vec{2,Float64}, Edge}), that entity owns
several consecutive global DOFs starting at field_starts[field][entity_gid].
local_dof_layout and _make_element_dofs treat them as separate components
on the same local entity index; assembly kernels that replicate a scalar facet
measure per unknown should match component(layout_i) == component(layout_j)
when filling diagonal blocks.
Hp-refinement, NURBS/IGA, or hierarchic enrichment imply either distinct
Element{K, P, S, N} templates per patch (so N and layout stay compile-time
constants) or precomputed facet tables built once when the mesh changes — not
dynamic Dict lookups inside element quadrature loops.
Variable numbers of DOFs on different mesh edges (non-uniform p along edges)
need matching counts on shared edges for conformity. A natural numbering stores
one contiguous band per global edge with widths given by a CSR offset vector;
see edge_dof_csr_offsets on AbstractFacetConnectivityMaps in
src/mesh/hex8_facet_maps.jl. Wiring that into DOFHandler and
local_dof_layout is future work when hp facet spaces are added.
Heterogeneous element types
create_elements! can be called repeatedly on the same mesh with
different Element{K, P, S} types (for example a thermal S1 and a
mechanical S2); the handler keeps DOFs of identically-named fields
shared between the element types.
Related code
src/topology/—Vertex,Edge,Face,Cell.src/elements/—Element{K, P, S, N}andlocal_dof_layout.src/assemblers/—DOFBasedCOOAssembleris the primary consumer ofDOFConnectivity.test/dofs/— public test suite.