This page documents the first field-infrastructure phase: separate typed identity and metadata catalogs for persistent Eulerian and solver-particle fields. The catalogs remove repeated field-name dispatch from ghost updates, periodic synchronization, field diagnostics, initialization, and Eulerian/particle coupling without changing vector creation, numerical stencil/repair algorithms, checkpoint formats, runtime configuration, or postprocessing recipe syntax.
The catalog provides:
FieldId, a compile-time identity for each field formerly recognized by UpdateLocalGhosts;FieldDescriptor, immutable metadata describing name, aliases, degree of freedom, DM family, grid layout, ghost-repair class, availability conditions, capabilities, and the existing UserCtx vector binding;FieldView, a non-owning runtime view of an already-created DM/global/local vector tuple;The Eulerian catalog covers the 38 persistent fields supported by the previous ghost dispatcher. The separate particle catalog covers the ten persistent DMSwarm fields used by the solver, including PETSc-managed particle ID and rank fields. Adding a field to a supported operation now requires metadata and a typed call site rather than another string branch.
Field IDs are not assigned after PETSc vector creation. They are enum constants known when the program is compiled. Storage is still created by the existing setup lifecycle:
Particle identities are likewise compile-time constants. During RegisterParticleFields, PICurv-owned descriptors drive the existing DMSwarm registration helper; PETSc-owned DMSwarm_pid and DMSwarm_rank descriptors are recorded but deliberately not re-registered. Particle IDs do not depend on particle allocation order or on the number of local particles.
This separation is deliberate. FieldId identifies a concept, while FieldView reports whether that concept has storage in a particular UserCtx. Optional fields remain valid catalog entries even when their runtime conditions are disabled; asking for their view then returns a wrong-state error instead of silently dereferencing a null PETSc object.
Each FieldDescriptor records:
| Member | Meaning |
|---|---|
id | Typed identity used by compiled consumers |
canonical_name | Stable printable/configuration-facing name |
alias_1, alias_2 | Existing accepted names resolved only at ingress |
dof | PETSc degrees of freedom per DMDA entry: 1, 2, or 3 |
dm_kind | da, fda, fda2, or PETSc coordinate storage |
layout | node, shifted cell, I/J/K face, or component-staggered |
sync_class | normal-face repair policy after global-to-local scatter |
availability | setup conditions under which storage may exist |
capabilities | operations for which the field is registered |
| vector offsets | binding to the existing global/local UserCtx members |
The availability flags describe requirements; they do not replace runtime state validation. FieldGetView always checks the actual DM and vector handles.
ParticleFieldDescriptor is separate because DMSwarm fields have a different contract: canonical PETSc name, component count, PetscDataType, registration owner, default initialization value, supported operations, and an optional Eulerian scatter target. In particular, particle Psi maps explicitly to Eulerian FIELD_ID_PSI; no name coincidence is used to infer that bridge.
The registered aliases are retained because current diagnostic and boundary surfaces use them: Eddy Viscosity for Nu_t, Cs for CS, Friction Velocity for Utau, Wall Eddy Viscosity for NuWall, Center-Coordinates for Cent, and the X/Y/Z-Face-Centers names for Centx/Centy/Centz. Aliases do not create additional field identities.
Utau and NuWall exist only while a wall model is active, and neither is restored on restart: the first boundary pass of a restarted run recomputes both from the restored velocity field. Utau is written to checkpoints so postprocessing can read it; NuWall is not written at all, being rebuilt by every wall pass within a step.
CS stores the LES model coefficient C, the factor multiplying Delta^2 |S| in the eddy viscosity. In the classical notation that is Cs^2, not Cs, and under clipping.mode: none it is signed, because a negative coefficient is backscatter. Its Cs alias is a display name inherited from the diagnostic surfaces, not a statement about the stored quantity; readers wanting Cs itself should take the signed square root, which is what 8. Reading the Diagnostics reports. The field carries FIELD_AVAILABILITY_LES_DYNAMIC and therefore exists only for dynamic_smagorinsky; the constant model reads its coefficient from configuration and allocates no field.The catalog's topology is based on the solver's shifted/staggered architecture, not just PETSc's owned-versus-ghosted partition.
| Catalog layout | Physical interpretation | Logical boundary convention |
|---|---|---|
FIELD_LAYOUT_NODE_CENTERED | grid nodes | direct indices; the extra high-side slot is not a physical node |
FIELD_LAYOUT_CELL_CENTERED | cell-centered value | physical values occupy shifted indices 1..N-1; indices 0 and N are boundary/dummy locations |
FIELD_LAYOUT_I_FACE | one I-face family | node-like in I, cell-like in J and K |
FIELD_LAYOUT_J_FACE | one J-face family | node-like in J, cell-like in I and K |
FIELD_LAYOUT_K_FACE | one K-face family | node-like in K, cell-like in I and J |
FIELD_LAYOUT_COMPONENT_STAGGERED | Ucont-style packed vector | x/y/z components live on I/J/K faces respectively |
Therefore, a local vector may contain two different categories that must not be confused:
UpdateLocalGhosts still calls the established DMGlobalToLocalBegin/End pair. It then uses sync_class, instead of a field name, to select the existing normal-face repair for I-, J-, K-face, or packed component-staggered storage. Standard shifted-cell fields retain the same PETSc scatter behavior.
Periodicity does not create a second identity for a field. It changes the runtime DM topology and which physical boundary repairs are active:
InitializeAllGridDMs still constructs the actual periodic/nonperiodic DMDA;UserCtx;Boundaries.c are unchanged.The periodic cell, single-face-family, and component-staggered synchronizers now accept FieldId arrays. Catalog capabilities reject a field routed to an incompatible synchronizer before the unchanged numerical copy loop runs. Coordinate-face translation is selected through FIELD_CAPABILITY_PERIODIC_GEOMETRY_SHIFT, not a field-name comparison. No string comparison remains in the periodic field dispatcher.
The catalog groups fields by layout as follows:
Coordinates, CellScalarAtCorner, and CellVectorAtCorner;Ucat, P, Nu_t, CS, Utau, NuWall, Diffusivity, DiffusivityGradient, Nvert, Aj, Cent, GridSpace, Phi, Psi, Nvert_o, ParticleCount, PostScalar, PostVector, and Qcrit;Ucont, Ucont_o, and Ucont_rm1;Csi, Centx, ICsi, IEta, IZet, and IAj;Eta, Centy, JCsi, JEta, JZet, and JAj;Zet, Centz, KCsi, KEta, KZet, and KAj.Four of those entries are staging workspaces rather than solver state, and none of them is checkpointed. CellScalarAtCorner and CellVectorAtCorner hold the result of writing cell-centered data onto grid corners, which is why they are node-centered by construction; they are transient scratch rebuilt on every conversion. PostScalar and PostVector stage a derived post-processing statistic, which is config-counted and therefore has no compile-time offset of its own; staging it under a catalog identity lets the existing ghost, nodal-average, and logging paths address it by name instead of each growing a second, view-based entry point. Qcrit is catalogued for the same reason: the post-processor computes it at cell centres, and only a catalog identity lets nodal_average refresh its ghosts by name and average it to nodes, since a .vts carries point data only.
The particle inventory is: position, velocity, DMSwarm_CellID, weight, Diffusivity, DiffusivityGradient, Psi, DMSwarm_location_status, DMSwarm_pid, and DMSwarm_rank. It is intentionally not mixed into FieldId: particle fields are per-particle DMSwarm arrays, may be integer-valued, and have no persistent UserCtx global/local Vec pair. Dynamic postprocessor fields such as recipe-created ske or disp remain outside the persistent solver-particle catalog.
Compiled code should pass IDs directly:
Code that genuinely receives text should resolve it once at its ingress:
Consumers must not cache a FieldView beyond the lifetime or re-creation of the corresponding UserCtx PETSc objects. A view is non-owning and must never be destroyed independently.
PETSc's DMSwarm API itself remains name-based. Code at that API boundary obtains the canonical text with ParticleFieldName; this does not require assigning particle fields an Eulerian FieldId or wrapping every PETSc access function. Generic gather/restart/output helpers also remain name-based for dynamic postprocessor fields, but query the registered PetscDataType from DMSwarm instead of inferring integer width from a field-name comparison.
The catalog deliberately does not:
CreateAndInitializeAllVectors, setup ordering, or teardown ordering;Runtime configuration strings and postprocessing recipe keywords are true text ingress and remain textual. Function identity/profiling is intentionally deferred to a later specification; it is not conflated with field identity.
These boundaries prevent a common-infrastructure change from silently becoming a numerical, monitoring, I/O, or statistics change.
make unit-setup contains direct catalog tests for:
FieldId;Psi/Eulerian-Psi bridge;Existing unit-periodic, MPI, postprocessing, and runtime smoke tests remain the behavioral regressions for the unchanged numerical paths.