# Equation MVP Start with [the consumer sandbox](../tests/sandbox/equations_sandbox.cpp), then read [the barotropic density balance](../src/include/serif/equations/barotropic_density.hpp) and [primal gravity](../src/include/serif/equations/primal_gravity.hpp). The sandbox assembles a manufactured equilibrium on a box; it is not a stellar solver. ## Physics and execution Physics lives in `serif/equations/.hpp`. An equation declares its test term, existing `Operand` dependencies, local residual, explicit partial derivatives, admissibility, and a numerical-order hint. EOS calls occur here, never in assembly. `EquationPoint` and `PointLinearization` are the author-facing input and output types. The `Equation` concept checks this interface without selecting a backend. `EquationRegistry{...}` is the model's list of equations and validates unique residual rows. It does not own coefficients, prepared data, or solver layout. Scalar H1/L2 values and gradients, and vector H1 values/divergences/gradients are executable volume operations. Declaring another operation does not make it executable. Execution lives in `serif/operators`. `EquationPlan` interprets the description; `operators::backend::mfem::make_volume_equation_cache` declares a graph whose ordinary dependency cache owns the accepted linearization. `prepare_volume` supplies current inputs; free residual and Jacobian functions consume the accepted data. `Workspace` is reusable scratch, separate from frozen element data. All equations use the same graph construction and application functions. The physics files do not include MFEM or an operator backend. See [the operator architecture and extension guide](operators.md) for ownership, restrictions, and the numerical implementation. Start with the consumer sandbox to see how a model connects MFEM term bindings, named caches, and the MFEM Jacobian adapter. ## Mathematics and state The density balance is `integral (A(rho) - A(rho_s) + Phi - Omega^2 Psi - U_s) q_rho dV` on stellar domains, with the EOS pressure potential `A`. It reads `Omega` and the surface level `U_s` from global scalar coefficients in the numerical state; its constructor specifies the rotation axis and center. The generic Jacobian applies `d(w f) = w df + f dw`, including the equation's explicit position derivative. Scalar pullback basis functions stay fixed when displacement varies; gradients and divergences also differentiate the mapped operand and test. Packed Jacobian columns follow `EquationPlan::Columns` (unique terms), then displacement. Default preparation uses `PreparationMode::reuse_unchanged` and refreshes only changed dependencies. Explicit `PreparationMode::rebuild` forces the requested graph to refresh. Caller vectors may change or be destroyed after preparation without changing that snapshot. A rejected preparation invalidates the previous snapshot; residual and Jacobian calls then fail until a successful preparation. `try_prepare_volume` returns numerical rejections with element/point context. Incorrect bindings and sizes remain programming errors reported through SERiF's always-active checks. Mesh, spaces, mapping object, term bindings, and compactification grid function must outlive the cache and any accepted data still in use. Rebuild term bindings after mesh topology or space changes. For coordinate edits, notify MFEM with `NodesUpdated()` and prepare again. In-place coordinate changes that bypass MFEM's update notification cannot be detected. Calls sharing MFEM spaces/mapper are serial. `discretization::backend::mfem::TermBinding` associates an existing term with an MFEM space, using its registered support and domain schema. It owns only its supported-DOF indices. It does not define another field catalog. When a general DOF implementation is migrated, consolidate this binding with it rather than retaining two maps. The existing quadrature policy can override each equation's quadrature estimate. The shared rule covers every operand form and includes an estimate for the displacement determinant's degree. Fractional/tabulated EOS and curved mappings still require quadrature convergence checks. ## Gravity contributions [`primal_gravity.hpp`](../src/include/serif/equations/primal_gravity.hpp) contains `PrimalPoissonBalance{four_pi_G}`. The coefficient is supplied by the caller so dimensional and normalized units use the same equation. Its volume residual is ```text integral_all grad(v) . grad(Phi) + four_pi_G integral_star v rho ``` A row can return `contributions()` as a tuple. Each contribution declares a `TestOperand`, `Support`, `Operands`, and a point evaluator returning the coefficient of its test. `Contribution, Support>` supplies the first two aliases and the row term. This keeps the gradient on the test and the restricted source visible in the physics file. The zero potential at infinity is imposed separately. Values are selected with `point.fields.get()` when a term appears once, or `point.fields.get>()` when sampling more than one operation. The divergence of a vector is a scalar. For a vector result use `PointLinearization`; a derivative with respect to a scalar is a three-vector, and with respect to a vector is a 3x3 matrix indexed `[output_component][input_component]`. Existing scalar equations retain their original interface. Use the scalar binding for `gravity::potential` and the restricted density binding. The row uses the same `make_volume_equation_cache` and numerical application functions as every other equation; no gravity-specific MFEM operator is required. ## Scope and regression coverage The backend handles scalar H1/L2 **value**, scalar H1 **gradient** and vector H1 **value/divergence/gradient** volume equations in three dimensions on conforming serial meshes. It rejects unsupported operations and spaces. Scalar boundary constraints, point constraints, global scalars, tensor contractions and complete serial transpose actions are implemented; see [the operator guide](operators.md). H(div), H(curl), surface integrals and MPI assembly remain deferred. The closure numerical tests were in **MeanField**, not yet SERiF. The following contracts are ported using SERiF naming/style and a small serial mesh with core, envelope and vacuum attributes. They exercise the shared volume machinery through a test-only two-field closure, `rho - rho_EOS(h)`, declared in `tests/include/serif/tests/equations/test_equations.hpp`; it is not a production equation: | MeanField test contract | SERiF coverage | | --- | --- | | Representable constant closure; stellar mass matrix | `closure_kernels.cpp` | | Thermodynamic and geometry centered differences | `closure_kernels.cpp`, `closure_conditions.cpp` | | Mapped volume, vacuum exclusion, directional linearity | `closure_kernels.cpp` | | Supported dimensions; reduced/full restriction | `closure_prepared.cpp`, `state_validation.cpp` | | EOS and folded-geometry rejection, then recovery | `closure_prepared.cpp` | | Residual, individual columns, combined and packed actions vs manual assembly | `closure_prepared.cpp` | | Frozen ownership and fresh preparation after rho/h/geometry changes | `closure_prepared.cpp` | | n=1, 1.5, 3 and deformation conditions; balanced combined differences | `closure_conditions.cpp` | | Exact closure remains zero under deformation | `closure_conditions.cpp` | | Context identity/revision invalidation | Shared exact snapshots and binding/mesh validation; explicit full refresh remains available | `manual_closure.cpp` ports the direct quadrature algorithm and evaluates the analytic polytrope independently of the new equation evaluator and cached basis contractions. It is a test reference, not a second production operator. The shared serial tests additionally compare tensor and dense contractions and complete transpose actions; MPI assembly remains outside this port. Additional checks cover tetrahedra/hexahedra, both displacement orderings, analytic dilation volume and derivative, stale bindings, quadrature overrides and a compatible non-polytropic EOS. No new convergence experiments or resolution study are part of this change. Build with `meson compile -C build`, run the full suite with `meson test -C build --print-errorlogs`, or select the new cases with `build/tests/serif_tests '[equations]'`.