Fluxes, sources and eigenvalues¶
A model in pops is, at its core, three pointwise functions: a flux that transports
matter across cell faces, a source that adds or removes it in place, and a set of
eigenvalues that tells the time stepper how fast information travels. This page explains
what each piece means, why they are kept separate, and how the numerical flux turns the
physical flux into a face value.
What the core solves¶
The hyperbolic part of every model has the same shape:
Here U is the evolved state of a cell (a density, or a density and its momenta), F is
the flux, and S is the source. The aux channel carries the fields the elliptic solver
produces (the potential phi and its gradient, and optionally B_z or T_e). Both F
and S read aux, which is the single idea that lets one spatial operator serve both a
drift transport (where the coupling enters the flux) and a self-gravitating fluid (where it
enters the source).
The flux: transport across a face¶
The flux answers “how much of U crosses a face, in which direction”. For scalar advection
by the ExB drift it is the density times the drift velocity; for compressible Euler it is the
usual mass, momentum and energy flux. You write it once, as a pure function of the local state.
The flux is not evaluated naively at a face, because two cells meet there with two different
states. That meeting is a local Riemann problem: a small discontinuity whose evolution the
scheme must approximate. The physical flux F describes the equation; the numerical flux
decides how to resolve the discontinuity.
The numerical flux as a policy¶
The numerical flux is a separable choice, not a property of the model. The same model can run under any of four resolvers, ordered by how many waves they reconstruct:
Policy |
Resolves |
Trade-off |
|---|---|---|
Rusanov |
one diffusion bump |
most robust, most diffusive, needs only a wave speed |
HLL |
two signal speeds |
one star region, still smears the contact |
HLLC |
the contact wave |
sharper; canonical Euler 2D, or generic via the model’s HLLC hooks |
Roe |
all waves, linearized |
sharpest, needs an entropy fix and a pressure; generic via the model’s Roe hook |
Rusanov is the safe default: it reads only max_wave_speed, so it applies to any model,
including a plain scalar transport. The cost of that robustness is added numerical diffusion,
which can erode a growth rate you care about. HLLC and Roe default to the canonical four-variable
Euler state (a declared pressure), but are generic when the model supplies the capability hooks
(HasHLLCStructure / HasRoeDissipation, emitted in the DSL with m.enable_hllc() /
m.enable_roe()); they sharpen the contact discontinuity at the price of a narrower domain of
safety. You pick the policy where you build the spatial operator,
independently of the reconstruction limiter (see the models guide).
The source: change in place¶
The source S adds to a cell without moving anything across a face: a potential force on the
momentum, a gravitational pull, a relaxation toward equilibrium. It reads the same aux as the
flux, so a force like -rho grad phi simply reads grad_x and grad_y.
A source can be stiff, relaxing far faster than the transport. Treating it explicitly would force a tiny time step. That is why the source is its own function: the integrator can advance it implicitly while transport stays explicit. The discretization of that split lives in the algorithms guide; the concept to retain is that flux and source are kept separate so they can be integrated differently.
The eigenvalues: setting the time step¶
The eigenvalues are the characteristic speeds of the system per direction: the drift velocity
for a scalar, or {u-c, u, u, u+c} for Euler with sound speed c. They are what makes the
equation hyperbolic, and they set the largest speed at which information moves.
The core reduces them to max_wave_speed and from it derives the explicit time step through
the CFL (Courant-Friedrichs-Lewy) condition:
A wave must not cross more than one cell per step, or the scheme loses stability. The maximum
is taken over every local box and then reduced across MPI ranks, so all ranks pick the same
dt and stay in step. A model with no transport reports a zero wave speed and places no limit
on the step. The eigenvalues also feed the numerical flux: Rusanov bounds its diffusion by the
maximum speed, and HLL uses the signal speeds directly.
How you declare the three¶
In a symbolic (DSL) model the three pieces map one to one onto declarators: m.flux(x=..., y=...)
for the physical flux, m.eigenvalues(x=..., y=...) for the characteristic speeds, and
m.source(...) for the source. A native model picks the same three from precompiled bricks
instead. Either way the result is one PhysicalModel: a flux, a set of eigenvalues, a source,
and an elliptic right-hand side, with nothing about the mesh or the integrator baked in.