diff --git a/docs/src/elements_multiphysics_teaser.md b/docs/src/elements_multiphysics_teaser.md new file mode 100644 index 0000000..5d1ac09 --- /dev/null +++ b/docs/src/elements_multiphysics_teaser.md @@ -0,0 +1,141 @@ +# [Elements, DOF placement, materials, and multiphysics](@id elements_multiphysics_teaser) + +This page is a conceptual teaser for the current (0.x) element pipeline: what the +types mean, how global numbering attaches to an element, how materials +hook in, and how a deliberately mixed DOF specification still follows one +mechanical pattern. + +For API listings, see [API Reference](@ref). For maintainer detail and +invariants, see `AGENTS.md` and the `README.md` files under `src/elements/` +and `src/dofs/`. + +## Element as a typed template + +A finite element is Ciarlet's triple ``(K, P, \Sigma)`` carried almost +entirely in the type system: + +| Role | Julia type | +|------|----------------| +| Reference domain ``K`` | Topology type, e.g. `Tetrahedron{4}`, `Hex8` | +| Local space ``P`` | Basis type, e.g. `Lagrange{1}` | +| Degrees of freedom ``\Sigma`` | `S`, a `@DOFSet{...}` (NamedTuple of `DOF{Quantity, Entity}`) | +| Total local DOF count | Type parameter `N`, inferred from `K` and `S` | + +The runtime struct is minimal: + +```julia +struct Element{K, P, S, N} + id::UInt + dof_indices::NTuple{N, UInt64} +end +``` + +`K` and `P` are not stored; they only refine the type. The only heavy +per-element data is the flat tuple `dof_indices`, one global DOF index per +local DOF, in the canonical order defined below. + +## Where each DOF "lives" + +You declare fields with `@DOFSet`: + +```julia +S = @DOFSet{ + T::DOF{Temperature, Vertex}, + u::DOF{Displacement{3}, Vertex}, + p::DOF{Float64, Cell}, # discontinuous scalar (e.g. pressure bubble) +} +``` + +Each entry is `DOF{Quantity, Entity}`: + +- `Quantity` fixes the number of components per attachment point + (scalar temperature, 3-vector displacement, …) via `dof_size`. +- `Entity` selects the topological anchor: `Vertex`, `Edge`, `Face`, or + `Cell`. + +The constructor for `Element{K,P,S,N}` checks that `N` equals `ndofs(K, S)`. + +`create_elements!(mesh, Element{K,P,S})` builds a `DOFHandler` and fills +each element's `dof_indices` so shared mesh entities reuse the same global +indices. You do not hand-wire indices by hand in normal use. + +## Compile-time layout: `local_dof_layout` + +The order of entries in `dof_indices` is defined by +`local_dof_layout(::Type{Element{K,P,S,N}})`, a `@generated` function that +returns `NTuple{N, DOFLayoutEntry}`. Each entry stores +`(field_idx, entity_local, component)` for one local DOF index. + +Fields are visited in NamedTuple key order. For each field, all vertex +DOFs (if `Entity === Vertex`) are enumerated in local vertex order, then +components; then the next field. A `Cell`-anchored field contributes one +logical "entity" worth of components per element (pressure bubble, …). + +Assemblers and matrix-free operators index this tuple instead of doing +runtime `div`/`mod` arithmetic to decode local DOFs. + +Current limitation: the generated `local_dof_layout` implements `Vertex` +and `Cell` attachments. `Edge` and `Face` are part of the long-term design +(documented in `src/dofs/fields.jl`) but are not wired through the +generator yet; adding them is a localized extension. + +## Materials: traits and kernels + +Materials subtype `AbstractMaterial`. Several open functions form a small +trait layer, for example: + +- `material_behavior(material)` — constant tangent, strain-dependent, + stateful plasticity, … +- `supported_physics(material)` — tuple of physics markers such as + `Elasticity{3}()`, `Thermal{3}()` +- `required_state_variables(material)` — what must live in IP state + storage + +Concrete materials implement these in their own files; see +`src/materials/traits.jl` and `src/materials/api.jl`. + +Important separation: the element type `S` describes which global DOFs exist +and how they are ordered. A kernel decides which physics runs at a +quadrature point and which material(s) supply tangents and forces. + +Example: `ContinuumKernel` pairs one elastic material with a displacement +field. `ThermoElasticKernel` (under `src/domains/thermo_elastic/`) holds +both a mechanical and a thermal material; `evaluate_entry` dispatches on +the `field_idx` pair from `DOFLayoutEntry` so `K_uu`, `K_TT`, and the +coupling blocks are assembled from one element loop. That is the intended +pattern for multiphysics: one `S`, one element storage model, kernels that +know the physics blocks. + +## A deliberately mixed specification + +Thermo-mechanics with temperature and displacement on vertices is the +simplest coupled story (four DOFs per node on a tetrahedron). A harsher +stress test for the DOF machinery is vertex displacement plus a +discontinuous cell pressure (Stokes-style `u`–`p`): + +```julia +S = @DOFSet{u::DOF{Displacement{3}, Vertex}, + p::DOF{Float64, Cell}} +``` + +The test file `test/dofs/test_multifield_dof_system.jl` walks this path +end-to-end: `ndofs` matches `local_dof_layout`, block ordering (all `u` +DOFs, then all `p` DOFs), handler connectivity, and helpers such as +`field_dof_range` / `element_dofs` aligned with the layout tuple. + +Adding more named fields is mostly declaring another `DOF{…, …}` line in +`@DOFSet` and providing a kernel that implements the cross-blocks you care +about. The expensive part is physics and testing, not extending the +element struct. + +## Summary + +- Elements are types plus a flat global index tuple; `S` is the single + source of truth for DOF count and ordering. +- `create_elements!` maps mesh topology to those indices in a type-stable + way. +- Materials expose traits; kernels connect materials to the relevant + `field_idx` blocks via `local_dof_layout`. +- Multifield and mixed `Vertex`/`Cell` anchoring already exercise the + production DOF path; richer entity attachments follow the same layout + idea once implemented.