Note for now much of the docs is LLM written. This was done to save time and focus on code development. The docs have been LOOSLEY reviewed. However, the code itself has been either fully written by hand or fully reviewed. Therefore, the docs should at the moment be seen as a loose resource rather than authoratiative (and this is on top of the general ability for docs to drift)
588 lines
36 KiB
Markdown
588 lines
36 KiB
Markdown
# Applying equations as discrete operators
|
|
|
|
An equation describes local physics and its chosen weak formulation. An operator
|
|
evaluates that description on a discretization to produce a residual `R(u)` and a
|
|
Jacobian action `J(u) du`. New physics using supported operands needs an equation,
|
|
not another bespoke operator.
|
|
|
|
The MFEM volume backend supports global scalar VALUE, scalar H1/L2 VALUE, scalar H1 GRADIENT and vector H1
|
|
VALUE/DIVERGENCE/GRADIENT operands and tests on three-dimensional conforming serial meshes.
|
|
Scalar equations, mass and angular-momentum constraints, and primal gravity use the same volume graph and numerical application functions.
|
|
Surface integrals,
|
|
H(div), H(curl), and parallel assembly remain outside the current scope.
|
|
|
|
## Where things belong
|
|
|
|
| Responsibility | Implementation |
|
|
| --- | --- |
|
|
| Physical laws, parameters, admissibility, local partial derivatives | [`equations`](../src/include/serif/equations/) |
|
|
| Existing field and term identities, spaces, supports, operands | [`discretization`](../src/include/serif/discretization/) |
|
|
| Bind a spatial term to an MFEM space or a global scalar to its mesh | [`TermBinding`](../src/include/serif/backend/mfem/discretization/term_binding.hpp) |
|
|
| Supported coefficient indices, orientation, extraction, assembly | [`DofMap`](../src/include/serif/backend/mfem/discretization/dof_map.hpp) |
|
|
| Traverse an equation's additive contributions in declaration order | [`operators/contributions.hpp`](../src/include/serif/operators/contributions.hpp) |
|
|
| Interpret operand order, row support, and quadrature forms | [`EquationPlan`](../src/include/serif/operators/plan.hpp) |
|
|
| Bind physical terms to coefficient vectors in an ordinary dependency context | [`CoefficientView`, `make_term_coefficients`](../src/include/serif/backend/mfem/discretization/term_coefficients.hpp) |
|
|
| Verify and reuse canonical tensor factors | [`TensorBasisCache`](../src/include/serif/backend/mfem/discretization/tensor_basis.hpp) |
|
|
| Contract a dense basis or tensor factors with pointwise maps | [`BasisEvaluation`](../src/include/serif/backend/mfem/discretization/basis_evaluation.hpp) |
|
|
| Evaluate a supported operation and apply its basis or transpose | [`OperandEvaluator`](../src/include/serif/backend/mfem/operators/operand_evaluation.hpp) |
|
|
| Name volume stages and describe their fixed configuration and results | [`volume_data.hpp`](../src/include/serif/backend/mfem/operators/volume_data.hpp) |
|
|
| Sample bases and operands, map geometry, and evaluate one element's equation | [`element_preparation.hpp`](../src/include/serif/backend/mfem/operators/element_preparation.hpp) |
|
|
| Apply these stages over one contribution's supported elements | [`volume_stages.hpp`](../src/include/serif/backend/mfem/operators/volume_stages.hpp) |
|
|
| Validate and describe current volume sources | [`volume_inputs.hpp`](../src/include/serif/backend/mfem/operators/volume_inputs.hpp) |
|
|
| Declare volume graph nodes and return an ordinary dependency cache | [`volume_computations.hpp`](../src/include/serif/backend/mfem/operators/volume_computations.hpp) |
|
|
| Consume accepted volume data and apply its Jacobian or transpose | [`volume_application.hpp`](../src/include/serif/backend/mfem/operators/volume_application.hpp) |
|
|
| Integrate and differentiate accepted element data | [`element_evaluation.hpp`](../src/include/serif/backend/mfem/operators/element_evaluation.hpp) |
|
|
| Compare exact MFEM coefficient values and persistent binding identities | [`CoefficientTracking`](../src/include/serif/backend/mfem/discretization/coefficient_tracking.hpp) |
|
|
| Track changes, retain accepted results, and publish successful preparations | [`utils::dependencies`](dependencies.md) |
|
|
| Expose the frozen linearization to MFEM solvers | [`JacobianOperator`](../src/include/serif/backend/mfem/operators/jacobian_operator.hpp) |
|
|
|
|
The volume path separates input construction, graph declarations,
|
|
accepted data, and numerical application. The dependency cache is their only
|
|
preparation owner. Operators consume the equation interface; concrete physics
|
|
headers do not depend on operators or MFEM. Geometry and quadrature continue to use
|
|
the existing mapping and discretization modules.
|
|
|
|
A binding identifies a **term**, not a new field catalog and not an operand operation.
|
|
Gravity's potential and gradient are different terms. Multiple operations on a term
|
|
share one coefficient vector and one packed Jacobian column. `Plan::Operands` lists
|
|
unique sampled operations; `Plan::Columns` lists unique coefficient terms.
|
|
|
|
## Reading the calculation
|
|
|
|
`make_contribution_computations` in `volume_computations.hpp` reads in numerical
|
|
order: reference bases, geometry, operand samples, then equation values. Its short
|
|
lambdas call the ordinary numerical functions in `volume_stages.hpp` and
|
|
`element_preparation.hpp`. The declarations themselves do no numerical work;
|
|
`try_prepare` runs the required nodes later.
|
|
|
|
The contribution index distinguishes repeated equations, including repeated instances
|
|
of the same type. Each node returns data for the contribution's supported elements;
|
|
there is no separate C++ graph node for every mesh point.
|
|
|
|
| Result key (`Basis`, `Geometry`, and sample keys are in `backend::volume_keys`) | Declared numerical dependencies |
|
|
| --- | --- |
|
|
| `Basis<I>` | `MeshAndCoefficientBindings`: fixed-mesh coordinate sequence and binding identities; equation and integration options are fixed configuration. |
|
|
| `Geometry<I>` | `Basis<I>`, displacement coefficients, compactification coefficients. |
|
|
| `ReferenceValues<I, Operand>` | `Basis<I>` and the coefficient source identified by `Operand::term_type`. |
|
|
| `PhysicalValues<I, Operand>` | That operand's reference values and `Geometry<I>`; present only when the operand's physical representation depends on geometry. |
|
|
| `QuadratureLinearization<I>` | Basis, geometry, and each declared operand's reference or physical values. Residuals, derivatives, weights, and contribution scaling are computed together. |
|
|
| `Linearization` | Mesh and binding metadata and every contribution's `QuadratureLinearization`; retain ordered batches, assemble the residual, and validate complete integrals. |
|
|
|
|
There are no manual change flags inside these numerical stages. The dependency
|
|
engine follows the declared inputs to decide which stages need execution. For scalar VALUE,
|
|
|
|
```text
|
|
u(q) = sum_j E(q,j) c(j)
|
|
z(q) = W(q) f(u(q), x(q))
|
|
R_element = E_test^T z
|
|
```
|
|
|
|
`element_residual` performs the last contraction. `element_jacobian_action` differentiates
|
|
the same expression. Coefficients, explicit physical position, integration weight,
|
|
and (for gradients and H1 divergence) both operand and test bases vary. Scalar and vector H1 value bases stay fixed.
|
|
The evaluator's `evaluate` method and its transpose contraction work for all supported
|
|
operations by flattening a vector sample's components into three rows per point.
|
|
|
|
`OperandValue<Operand>` describes the sampled shape using **both** term rank and
|
|
operation: `Value(u)` of a vector is `std::array<double, 3>`, while `Divergence(u)` is `double`.
|
|
`PointLinearization<Operands, Result>` supplies scalar/vector/matrix partials of the
|
|
appropriate shape; the default result remains scalar for existing equations.
|
|
|
|
There are two maps. MFEM first maps from its canonical element to the fixed
|
|
computational mesh: RT `CalcVShape(transformation, ...)` and `CalcPhysDivShape(...)`
|
|
already include this map. `OperandEvaluator` then applies SERiF's additional map
|
|
`A = dx/dX`, `j = det(A)`: `g_x = A g_X / j`, `div_x(g_x) = div_X(g_X) / j`.
|
|
The same transformation is applied to the test basis. Geometry variations include
|
|
`delta(A g_X / j)` and `delta(div_X(g_X) / j)` at fixed coefficients, on both sides
|
|
of the contraction. Cached fixed-mesh samples make these variations independent of
|
|
subsequent changes to the caller's coefficient vectors.
|
|
|
|
H1 divergence uses a different transformation: `div_x(u) = trace(G A^-1)`, where
|
|
`G[c,k] = partial_X_k(u_c)`. Its evaluator retains all nine entries of `G` internally,
|
|
then produces one scalar per physical quadrature point. A scalar reference divergence
|
|
alone would lose the information needed under shear or anisotropic dilation. The
|
|
geometry derivative is `trace(G delta(A^-1))`, using the existing mapping variation.
|
|
|
|
Consequently an evaluator's reference sample and physical value can have different
|
|
shapes. It declares both sizes, reads its own reference samples, and advertises
|
|
`geometry_dependent`. Element assembly uses those declarations without branching on
|
|
a particular finite-element family. Coefficient evaluation and test integration
|
|
still share the same matrix-vector and transpose contractions.
|
|
|
|
`prepare_volume` validates the current coefficients, constructs their dependency
|
|
context, and asks the ordinary dependency cache to prepare the graph. The final
|
|
`volume_keys::Linearization` result owns the assembled residual and each contribution's
|
|
immutable element data. Ordinary application functions consume this result; there is
|
|
no second prepared-operator lifecycle.
|
|
|
|
Residual and Jacobian calls do not evaluate the EOS or silently prepare new values.
|
|
Packed columns remain unique terms in first-appearance order (`Plan::Columns`),
|
|
followed by displacement.
|
|
|
|
## Model ownership and startup
|
|
|
|
The model owns caches directly. Fixed discretization, current coefficient inputs,
|
|
and integration choices have separate names. With bindings, coefficient vectors,
|
|
and the mapping already constructed, one volume row can be set up as follows:
|
|
|
|
```cpp
|
|
namespace dependencies = serif::utils::dependencies;
|
|
namespace backend = serif::operators::backend::mfem;
|
|
namespace equations = serif::equations;
|
|
|
|
const auto closure_equation = equations::BarotropicDensityBalance{equation_of_state};
|
|
using Closure = std::remove_cvref_t<decltype(closure_equation)>;
|
|
|
|
const backend::VolumeDiscretization closure_discretization{
|
|
.residual_binding = density_binding,
|
|
.displacement_binding = displacement_binding,
|
|
.compactification = compactification,
|
|
.mapper = mapper
|
|
};
|
|
const auto term_coefficients = backend::make_term_coefficients(
|
|
backend::bind(density_binding, density),
|
|
backend::bind(potential_binding, potential),
|
|
backend::bind(surface_level_binding, surface_level),
|
|
backend::bind(angular_velocity_binding, angular_velocity)
|
|
);
|
|
const auto closure_inputs = backend::make_volume_inputs<Closure>(
|
|
closure_discretization, term_coefficients, displacement
|
|
);
|
|
auto closure_cache = backend::make_volume_equation_cache(
|
|
closure_equation, closure_discretization, closure_inputs
|
|
);
|
|
auto& closure = closure_cache;
|
|
|
|
// Repeat preparation with the current coefficients during nonlinear iterations.
|
|
backend::prepare_volume(closure, closure_discretization, term_coefficients, displacement);
|
|
const auto& linearization = closure.get<backend::volume_keys::Linearization>();
|
|
backend::volume_residual(linearization, density_residual);
|
|
backend::apply_jacobian(linearization, direction, displacement_direction, workspace, action);
|
|
backend::JacobianOperator jacobian(closure);
|
|
```
|
|
|
|
The snippets use `<type_traits>` for `std::remove_cvref_t` and `<utility>` for
|
|
`std::move`. The [equation sandbox](../../tests/sandbox/equations_sandbox.cpp) supplies
|
|
the complete manufactured equilibrium.
|
|
`VolumeOptions` optionally supplies named `quadrature_policy` and `contraction_mode`
|
|
members as the factory's fourth argument.
|
|
|
|
Cache construction declares work without evaluating physics. The graph owns the
|
|
equation, integration options, and reusable preparation scratch. Meshes, spaces,
|
|
term bindings, compactification, and mapper are borrowed and must outlive its uses.
|
|
Coefficient contexts own views, not the coefficient vectors. Keep those vectors
|
|
alive while preparing; accepted numerical results do not borrow their contents.
|
|
The workspace used for Jacobian application remains caller-owned and is not a
|
|
tracked dependency.
|
|
|
|
`make_volume_inputs` captures current mesh/binding metadata and borrows current
|
|
coefficients. Rebuild this context when the discretization changes. Calling
|
|
`prepare_volume` or `try_prepare_volume` does that explicitly on every call and
|
|
validates before requesting the complete linearization. Calling `cache.try_prepare`
|
|
directly is also available with a current, validated context; it is the generic
|
|
engine's interface and permits partial result requests.
|
|
|
|
Moving a cache after an adapter borrows it invalidates that reference. The cache must outlive
|
|
its adapter. The adapter reads the cache's latest accepted linearization on every
|
|
call, so rejection or a request that omits that linearization makes application
|
|
unavailable. References returned by `cache.get` must be reacquired after preparation
|
|
or invalidation.
|
|
|
|
Two adapters can share one cache, while separate caches retain independent
|
|
linearizations. Separate caches are not connected or published as one transaction; a
|
|
controller prepares them explicitly. Shared intermediates can be declared in a common
|
|
graph when that becomes useful.
|
|
|
|
## Equation contributions
|
|
|
|
An equation may describe itself as an additive tuple returned by `contributions()`; the
|
|
primal Poisson balance does so for its flux and source. Contributions share the same
|
|
residual term, while leaves retain independent test operations, supports, and
|
|
quadrature rules. `ContributionsOf` collects leaf types for concept checks and operand
|
|
planning. `operators::for_each_contribution` visits the corresponding leaf objects in
|
|
declaration order, borrowing the owned objects, including equations that cannot be copied.
|
|
|
|
## Global scalars: mass, angular momentum, C, and Ω
|
|
|
|
`barotropic_constant::mass_normalization` stores the unknown Bernoulli constant C and
|
|
receives the mass constraint. `rotation::angular_velocity` stores the signed angular
|
|
speed Ω about a prescribed axis and center. They are explicitly declared
|
|
`GlobalScalarTerm`s, with one coefficient each and no finite-element space. Other
|
|
nonspatial descriptors, such as surface-deformation coefficients, are not implicitly
|
|
admitted as scalars.
|
|
|
|
```cpp
|
|
using C = serif::discretization::blocks::barotropic_constant::mass_normalization;
|
|
using Omega = serif::discretization::blocks::rotation::angular_velocity;
|
|
using serif::discretization::backend::mfem::TermBinding;
|
|
namespace backend = serif::operators::backend::mfem;
|
|
|
|
TermBinding<C, Schema> c_binding(mesh);
|
|
TermBinding<Omega, Schema> omega_binding(mesh);
|
|
mfem::Vector c(1), omega(1);
|
|
c = surface_level;
|
|
omega = angular_velocity;
|
|
const auto term_coefficients = backend::make_term_coefficients(
|
|
backend::bind(density_binding, density),
|
|
backend::bind(potential_binding, potential),
|
|
backend::bind(c_binding, c),
|
|
backend::bind(omega_binding, omega)
|
|
);
|
|
const backend::VolumeDiscretization mass_discretization{
|
|
.residual_binding = c_binding,
|
|
.displacement_binding = displacement_binding,
|
|
.compactification = compactification,
|
|
.mapper = mapper
|
|
};
|
|
const auto mass_inputs = backend::make_volume_inputs<serif::equations::MassNormalization>(
|
|
mass_discretization, term_coefficients, displacement
|
|
);
|
|
auto mass = backend::make_volume_equation_cache(
|
|
serif::equations::MassNormalization{target_mass}, mass_discretization, mass_inputs
|
|
);
|
|
backend::prepare_volume(mass, mass_discretization, term_coefficients, displacement);
|
|
|
|
const backend::VolumeDiscretization angular_momentum_discretization{
|
|
.residual_binding = omega_binding,
|
|
.displacement_binding = displacement_binding,
|
|
.compactification = compactification,
|
|
.mapper = mapper
|
|
};
|
|
const auto angular_momentum_inputs = backend::make_volume_inputs<serif::equations::AngularMomentum>(
|
|
angular_momentum_discretization, term_coefficients, displacement
|
|
);
|
|
auto angular_momentum = backend::make_volume_equation_cache(
|
|
serif::equations::AngularMomentum{target_momentum, axis, center},
|
|
angular_momentum_discretization, angular_momentum_inputs
|
|
);
|
|
backend::prepare_volume(angular_momentum, angular_momentum_discretization, term_coefficients, displacement);
|
|
```
|
|
|
|
The scalar basis is one at every quadrature point. The same basis contractions,
|
|
geometry derivatives, coefficient validation, frozen snapshots, and packed adapter
|
|
serve spatial and global terms. A scalar row sums every supported element into its
|
|
single entry; there is no artificial order-zero FE space or per-element scalar DOF.
|
|
Global scalar reductions compensate cancellation at quadrature points and across
|
|
elements, preserving the old column-sum accuracy. Its mesh, support, and lifetime
|
|
checks still apply. Preparation also rejects overflow in the integrated scalar
|
|
residual, including its offset, before publishing the candidate. Calling `.space()` on a global
|
|
binding is an error; common assembly uses `.mesh()` and `.full_size()`.
|
|
|
|
`MassNormalization` evaluates rho over the stellar domains. Its `residual_offset()`
|
|
returns `-target_mass`, which final volume assembly adds **once after integration**.
|
|
This optional hook is restricted to global scalar rows and describes a constant,
|
|
state-independent offset. Arithmetic scales the offset along with its contribution;
|
|
the offset has no density or geometry derivative. The mass row depends on density
|
|
and displacement, not on C directly. C influences the density-balance row.
|
|
|
|
[`AngularMomentum`](../src/include/serif/equations/angular_momentum.hpp) receives the
|
|
Ω row and evaluates `rho Omega s^2`, where `s` is distance from the prescribed
|
|
physical axis through `center`. Its offset is `-target_momentum`. The target must be
|
|
finite and nonnegative; trial angular speeds may be signed or zero. It reuses
|
|
`RigidRotation` for `s^2 = 2 Psi_unit` and its position derivative. The equation
|
|
supplies `dF/drho = Omega s^2`, `dF/dOmega = rho s^2`, and
|
|
`dF/dx = 2 rho Omega grad(Psi_unit)`; the existing backend differentiates volume.
|
|
|
|
An equation may also define `validation_integrand(point, linearization)` and
|
|
`validate_integral(value)`. During the existing quadrature loop the backend sums
|
|
that scalar measure with the physical integration weight, then validates its total
|
|
over the contribution's support before publishing the preparation. Throwing
|
|
`std::domain_error` rejects the trial state. Each leaf is validated separately. Angular momentum uses its already computed
|
|
`dF/dOmega` to reject a nonfinite or negative total moment of inertia, including
|
|
at Ω = 0; it does not require every local trial density to be positive.
|
|
|
|
The density-balance constructor accepts an axis (default z) and center (default zero),
|
|
not C or Ω. The axis is normalized once. The balance reads Ω and C from the state.
|
|
It implements `d(A(rho) + Phi - Psi - C)/dC = -1` and `dPsi/dOmega = 2 Omega Psi_unit`.
|
|
These derivatives are defined at Ω = 0 without dividing by Ω. `RigidRotation` remains a
|
|
fixed-vector utility for independent calculations.
|
|
|
|
Packed density-balance columns are `rho, Phi, C, Omega, displacement`. Mass columns are
|
|
`density, displacement`; angular-momentum columns are `density, Omega, displacement`.
|
|
Each global scalar column occupies one entry. Directions
|
|
must bind the same scalar descriptors, including an explicit zero when a control is
|
|
held fixed. Reprepare after changing its value, as with spatial coefficients.
|
|
|
|
The mass port retains the old `1e-12` integral comparison, `5e-7` affine scaling,
|
|
`3e-8` density finite difference at step `1e-3`, `2e-7` geometry finite difference at
|
|
step `1e-6`, `2e-11` independent complete action, and `2e-15` column sum.
|
|
Serial transpose actions and preparation work reports are available. MPI reductions
|
|
and coupled root-layout routing remain deferred.
|
|
|
|
The angular-momentum port retains the old `2e-13` moment comparison, `7e-7`
|
|
fifth-power dilation law, `3e-13` parallel-axis identity, `2e-11` independent action,
|
|
`3e-15` column sum, and `2e-15` angular-speed action comparison. Density, geometry,
|
|
and angular-speed differences retain thresholds `4e-8`, `4e-7`, and `2e-10` at
|
|
steps `1e-3`, `1e-6`, and `1e-6`. Tests exercise tilted and displaced axes, both
|
|
element types and vector orderings, a curved star, frozen snapshots, rejection and
|
|
recovery, and default quadrature. Serial cache behavior is checked through the shared preparation reports. Distributed/root-layout
|
|
tests await their corresponding infrastructure.
|
|
The supported physical part of the old rotating-system test also checks one shared
|
|
Ω across density-balance and invariant rows (`3e-7` and `3e-10` at step `1e-5`),
|
|
its zero columns in mass, the test closure, and gravity, and the different
|
|
zero-speed limits. Seed generation, surface projection, and root manifests are not
|
|
part of this volume-row port.
|
|
|
|
## Prescribed surface pressure
|
|
|
|
[`Isobaric`](../src/include/serif/geometry/surface/isobaric.hpp) prescribes a typed
|
|
`PressureValue`. It describes the physical condition without selecting enthalpy or
|
|
any other field. The model's surface compiler selects a formulation from the
|
|
available thermodynamic fields, solved equation rows, and EOS relations:
|
|
|
|
1. Use a pressure unknown directly when one is available.
|
|
2. Otherwise use a declared inverse EOS relation to resolve a boundary field.
|
|
3. Otherwise evaluate a declared forward pressure relation and subtract the target.
|
|
|
|
An unavailable or ambiguous choice is rejected at compile time. The compiler uses
|
|
the EOS's declared relations; it does not numerically invert an EOS. A preferred
|
|
surface field can narrow the choice when the physical model admits several rows.
|
|
See [`AutomaticPressureSurface`](../src/include/serif/geometry/surface/automatic.hpp).
|
|
|
|
For a polytrope using density, the selected condition reduces to
|
|
|
|
```text
|
|
R_i = rho_i - rho_EOS(P_surface)
|
|
delta R_i = delta rho_i
|
|
```
|
|
|
|
Other EOS relations may depend on temperature, composition, or other bound fields.
|
|
Their values and derivatives are evaluated from the current boundary state. Zero
|
|
pressure also goes through the selected relation; its carrier value need not be zero.
|
|
A pressure unknown needs no EOS evaluation at all.
|
|
|
|
The finite-element backend compiles traces of the required fields at the selected
|
|
carrier nodes on `StellarSurfaceBoundary`. These fields may have different spaces
|
|
and polynomial orders. The scalar carrier must have a supported nodal H1 row;
|
|
Bernstein/positive coefficients are rejected because they are not point values.
|
|
|
|
For standalone operator work, the shared boundary graph can be used directly.
|
|
Here `Formulation` is the selected formulation, `eos_source` explicitly tracks the
|
|
EOS parameters, and `coefficients` binds the fields required by that formulation:
|
|
|
|
```cpp
|
|
namespace physics = serif::geometry::surface;
|
|
namespace surface = physics::backend::mfem;
|
|
namespace dependencies = serif::utils::dependencies;
|
|
|
|
const auto boundary = surface::make_pressure_boundary<Formulation>(coefficients);
|
|
const auto inputs = surface::make_pressure_boundary_inputs(
|
|
boundary, eos_source,
|
|
physics::Isobaric{serif::dimensions::PressureValue{0.03125}}, coefficients
|
|
);
|
|
auto cache = dependencies::make_computation_cache<physics::SurfaceFailure>(
|
|
surface::make_pressure_boundary_graph<Formulation>(), inputs
|
|
);
|
|
const auto prepared = cache.try_prepare(inputs);
|
|
if (prepared) {
|
|
const auto& linearization = cache.get<physics::keys::Linearization>();
|
|
// First assemble the volume residual and Jacobian action into these vectors.
|
|
surface::apply_residual_rows(*boundary, linearization, residual);
|
|
surface::apply_jacobian_rows(*boundary, linearization, direction, action);
|
|
}
|
|
// Handle prepared.error() before using a result from a rejected trial.
|
|
```
|
|
|
|
The graph tracks pressure, EOS parameters, and field coefficients. Preparation
|
|
owns the evaluated residuals and derivatives until the next preparation, so later
|
|
input changes do not silently change an already prepared action. Invalid numerical
|
|
states return a `SurfaceFailure`; invalid or stale bindings remain programming
|
|
errors. Both `coefficients` and `direction` must use the bindings that compiled the
|
|
traces, and those bindings and borrowed values must remain alive.
|
|
|
|
Apply boundary replacement **after** volume assembly. It removes every volume
|
|
coupling at a selected row and inserts every derivative required by the chosen
|
|
surface relation. For the transpose, mask selected residual-dual entries before
|
|
applying the volume transpose, then add the boundary transpose to each affected
|
|
field. Interior rows remain untouched. There is no surface measure or quadrature
|
|
weight in this strong condition. The model's assembled operator performs this
|
|
composition; a standalone volume operator alone does not.
|
|
|
|
The pressure regressions retain the MeanField residual/action comparisons at
|
|
`1e-13`, the analytic closure/density-balance thresholds of `2e-12`, and the mass
|
|
threshold of `2e-11 * target_mass`. The constrained density-balance finite difference
|
|
retains step `1e-5` and threshold `2e-6`. Analytic boundary values and independent
|
|
MFEM boundary masks check the shared graph on tetrahedra, hexahedra, both vector
|
|
orderings, and a curved star. General surface tests also cover non-enthalpy EOS
|
|
relations, state-dependent couplings, transpose actions, cache invalidation,
|
|
rejection, and recovery.
|
|
|
|
## Selective serial preparation
|
|
|
|
Preparation defaults to `PreparationMode::reuse_unchanged`, matching the generic
|
|
dependency engine. Request a full refresh explicitly with `PreparationMode::rebuild`:
|
|
|
|
```cpp
|
|
backend::prepare_volume(closure, closure_discretization, term_coefficients, displacement);
|
|
const auto report = backend::volume_work_report(closure);
|
|
// report.geometry_points, sampled_operands, physics_points and reused_elements
|
|
|
|
backend::prepare_volume(
|
|
closure, closure_discretization, term_coefficients, displacement,
|
|
backend::PreparationMode::rebuild
|
|
);
|
|
```
|
|
|
|
The dependency engine owns exact coefficient snapshots and accepted results.
|
|
`make_volume_inputs` supplies `MeshAndCoefficientBindings`, displacement,
|
|
compactification, and the equation plan's coefficient terms as source roles.
|
|
`CoefficientTracking` identifies each source by its persistent binding and compares
|
|
coefficient contents. Temporary context wrappers and copied equal vectors therefore
|
|
retain valid work. A changed operand refreshes its samples and the physics of
|
|
contributions using that operand. Other contributions retain their accepted data.
|
|
Displacement or compactification changes refresh mapping and mapped bases while
|
|
retaining unaffected fixed-mesh coefficient samples. A fixed-mesh coordinate change
|
|
rebuilds those bases too. No coefficient hashes or caller-maintained coefficient
|
|
revision counters are required.
|
|
|
|
Reuse assumes fixed equation configuration and any external data it references.
|
|
Request `PreparationMode::rebuild`, or invalidate the cache, after modifying external
|
|
EOS data; changes hidden behind a runtime EOS view cannot be discovered automatically.
|
|
Changing owned equation parameters requires a new graph. `preparation_count()` counts
|
|
successful generic cache preparations, including reuse and partial requests. Failed
|
|
attempts do not increment it; invalidation preserves the count. Numerical work counts
|
|
are derived from the most recent graph report, rather than separately stored state.
|
|
|
|
Inspect any cache directly through the dependency API:
|
|
|
|
```cpp
|
|
namespace stages = backend::volume_keys;
|
|
const auto& graph_report = closure.report();
|
|
const auto basis_status = graph_report.status<stages::Basis<0>>();
|
|
const auto geometry_status = graph_report.status<stages::Geometry<0>>();
|
|
const auto physics_status = graph_report.status<stages::QuadratureLinearization<0>>();
|
|
```
|
|
|
|
Statuses are `computed`, `reused`, or `not_required`. The numerical work report
|
|
summarizes these statuses in element and quadrature counts. Volume helpers request
|
|
the whole graph; `volume_keys::Linearization` retains accepted contributions and the
|
|
assembled residual together. Direct dependency requests can prepare selected
|
|
intermediates, but application requires the final linearization to be available.
|
|
|
|
Rejection makes current cache results unavailable. The generic engine keeps its last
|
|
accepted storage for possible later reuse; backend preparation helpers additionally
|
|
invalidate that storage on validation or numerical failure, ensuring a fresh retry.
|
|
Neither path publishes a partly prepared result. Reacquire accepted data after every
|
|
successful preparation rather than retaining a reference across cache updates.
|
|
|
|
Reuse depends only on unchanged tracked inputs. A recomputed node advances its
|
|
version even when it happens to produce the same value, so required downstream
|
|
computations also refresh. There is no output-equivalence test. Coefficient lookup
|
|
uses the ordinary dependency context; operand-list deduplication uses `CombinedKeyList`.
|
|
|
|
## Tensor contractions and complete transposes
|
|
|
|
On eligible H1/L2 and RT hexahedra, the backend verifies Cartesian quadrature ordering,
|
|
native signed DOF maps, and the factorization against MFEM basis values and derivatives.
|
|
It then performs successive one-dimensional contractions. H1 gradients and RT divergence
|
|
use the same factors and transpose routine. Owned tables are shared within one volume graph
|
|
and keyed by finite-element identity, operation and complete quadrature contents.
|
|
Temporary or modified rules cannot leave borrowed table data behind.
|
|
|
|
Small pointwise maps apply MFEM's canonical-to-fixed-mesh transformation and SERiF's
|
|
additional physical transformation. Tetrahedra, non-Cartesian rules and unverified
|
|
factorizations keep the dense path. Neither path changes the quadrature rule. Supply
|
|
`VolumeOptions{.contraction_mode = ContractionMode::dense}` to the cache factory
|
|
to use the dense reference path explicitly. `tensor_element_count(linearization)`
|
|
reports how many accepted contribution-elements use tensor contractions, including geometry.
|
|
|
|
`apply_jacobian_transpose(linearization, residual_dual, workspace, packed_result)` returns all
|
|
coefficient duals in `Plan::Columns` order followed by displacement. `JacobianOperator`
|
|
also implements MFEM's `MultTranspose`. The transpose includes weight, explicit
|
|
position, operand mapping, and test mapping derivatives. Geometry is differentiated
|
|
through twelve local inputs: three displacement values and nine canonical derivatives.
|
|
The mapping module supplies their exact directional variations; no finite differences
|
|
or global Jacobian-column probing are used in production.
|
|
|
|
A strong nodal replacement requires the following transpose order:
|
|
|
|
```cpp
|
|
backend::mask_residual_dual(boundary, residual_dual, volume_dual);
|
|
backend::apply_jacobian_transpose(volume, volume_dual, workspace, packed_dual);
|
|
// Obtain the constrained term's block from packed_dual using the volume plan's column order.
|
|
backend::add_jacobian_transpose_rows(boundary, residual_dual, constrained_term_dual);
|
|
```
|
|
|
|
Mask residual rows before the volume transpose, then add the constraint transpose to
|
|
the appropriate coefficient block. Zeroing coefficient entries after the volume
|
|
transpose would incorrectly remove contributions from unconstrained residual rows.
|
|
For centering, the constrained coefficient block is the displacement geometry block.
|
|
These remain serial coordinate transposes; no mass-matrix identification of duals or
|
|
MPI true-DOF ownership is implicit.
|
|
|
|
Tensor tests retain the
|
|
old scalar `2e-12` and gradient/RT `2e-11` comparisons. Full equation transposes are
|
|
checked against dense contractions, individual Jacobian columns, the old mass-adapter
|
|
`2e-12` pairing threshold, both element types/orderings, and curved compactified geometry. Existing tests and numerical tolerances are unchanged.
|
|
|
|
## Restrictions and what depends on them
|
|
|
|
| Boundary | Enforcement | Why the numerical code needs it |
|
|
| --- | --- | --- |
|
|
| Authoring interface | `equations::Equation` | Physics supplies the declared pointwise input/output contract. This does not promise execution support. |
|
|
| Current volume algorithm | `operators::VolumeEquation` | Every contribution needs a volume-domain support and implemented test/input operations. `ScalarVolumeEquation` remains available for existing callers. |
|
|
| Requested operand evaluation | `OperandEvaluator<Operand>` specialization; `EvaluableOperand` queries its interface | There is no generic fallback that ignores the operation tag. An unsupported operation cannot silently become VALUE sampling. |
|
|
| Actual MFEM element used for sampling | Always-active range/map checks in `sample_basis`; gradients and H1 divergence additionally require a transformation | Only scalar VALUE bases (one or three copies) are sampled. |
|
|
| Actual space bound to a term | `TermBinding`: family/rank compile-time checks; dimension, continuity, order, range, mapping runtime checks | Coefficient layout and basis representation must match the declared term. A valid binding still needs a supported operation; vector H1 VALUE and DIVERGENCE are implemented; GRADIENT and CURL remain unsupported. |
|
|
| Supported DOF mapping | `DofMap` checks serial conforming 3D spaces, sizes, support, orientation, and revisions | Extraction and assembly assume this representation. |
|
|
| Selected boundary rows | `select_boundary_dofs` and `DofSelection::validate` | Boundary tags must exist in the schema, have supported DOFs, and remain unchanged during use. |
|
|
| Cross-binding consistency | `make_volume_inputs` and the basis stage check schema, mesh, binding identities, and input support | Every operand must cover its contribution's integration support, which may be narrower than the row. No operand is silently extended into vacuum. |
|
|
| Inadmissible geometry or physics | Element preparation returns `PreparationRejection` with element and point; optional integral validation rejects at contribution level | A nonlinear trial state can be rejected without publishing incomplete data. Integral rejections have no single element/point. |
|
|
| Missing or stale preparation | Dependency result availability and `validate_volume_data` | Cached matrices and geometry must match the borrowed discretization. |
|
|
|
|
Malformed bindings and coefficient sizes are programming errors. Numerical trial-state
|
|
rejections are values returned from `try_prepare_volume`. The corresponding `prepare_*` convenience functions
|
|
translate a rejection for the caller. Physical admissibility
|
|
remains the equation/EOS author's responsibility.
|
|
|
|
## Extending the implementation later
|
|
|
|
For new physics expressible using supported operands, change only the equation and
|
|
model selection. Keep local physical partial derivatives in the equation; mesh and
|
|
mapping derivatives belong to numerical execution.
|
|
|
|
For a new space representation, start with `TermBinding` and the existing space
|
|
descriptors. For a new operation, start with `OperandEvaluator`: its result shape,
|
|
basis evaluation, coefficient action, test contraction, and geometry dependence must
|
|
agree. Then examine the element residual and its Jacobian together. In particular,
|
|
the existing gradient and H1 divergence implementations show why both operand and test
|
|
variations are needed. A new operation must provide these mathematics before its capability
|
|
check can admit it.
|
|
|
|
The volume admission contract should expand only after those calculations,
|
|
point/cache shapes, and quadrature handling exist. Unsupported operations remain
|
|
compile-time failures. There is no separate hand-maintained capability registry.
|
|
|
|
Compile-time checks in [`operators/plan.cpp`](../src/compile_time_checks/serif/operators/plan.cpp)
|
|
cover supported scalar, gradient and H1 divergence operations, sampled result shapes, packed columns,
|
|
and rejection of unsupported operations. Authoring an equation remains distinct from
|
|
executing it with this backend.
|
|
The existing equation tests retain independent quadrature comparisons, analytic
|
|
dilation checks, centered differences, ownership checks, and their original tolerances.
|
|
|
|
## Gravity without a separate assembly path
|
|
|
|
[`primal_gravity.hpp`](../src/include/serif/equations/primal_gravity.hpp) returns its weak
|
|
Poisson residual as a tuple of contributions: the potential-gradient flux on all domains and
|
|
the density source on `StellarDomains`. `Contribution<TestOperand, Support>` is an optional
|
|
authoring helper; each contribution supplies `Operands`, `evaluate`, and `additional_order`.
|
|
All contributions share the row term, but may select different test operations and supports.
|
|
Existing single-contribution equations need no wrapper. The source's `StellarDomains` support
|
|
prevents reading density on vacuum elements. This is volume-operator support, not a new
|
|
boundary-condition API.
|
|
|
|
## API migration
|
|
|
|
| Previous name | Current name |
|
|
| --- | --- |
|
|
| `equations::backend::mfem::EquationField` | `discretization::backend::mfem::TermBinding` |
|
|
| `equations::backend::mfem::FieldDofs` | `discretization::backend::mfem::DofMap` |
|
|
| `equations::backend::mfem::FieldState` | `operators::backend::mfem::CoefficientView` |
|
|
| `EquationState`, `State` | `make_term_coefficients(bind(...), ...)` returns a dependency context |
|
|
| `PreparedEquation`, `PreparedOperator`, `VolumeComputations` | `make_volume_equation_cache`, `prepare_volume`, and free application functions |
|
|
| `equations::EquationPlan` | `operators::EquationPlan` |
|
|
| `EQWorkspace`, `EQElementData` | `operators::backend::mfem::Workspace`, `ElementData` |
|
|
|
|
`bind(binding, coefficients)` now produces a view with `.binding` and `.values`.
|
|
Pointwise equation inputs and physical derivatives retain their existing interface.
|