feat(fields): expand AbstractField tags and documentation

Extend the field vocabulary with diffusion/Darcy/mixed facet tags while
making `dofs_per_node` / `quantity_type` expectations explicit and dropping
legacy `Physics(...)` snippets.

- Clarify `dofs_per_node` as components per primary entity (historical name)
  and document `quantity_type` as part of the `AbstractField` contract.
- Add `MoistureContent`, `PressurePotential`, `RT0FaceFlux`, and `Nedelec1Edge`
  with matching `dofs_per_node` / `quantity_type` hooks.
- Point concrete usage at active kernels and grouped tests; note beam/shell
  tags as not wired in the default 0.x build.
- Prefer plain prose over bold emphasis in nearby docstrings.
This commit is contained in:
Jukka Aho
2026-05-09 17:07:14 +03:00
parent 37abd55c34
commit 8064e86737
+79 -50
View File
@@ -28,17 +28,26 @@ Field defines what physical quantity is being computed:
# Interface Requirements
All field types must implement:
- `dofs_per_node(field)` - Number of DOFs per node
- `dofs_per_node(field)` - Number of scalar components of the field. The name
is historical: for vertex-located fields this equals the DOF count per node,
but for face/edge/cell-located fields (e.g. `RT0FaceFlux`, `Nedelec1Edge`,
`Pressure` on `Cell`) it returns the number of components per primary
entity, which need not be a node. The entity location is carried by the
`DOF{Quantity, Entity}` spec, not by the field type itself.
- `quantity_type(::Type{<:AbstractField})` - Underlying scalar / `Vec` /
`Tensor` type used to represent values.
# Concrete Types
- `Displacement{Dim}` - Displacement field (Dim components)
- `Temperature` - Temperature field (1 component)
- `DisplacementRotation{Dim}` - Combined displacement + rotation (2*Dim components)
- `RT0FaceFlux` / `Nedelec1Edge` - scalar tags for face / edge facet DOFs
# Design Philosophy
Field type determines:
- DOF count per node
- Component count of the quantity (and therefore DOF count per primary entity)
- Solution vector structure
- Boundary condition interpretation
- Assembly dispatch (different fields may need different assembly strategies)
@@ -60,7 +69,9 @@ dofs_per_node(field) # 6 (ux, uy, uz, θx, θy, θz)
```
# See Also
- [`dofs_per_node`](@ref)
- [`dofs_per_node`](@ref), [`quantity_type`](@ref)
- `DOF{Q, E}` in `src/dofs/api.jl` for how field types compose with
topological entities.
"""
abstract type AbstractField end
@@ -84,23 +95,9 @@ Displacement field with Dim components per node.
# Usage
```julia
# 3D elasticity
physics = Physics(
formulation=ContinuumFormulation{FullThreeD}(),
field=Displacement{3}(),
mesh=mesh,
material=steel
)
# 2D plane stress
physics = Physics(
formulation=ContinuumFormulation{PlaneStress}(),
field=Displacement{2}(),
mesh=mesh,
material=aluminum
)
```
`Displacement{Dim}` is consumed as a type tag by `ContinuumKernel` /
`AssemblyMaterialWorkspace` / `local_dof_layout`. See `test/runtests.jl`
under `Continuum Domain` for end-to-end use.
"""
struct Displacement{Dim} <: AbstractField end
@@ -116,20 +113,57 @@ Used for heat transfer problems.
# Usage
```julia
physics = Physics(
formulation=ContinuumFormulation{FullThreeD}(),
field=Temperature(),
mesh=mesh,
material=thermal_material
)
```
# See Also
- Heat transfer problems in docs/
`Temperature` is consumed as a type tag by `HeatKernel` and
`local_dof_layout`. See `test/runtests.jl` under `Heat Domain` for
end-to-end use.
"""
struct Temperature <: AbstractField end
"""
MoistureContent <: AbstractField
Scalar moisture-related field (humidity, saturation, or pore-water fraction) at
vertices for diffusion solves paired with [`MoistureDiffusivity`](@ref).
"""
struct MoistureContent <: AbstractField end
"""
PressurePotential <: AbstractField
Scalar hydraulic **pressure head** (or pore-pressure potential) at vertices for
primal Darcy flow; same numerical layout as [`Temperature`](@ref) — one DOF
per node — but carried under [`DarcyPotentialKernel`](@ref) with
[`HydraulicConductivity`](@ref).
"""
struct PressurePotential <: AbstractField end
"""
RT0FaceFlux <: AbstractField
Type tag for one scalar unknown per mesh face used as the coefficient of a
lowest-order RaviartThomas-type (`RT₀`) vector space. This is not a Cartesian
flux component. See `DarcyMixedRT0P0Kernel` in `src/domains/darcy/mixed_rt0.jl`
for Tet4 mixed Darcy; hex and other topologies are still open.
Prefer `DOF{RT0FaceFlux, Face}` over `DOF{Float64, Face}` when the unknown should
dispatch as H(div)-facet data. See [`Hex8FacetMaps`](@ref).
"""
struct RT0FaceFlux <: AbstractField end
"""
Nedelec1Edge <: AbstractField
Type tag for one scalar unknown per mesh edge (circulation DOF) in a lowest-order
Nédélec edge element on hexes. Reference bases, covariant Piola transforms, and
weak forms are not implemented yet.
Prefer `DOF{Nedelec1Edge, Edge}` over raw `DOF{Float64, Edge}` for dispatch.
See [`Hex8FacetMaps`](@ref) field `elem_edge_orientation` for a first edge sign hint;
full H(curl) conformity still needs Piola maps and may require extra flips when
two elements agree on that hint across a shared edge.
"""
struct Nedelec1Edge <: AbstractField end
"""
DisplacementRotation{Dim} <: AbstractField
@@ -146,23 +180,10 @@ DOFs per node: 2*Dim (Dim displacements + Dim rotations)
# Usage
```julia
# 3D beam (Timoshenko theory)
physics = Physics(
formulation=BeamFormulation{Timoshenko}(),
field=DisplacementRotation{3}(),
mesh=beam_mesh,
material=steel
)
# 3D shell (Reissner-Mindlin theory)
physics = Physics(
formulation=ShellFormulation{ReissnerMindlin}(),
field=DisplacementRotation{3}(),
mesh=shell_mesh,
material=composite
)
```
`DisplacementRotation` is currently a type tag for the field structure of
beam and shell elements. Concrete beam and shell formulations are not
implemented in the default 0.x build; the active analytic surface lives in
`ContinuumKernel` / `HeatKernel` under `src/domains/`.
# DOF Layout
@@ -215,6 +236,10 @@ function dofs_per_node end
# Concrete implementations
dofs_per_node(::Displacement{Dim}) where Dim = Dim
dofs_per_node(::Temperature) = 1
dofs_per_node(::MoistureContent) = 1
dofs_per_node(::PressurePotential) = 1
dofs_per_node(::RT0FaceFlux) = 1
dofs_per_node(::Nedelec1Edge) = 1
dofs_per_node(::DisplacementRotation{Dim}) where Dim = 2 * Dim
# ============================================================================
@@ -256,6 +281,10 @@ function quantity_type end
# Concrete implementations
quantity_type(::Type{Displacement{Dim}}) where Dim = Vec{Dim}
quantity_type(::Type{Temperature}) = Float64
quantity_type(::Type{MoistureContent}) = Float64
quantity_type(::Type{PressurePotential}) = Float64
quantity_type(::Type{RT0FaceFlux}) = Float64
quantity_type(::Type{Nedelec1Edge}) = Float64
quantity_type(::Type{DisplacementRotation{Dim}}) where Dim = Vec{2*Dim}
# ============================================================================
@@ -269,9 +298,9 @@ quantity_type(::Type{DisplacementRotation{Dim}}) where Dim = Vec{2*Dim}
element_nodes::AbstractVector{Int}
) -> Nothing
Fill global DOF indices for an element **in-place** based on field type.
Fill global DOF indices for an element in-place based on field type.
**Node-major ordering**: All DOFs for node 1, then node 2, etc.
Node-major ordering: All DOFs for node 1, then node 2, etc.
# Arguments
- `dofs`: Pre-allocated DOF index buffer [ndofs_elem] (output)