# 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_size`
  for the supported quantity types (`Float64`, `Vec{D}`, `Tensor{2,D}`),
  and the entity types (`Vertex`, `Edge`, `Face`, `Cell`).
- `fields.jl`          — the `@DOFSet` macro (NamedTuple of `DOF{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 flat
  `Vector{Int}` per field stores the starting global DOF for each
  entity ID; `total_dofs` is 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.

```julia
# 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:

```julia
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::DOFConnectivity` for 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)`               — flat `NTuple{N, UInt64}`.
- `n_element_dofs(elem)`             — `N`.
- `local_to_global_map(elem)`        — same as above, for clarity.
- `field_dof_range(elem, :field)`    — `UnitRange` of local indices for
  one field block (compile-time constant).
- `extract_element_dofs(elem, u)`    — flat `NamedTuple` of 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`](@ref) 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`](@ref) 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}` and `local_dof_layout`.
- `src/assemblers/`      — `DOFBasedCOOAssembler` is the primary consumer
  of `DOFConnectivity`.
- `test/dofs/`           — public test suite.
