ProgramContext Class Reference

adc_cpp: pops::runtime::program::ProgramContext Class Reference
adc_cpp 0.3.0
Model-free C++23 core for coupled hyperbolic-elliptic systems on adaptive (AMR) meshes, with MPI and GPU (Kokkos) backends
pops::runtime::program::ProgramContext Class Reference

#include <program_context.hpp>

+ Collaboration diagram for pops::runtime::program::ProgramContext:

Public Member Functions

 ProgramContext (System *sys)
 
 ProgramContext (void *sys)
 Wraps a System passed as a flat void* (what pops_install_program(void* sys) receives).
 
void install (std::function< void(double)> step) const
 Register the macro-step body.
 
int sys_block (int b) const
 Translate a PROGRAM block index b (P.state declaration order, what the codegen emits) to the SYSTEM block index it names (Spec 3 criterion 23, ADC-457).
 
void solve_fields () const
 
void solve_fields_from_state (int b, MultiFab &u_stage) const
 Per-stage field solve (ADC-409): re-solve the elliptic fields and re-fill the shared aux from block b's STAGE state u_stage (not its live state), so a field-coupled multi-stage Program's stage k reads phi solved from stage k's own state.
 
void solve_fields_from_state (const std::string &field, int b, MultiFab &u_stage) const
 Named multi-elliptic field solve (ADC-428): re-solve the SECOND elliptic field field from block b's stage state u_stage and write its phi (+ centered grad) into the field's OWN aux components (distinct from the shared phi/grad the default solve_fields fills).
 
void solve_fields_from_blocks (const std::vector< const MultiFab * > &u_stages) const
 Coupled multi-block field solve (Spec 3 criterion 24, ADC-457): re-solve the elliptic fields and re-fill the shared aux from the SIMULTANEOUS stage states of MULTIPLE blocks at once – the system Poisson RHS is Sum_s elliptic_rhs_s(U_s), every coupled block reading its OWN stage state (not a single-target override).
 
int n_blocks () const
 
MultiFabstate (int b) const
 
void rhs_into (int b, MultiFab &u, MultiFab &r) const
 
void neg_div_flux_default_into (int b, MultiFab &u, MultiFab &r) const
 r <- -div F(u) for block b – the SAME flux divergence as rhs_into but WITHOUT the model's default/composite source (Poisson frozen).
 
void source_default_into (int b, MultiFab &u, MultiFab &r) const
 r <- S(u, aux) for block b – the model's default/composite SOURCE only, WITHOUT the flux divergence (the exact MIRROR of neg_div_flux_default_into).
 
Real hmin () const
 The MIN physical cell size of the grid (Cartesian min(dx, dy); polar min(dr, r_min*dtheta)) – the SAME hmin the native CFL uses.
 
Real max_wave_speed (int b, const MultiFab &u) const
 The maximum |wave speed| of block b on the state u: the SAME per-block reduction step_cfl reads (BlockState::max_speed).
 
MultiFabaux () const
 The System aux MultiFab (phi=0, grad_x=1, grad_y=2, B_z=3, T_e=4, named fields from kAuxNamedBase).
 
MultiFab alloc_scalar_field (int n_comp=1, int n_ghost=1) const
 A fresh scalar field co-distributed with the System mesh (block 0's box array / distribution), n_comp components, n_ghost ghost layers, zero-initialized.
 
Geometry geom () const
 The System mesh geometry (index domain + physical bounds, dx/dy).
 
void laplacian (MultiFab &out, MultiFab &in) const
 out = Lap(in): fill in's ghosts (transport BC, periodic by default) then apply the SHARED discrete 5-point Laplacian (pops::apply_laplacian, all optional coefficients null -> the bare bit-identical Laplacian).
 
void gradient (MultiFab &out, MultiFab &phi) const
 out = grad(phi) by centered differences: out(.,0) = d phi/dx, out(.,1) = d phi/dy (out needs >= 2 components).
 
void divergence (MultiFab &out, MultiFab &fx, MultiFab &fy) const
 out = div(fx, fy) by centered differences: out = d fx/dx + d fy/dy (component 0).
 
void geometric_mg_precond_apply (MultiFab &out, const MultiFab &in) const
 out <- M^{-1}(in): ONE geometric-multigrid V-cycle of the bare 5-point Laplacian, used as a matrix-free Krylov PRECONDITIONER (the preconditioner=preconditioners.GeometricMG() route of P.solve_linear for GMRES / BiCGStab, ADC-516).
 
void neg_div_flux_into (MultiFab &r, MultiFab &fx, MultiFab &fy) const
 r <- -div(fx, fy) per conservative component (ADC-419 named fluxes): r(.,c) = -(d fx(.,c)/dx + d fy(.,c)/dy), centered FV, for every component c of r.
 
MultiFab rhs_scratch_like (const MultiFab &u) const
 A zero-initialized RHS scratch with the SAME layout (box array / distribution / ghosts) as u, so the subsequent axpy(u, ., r) combines identical layouts.
 
MultiFab scratch_state_like (const MultiFab &u) const
 A zero-initialized scratch STATE with the same layout as u: an intermediate stage state of a multi-stage scheme (SSPRK/RK).
 
void axpy (MultiFab &u, Real a, const MultiFab &r) const
 u <- u + a r over the valid cells (linear combine; forwards to pops::saxpy).
 
void lincomb (MultiFab &z, Real a, const MultiFab &x, Real b, const MultiFab &y) const
 z <- a x + b y over the valid cells (assignment, not accumulation; z may alias x or y).
 
void register_history (const std::string &name, int lag) const
 Register (idempotent) the history name with maximum lag lag, allocating the ring buffer WITHOUT reading it.
 
MultiFabhistory (const std::string &name, int lag=1) const
 The history slot lag macro-steps back (the SYSTEM-OWNED ring buffer, ADC-406a): lag 1 = the previous step's stored value (e.g.
 
void store_history (const std::string &name, const MultiFab &value) const
 Store value into the CURRENT slot of history name (ADC-406a).
 
void rotate_histories () const
 Shift every history ring one macro-step (slot k <- slot k-1).
 
void fill_boundary (MultiFab &x) const
 Fill the ghost cells (halos) of x in place: the transport BC (periodic by default), the SAME exchange laplacian / gradient / divergence run internally before differencing (spec op 22).
 
void apply_projection (int b, MultiFab &u) const
 Apply block b's post-step positivity projection to u in place: U <- project(U, aux) over the valid cells, the SAME Zhang-Shu / floor projection the native per-step path runs (ADC-177, spec op 21).
 
void record_scalar (const std::string &name, Real value) const
 Store a runtime Scalar value into the System diagnostics map under name (spec op 23), retrievable after the step via System::program_diagnostic / program_diagnostics (exposed to Python as sim.program_diagnostic / sim.program_diagnostics).
 
RuntimeParams program_params (int b) const
 The CURRENT RuntimeParams of PROGRAM block b (epic ADC-479 / ADC-510, Spec 5 C5): the per-block runtime-parameter values a compiled Program's lowered source / linear-source kernel reads via params.get(<index>).
 
Anisotropic Schur condensation (epic ADC-399 / ADC-421)

The full condensed-Schur operator is L_schur(phi) = -div((I + c*rho*B^{-1}) grad phi), a tensor elliptic operator whose per-cell coefficient varies with rho and B_z.

These primitives let a compiled Program ASSEMBLE that coefficient tensor (from the live state + B_z aux) and APPLY it matrix-free, REUSING the native Schur kernels (coupling/schur/core/schur_condensation.hpp) and pops::apply_laplacian's coefficient path – no stencil / elimination reimplementation. The native pops::CondensedSchur source stepper is untouched.

void assemble_schur_coeffs (MultiFab &eps_x, MultiFab &eps_y, MultiFab &a_xy, MultiFab &a_yx, const MultiFab &state, Real c, Real th_dt, int c_rho, int c_bz) const
 Assemble the tensor coefficient A_op = I + c*rho*B^{-1} of the condensed-Schur operator per cell: eps_x = 1 + c*rho*binv_11, eps_y = 1 + c*rho*binv_22, a_xy = c*rho*binv_12, a_yx = c*rho*binv_21, with B^{-1} the closed 2x2 LorentzEliminator(th_dt, 1, B_z).
 
void apply_laplacian_coeff (MultiFab &out, MultiFab &in, const MultiFab &eps_x, const MultiFab &eps_y, const MultiFab &a_xy, const MultiFab &a_yx) const
 out = div(A grad in), A = [[eps_x, a_xy], [a_yx, eps_y]] – the coefficiented matrix-free matvec of the condensed-Schur operator.
 
void schur_explicit_flux (MultiFab &out, const MultiFab &state, Real th_dt, int c_mx, int c_my, int c_bz) const
 out = B^{-1} (mx, my) per cell – the EXPLICIT condensed-Schur flux F = rho*B^{-1}*v^n (= B^{-1} applied to the momentum, avoiding the divide by rho).
 
void assemble_schur_rhs (MultiFab &rhs, MultiFab &phi_n, const MultiFab &state, Real th_dt, Real g, int c_mx, int c_my, int c_bz) const
 rhs = -Lap(phi_n) - g*div(F), F = B^{-1}(mx, my) – the FUSED condensed-Schur right-hand side (the native ElectrostaticLorentzCondensation::assemble_rhs, reading B_z from the aux at c_bz).
 
void schur_reconstruct (MultiFab &state, MultiFab &phi, Real th_dt, int c_rho, int c_mx, int c_my, int c_bz) const
 Reconstruct v^{n+theta} = B^{-1}(v^n - theta*dt*grad phi^{n+theta}) and write mom = rho^n*v into state in place (rho frozen).
 
void schur_energy (MultiFab &state, const MultiFab &state_old, int c_rho, int c_mx, int c_my, int c_E) const
 Condensed-Schur kinetic-energy increment (ADC-427): E^{n+1} = E^n + (1/2)*rho*(|v^{n+1}|^2 - |v^n|^2) IN PLACE on state, reading v^{n+1} from state (after the velocity update + extrapolation) and v^n from state_old (U^n).
 
Reductions (spec op 16)

COLLECTIVE all_reduce over one component of a field (sum / signed max / signed min).

The codegen lowers P.sum / P.sum_component / P.max / P.min DIRECTLY to the pops:: free functions (like norm2 -> pops::dot), but these wrappers expose them on the context for hand-rolled C++ stages and mirror norm2 / dot above. MANDATORY UNDER MPI: called on EVERY rank (empty ranks included), like dot.

Real sum_component (const MultiFab &u, int comp) const
 
Real max_component (const MultiFab &u, int comp) const
 
Real min_component (const MultiFab &u, int comp) const
 
Real sum (const MultiFab &u) const
 
Real max (const MultiFab &u) const
 
Real min (const MultiFab &u) const
 
Per-node profiling (Spec 3 section 29, ADC-459)

Time a single Program node into the System Profiler, so sim.profile_report() shows per-node times ("node:rhs2", "node:solve_fields1", ...) alongside the coarse "step" / "field_solve" phases.

The Profiler is disabled by default; both calls are ~free when off (a ProfileScope still reads the clock twice – wrap a per-node scope, the intended granularity, not the inner loops).

The System Profiler (non-owning). A hand-written C++ stage can construct its own ProfileScope on it; the codegen uses profile_record below (which preserves the step body's C++ variable scope).

runtime::program::Profilerprofiler () const
 
runtime::program::ProfileScope profile_node (const std::string &name) const
 RAII timer for one node: pops::runtime::program::ProfileScope s = ctx.profile_node("node:x"); times its own lifetime into the System Profiler.
 
void profile_record (const std::string &name, std::chrono::steady_clock::time_point t0) const
 Record one node's elapsed time (now() - t0) under name into the System Profiler.
 
Profiling counters (Spec 3 section 29, ADC-459)

The named integer counters sim.profile_report() surfaces alongside the per-node timings: how many kernel launches a step issued, how the held-node scheduler hit/missed its cache, and the scratch peak memory.

Each helper is a single predictable branch when profiling is off (Profiler::count / count_max early-return), so the hot path pays nothing unless sim.enable_profiling() ran. These move only on the COMPILED-PROGRAM path (a problem.so step body calling these seam ops); the native step counts "kernels" at its own elliptic-solve chokepoint instead (System::Impl::solve_fields).

One per kernel-dispatching seam op (a -div F / source / matvec / solve). The compiled step body reaches the seam through these methods, so counting at this op granularity counts the per-node kernel LAUNCHES (the device dispatch in mesh/execution/for_each.hpp is a shared free function with no profiler handle – instrumenting it would touch every numerics TU and add a hidden hot-path argument, so the op-granularity count is the deliberate, labeled choice, Spec 3 section 29).

void count_kernel (std::int64_t by=1) const
 
void count_scratch (const MultiFab &mf) const
 Record one scratch MultiFab allocation: bumps the allocation count and updates the byte peak with THIS buffer's footprint.
 
Scheduler value cache (Spec 3 section 17-18, ADC-458)

A held field-solve node recomputes only when DUE (every N macro-steps) and reuses the cached System aux (phi / grad / E) in between.

The cache is owned by the System (one CacheManager per installed Program, keyed by the Program node id) so the checkpoint can reach it (Spec 3 section 30); every ProgramContext copy forwards to that single manager via sys_->program_cache(). The codegen wraps a held solve_fields in if (cache_should_update(id, N)) { solve_fields_from_state(...); cache_store_aux(id); } else { cache_restore_aux(id); }. The runtime cadence/checkpoint is exercised in a compiled .so step loop (validated on ROMEO; not buildable on a host-only Mac).

True if node node_id is due to recompute at the current macro step: cold start (never stored), then every every_n macro steps. Wraps CacheManager::is_due with System::macro_step().

PROFILER scheduler counters (ADC-459, Spec 3 section 29): a DUE step recomputes the node (a cache "miss" + a "due" scheduled node); a NOT-due step reuses the held value (a cache "hit" + a "skipped" scheduled node). Counted here at the one decision point every scheduled node routes through, gated on the profiler (zero cost when off). These move only under the compiled .so step loop that exercises a held schedule (validated on Kokkos/ROMEO, not buildable host-only).

bool cache_should_update (int node_id, int every_n) const
 
void cache_store_aux (int node_id) const
 Store a copy of the System aux (the field solve's output) as node node_id's cached value, stamped at the current macro step (resets its accumulated dt).
 
void cache_restore_aux (int node_id) const
 Restore node node_id's cached aux into the System aux (a held step: no elliptic solve).
 
void cache_store_scratch (int node_id, const MultiFab &scratch) const
 Store a copy of a NAMED scratch MultiFab (a held rhs / source / linear_combine output) as node node_id's cached value, stamped at the current macro step.
 
void cache_restore_scratch (int node_id, MultiFab &scratch) const
 Restore node node_id's cached scratch into scratch (a held step: no recompute).
 
int macro_step () const
 The current macro step (0-based).
 
void cache_accumulate_dt (int node_id, Real dt) const
 Add a skipped step's dt to node node_id's accumulator (accumulate_dt policy): on a NOT-due step the held node does not recompute but records the dt so the next due step sees the full skipped interval.
 
Real cache_effective_dt (int node_id, Real dt_now) const
 The effective dt a due accumulate_dt step applies: dt_now plus the summed skipped dt since the last recompute (resets the accumulator).
 
void scheduler_error (const std::string &what) const
 Fail loud: a node with an error policy was reached off its schedule cadence (a stale value would be read).
 

Constructor & Destructor Documentation

◆ ProgramContext() [1/2]

pops::runtime::program::ProgramContext::ProgramContext ( System sys)
inlineexplicit

◆ ProgramContext() [2/2]

pops::runtime::program::ProgramContext::ProgramContext ( void *  sys)
inlineexplicit

Wraps a System passed as a flat void* (what pops_install_program(void* sys) receives).

Member Function Documentation

◆ alloc_scalar_field()

MultiFab pops::runtime::program::ProgramContext::alloc_scalar_field ( int  n_comp = 1,
int  n_ghost = 1 
) const
inline

A fresh scalar field co-distributed with the System mesh (block 0's box array / distribution), n_comp components, n_ghost ghost layers, zero-initialized.

Forwards to System::alloc_scalar_field. The scratch fields (residual, search direction, solution) a matrix-free Krylov solve allocates – a 1-component field is distinct from the n_cons block state, but shares its (ba, dm) so laplacian / gradient pair it with the state and aux by local fab index.

+ Here is the call graph for this function:

◆ apply_laplacian_coeff()

void pops::runtime::program::ProgramContext::apply_laplacian_coeff ( MultiFab out,
MultiFab in,
const MultiFab eps_x,
const MultiFab eps_y,
const MultiFab a_xy,
const MultiFab a_yx 
) const
inline

out = div(A grad in), A = [[eps_x, a_xy], [a_yx, eps_y]] – the coefficiented matrix-free matvec of the condensed-Schur operator.

Fills in's ghosts (transport BC) then forwards to the SAME pops::apply_laplacian coefficient path the native GeometricMG operator uses (eps / cross pointers), component 0 (the scalar potential). in is non-const because the ghost fill writes its halos. The condensed operator is L_schur(phi) = -div(A grad phi) = -out, so a matrix-free apply forms it as ctx.apply_laplacian_coeff(out, in, ...); out *= -1 via the affine algebra. The coefficient fields are the ones assemble_schur_coeffs filled (1 ghost each).

+ Here is the call graph for this function:

◆ apply_projection()

void pops::runtime::program::ProgramContext::apply_projection ( int  b,
MultiFab u 
) const
inline

Apply block b's post-step positivity projection to u in place: U <- project(U, aux) over the valid cells, the SAME Zhang-Shu / floor projection the native per-step path runs (ADC-177, spec op 21).

REUSES the block's own projection closure (set at add_block time); a block WITHOUT a projection is a no-op. Forwards to System::block_project – it reimplements no positivity.

+ Here is the call graph for this function:

◆ assemble_schur_coeffs()

void pops::runtime::program::ProgramContext::assemble_schur_coeffs ( MultiFab eps_x,
MultiFab eps_y,
MultiFab a_xy,
MultiFab a_yx,
const MultiFab state,
Real  c,
Real  th_dt,
int  c_rho,
int  c_bz 
) const
inline

Assemble the tensor coefficient A_op = I + c*rho*B^{-1} of the condensed-Schur operator per cell: eps_x = 1 + c*rho*binv_11, eps_y = 1 + c*rho*binv_22, a_xy = c*rho*binv_12, a_yx = c*rho*binv_21, with B^{-1} the closed 2x2 LorentzEliminator(th_dt, 1, B_z).

state carries rho at component c_rho; B_z is read from the System aux at component c_bz. The four coefficient fields are filled over the valid cells (the SAME detail::SchurOperatorCoeffKernel the native builder uses) and their ghosts extended by zero-gradient (Foextrap, periodic preserved) – the eps_bc the GeometricMG / native assembly use, so the face mean at the boundary is consistent. c = theta^2 dt^2 alpha, th_dt = theta*dt. Assembled ONCE per step (rho / B_z frozen in the source), then reused across every Krylov iteration of the matrix-free phi solve.

+ Here is the call graph for this function:

◆ assemble_schur_rhs()

void pops::runtime::program::ProgramContext::assemble_schur_rhs ( MultiFab rhs,
MultiFab phi_n,
const MultiFab state,
Real  th_dt,
Real  g,
int  c_mx,
int  c_my,
int  c_bz 
) const
inline

rhs = -Lap(phi_n) - g*div(F), F = B^{-1}(mx, my) – the FUSED condensed-Schur right-hand side (the native ElectrostaticLorentzCondensation::assemble_rhs, reading B_z from the aux at c_bz).

phi_n is phi^n (its ghosts are filled here for the Laplacian); state carries mx / my at c_mx / c_my; th_dt = theta*dt; g = theta*dt*alpha. rhs is a 1-component scalar field. Internal Lap / flux buffers are allocated on rhs's layout (transient, like the native assembler). Mirrors native assemble_rhs step-for-step (bare apply_laplacian + NegateKernel + the explicit flux

  • SchurRhsAssembleKernel), so the top-level RHS assembly is a SINGLE op (no scalar-field affine combine at IR level): the same fused -Lap - g*div(F) the native source stepper assembles.
+ Here is the call graph for this function:

◆ aux()

MultiFab & pops::runtime::program::ProgramContext::aux ( ) const
inline

The System aux MultiFab (phi=0, grad_x=1, grad_y=2, B_z=3, T_e=4, named fields from kAuxNamedBase).

NOT owned by the context: it is the live System aux (stable address), the same channel solve_fields() fills. A generated local-linear-solve kernel reads the operator coefficients (e.g. B_z) from it. Forwards to System::grid_context().aux.

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ axpy()

void pops::runtime::program::ProgramContext::axpy ( MultiFab u,
Real  a,
const MultiFab r 
) const
inline

u <- u + a r over the valid cells (linear combine; forwards to pops::saxpy).

+ Here is the call graph for this function:

◆ cache_accumulate_dt()

void pops::runtime::program::ProgramContext::cache_accumulate_dt ( int  node_id,
Real  dt 
) const
inline

Add a skipped step's dt to node node_id's accumulator (accumulate_dt policy): on a NOT-due step the held node does not recompute but records the dt so the next due step sees the full skipped interval.

Variable step_cfl safe (the actual skipped dt, not N * dt_current).

+ Here is the call graph for this function:

◆ cache_effective_dt()

Real pops::runtime::program::ProgramContext::cache_effective_dt ( int  node_id,
Real  dt_now 
) const
inline

The effective dt a due accumulate_dt step applies: dt_now plus the summed skipped dt since the last recompute (resets the accumulator).

The codegen feeds this as the step's dt into the held node's recompute so it advances over the whole skipped interval at once.

+ Here is the call graph for this function:

◆ cache_restore_aux()

void pops::runtime::program::ProgramContext::cache_restore_aux ( int  node_id) const
inline

Restore node node_id's cached aux into the System aux (a held step: no elliptic solve).

+ Here is the call graph for this function:

◆ cache_restore_scratch()

void pops::runtime::program::ProgramContext::cache_restore_scratch ( int  node_id,
MultiFab scratch 
) const
inline

Restore node node_id's cached scratch into scratch (a held step: no recompute).

+ Here is the call graph for this function:

◆ cache_should_update()

bool pops::runtime::program::ProgramContext::cache_should_update ( int  node_id,
int  every_n 
) const
inline
+ Here is the call graph for this function:

◆ cache_store_aux()

void pops::runtime::program::ProgramContext::cache_store_aux ( int  node_id) const
inline

Store a copy of the System aux (the field solve's output) as node node_id's cached value, stamped at the current macro step (resets its accumulated dt).

+ Here is the call graph for this function:

◆ cache_store_scratch()

void pops::runtime::program::ProgramContext::cache_store_scratch ( int  node_id,
const MultiFab scratch 
) const
inline

Store a copy of a NAMED scratch MultiFab (a held rhs / source / linear_combine output) as node node_id's cached value, stamped at the current macro step.

The aux variants cache the System aux; this caches an arbitrary step-body scratch so ANY schedulable node can hold, not only a field solve.

+ Here is the call graph for this function:

◆ count_kernel()

void pops::runtime::program::ProgramContext::count_kernel ( std::int64_t  by = 1) const
inline
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ count_scratch()

void pops::runtime::program::ProgramContext::count_scratch ( const MultiFab mf) const
inline

Record one scratch MultiFab allocation: bumps the allocation count and updates the byte peak with THIS buffer's footprint.

The peak is the largest SINGLE scratch (a deep allocation); a running "live total" is not tracked because the seam hands the buffer to the caller (no free hook here), so we report what is exactly knowable – the allocation count and the largest one – never a faked live-bytes figure (Spec 3 section 29 scratch peak memory).

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ divergence()

void pops::runtime::program::ProgramContext::divergence ( MultiFab out,
MultiFab fx,
MultiFab fy 
) const
inline

out = div(fx, fy) by centered differences: out = d fx/dx + d fy/dy (component 0).

The x-flux is read from component 0 of fx and the y-flux from component 1 of fy, the SAME layout gradient writes (d/dx in component 0, d/dy in component 1) – so chaining ctx.gradient(g, phi) then ctx.divergence(out, g, g) recovers the 5-point Laplacian. Fills the ghosts of fx and fy (transport BC, periodic by default) then forwards to pops::apply_divergence – the exact inverse stencil of gradient and the same centered FV divergence the native Schur condensation assembles (coupling/schur/core/schur_condensation.hpp). fx and fy are non-const because the ghost fill WRITES their halos (the valid cells are unchanged). A compiled Program forms a Schur-like flux operator A(phi) = phi - alpha*div(grad phi) by chaining ctx.gradient then ctx.divergence inside a matrix-free apply.

+ Here is the call graph for this function:

◆ fill_boundary()

void pops::runtime::program::ProgramContext::fill_boundary ( MultiFab x) const
inline

Fill the ghost cells (halos) of x in place: the transport BC (periodic by default), the SAME exchange laplacian / gradient / divergence run internally before differencing (spec op 22).

The valid cells are untouched; only the halos change. Forwards to the shared pops::fill_ghosts.

+ Here is the call graph for this function:

◆ geom()

Geometry pops::runtime::program::ProgramContext::geom ( ) const
inline

The System mesh geometry (index domain + physical bounds, dx/dy).

BY VALUE: grid_context() returns a temporary, so a reference to its geom member would dangle. The metric the matrix-free Laplacian / gradient read.

+ Here is the call graph for this function:

◆ geometric_mg_precond_apply()

void pops::runtime::program::ProgramContext::geometric_mg_precond_apply ( MultiFab out,
const MultiFab in 
) const
inline

out <- M^{-1}(in): ONE geometric-multigrid V-cycle of the bare 5-point Laplacian, used as a matrix-free Krylov PRECONDITIONER (the preconditioner=preconditioners.GeometricMG() route of P.solve_linear for GMRES / BiCGStab, ADC-516).

It REUSES the already-wired pops::GeometricMG (the same V-cycle the field solve runs) – no new numerical kernel: set the level-0 rhs to in, start from phi = 0, run a SINGLE vcycle(), copy the result into out.

EXACTLY ONE V-cycle from a ZERO guess is mandatory: a preconditioner must be a FIXED linear map M^{-1} (the same operator on every Krylov apply) for GMRES / BiCGStab to converge. Iterating to a tolerance (solve()) would make the trip count – hence the map – depend on the input vector, a VARIABLE (nonlinear) preconditioner that breaks the Krylov recurrences. The V-cycle of the bare Laplacian is symmetric-positive and history-free, so one cycle from phi=0 is a valid stationary M^{-1} approximating L^{-1}.

The GeometricMG instance is built ONCE (lazily, on the first call) on the System mesh (geometry + block-0 BoxArray/DistributionMapping + transport BC) and CACHED in a mutable member, co-distributed with the Krylov scratch so its level-0 phi/rhs pair in / out by local fab index. in is the Krylov vector (logically read-only); out is fully overwritten. The matvec budget is decided C++-side inside the Krylov loop, so this apply is invisible to the IR.

+ Here is the call graph for this function:

◆ gradient()

void pops::runtime::program::ProgramContext::gradient ( MultiFab out,
MultiFab phi 
) const
inline

out = grad(phi) by centered differences: out(.,0) = d phi/dx, out(.,1) = d phi/dy (out needs >= 2 components).

Fills phi's ghosts then forwards to pops::field_postprocess with store_phi=false (the gradient lands in components 0/1) and the centered factors cx = 1/(2 dx), cy = 1/(2 dy) – the same derivation the elliptic aux post-process uses (+grad sign).

+ Here is the call graph for this function:

◆ history()

MultiFab & pops::runtime::program::ProgramContext::history ( const std::string &  name,
int  lag = 1 
) const
inline

The history slot lag macro-steps back (the SYSTEM-OWNED ring buffer, ADC-406a): lag 1 = the previous step's stored value (e.g.

R_{n-1} for Adams-Bashforth), lag 0 = the current slot. The codegen emits ctx.history("<name>", <lag>); the read registers the ring on first use (idempotent) and forwards to System::read_history, which throws if the history was never stored (spec error 17). lag defaults to 1 (the common one-step-back read).

+ Here is the call graph for this function:

◆ hmin()

Real pops::runtime::program::ProgramContext::hmin ( ) const
inline

The MIN physical cell size of the grid (Cartesian min(dx, dy); polar min(dr, r_min*dtheta)) – the SAME hmin the native CFL uses.

Forwards to System::cfl_min_dx. A compiled time Program's dt bound (epic ADC-399 / ADC-417, spec s18) reads it to express e.g. cfl * hmin / max_wave_speed.

+ Here is the call graph for this function:

◆ install()

void pops::runtime::program::ProgramContext::install ( std::function< void(double)>  step) const
inline

Register the macro-step body.

step advances ONE macro-step over dt (it owns solve_fields, the RHS, the linear combine and the commit). Empty std::function clears it.

+ Here is the call graph for this function:

◆ laplacian()

void pops::runtime::program::ProgramContext::laplacian ( MultiFab out,
MultiFab in 
) const
inline

out = Lap(in): fill in's ghosts (transport BC, periodic by default) then apply the SHARED discrete 5-point Laplacian (pops::apply_laplacian, all optional coefficients null -> the bare bit-identical Laplacian).

in is non-const because the ghost fill WRITES its halos (the valid cells are unchanged); this is the same matvec idiom the matrix-free Krylov test (tests/test_generic_krylov.cpp) wraps in its ApplyFn. The compiled program forms an operator A(in) = in - alpha*Lap(in) by combining this with ctx.lincomb.

+ Here is the call graph for this function:

◆ lincomb()

void pops::runtime::program::ProgramContext::lincomb ( MultiFab z,
Real  a,
const MultiFab x,
Real  b,
const MultiFab y 
) const
inline

z <- a x + b y over the valid cells (assignment, not accumulation; z may alias x or y).

Forwards to pops::lincomb. The codegen uses it for the committed stage: the block state becomes z = c_base * z + 1 * acc, where acc holds the non-base terms (self-alias z==x is safe).

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ macro_step()

int pops::runtime::program::ProgramContext::macro_step ( ) const
inline

The current macro step (0-based).

Mirrors System::macro_step(); the codegen lowers on_start() to ctx.macro_step() == 0 and reads it for any step-indexed predicate.

+ Here is the call graph for this function:

◆ max()

Real pops::runtime::program::ProgramContext::max ( const MultiFab u) const
inline
+ Here is the call graph for this function:

◆ max_component()

Real pops::runtime::program::ProgramContext::max_component ( const MultiFab u,
int  comp 
) const
inline
+ Here is the call graph for this function:

◆ max_wave_speed()

Real pops::runtime::program::ProgramContext::max_wave_speed ( int  b,
const MultiFab u 
) const
inline

The maximum |wave speed| of block b on the state u: the SAME per-block reduction step_cfl reads (BlockState::max_speed).

Forwards to System::block_max_speed – it REUSES the block's wave-speed closure, it does not recompute the speed. u is the state the bound is evaluated on (the block's current state for a CFL bound). The dt_bound expression uses it as the denominator of cfl * hmin / max_wave_speed (epic ADC-399 / ADC-417, spec s18).

+ Here is the call graph for this function:

◆ min()

Real pops::runtime::program::ProgramContext::min ( const MultiFab u) const
inline
+ Here is the call graph for this function:

◆ min_component()

Real pops::runtime::program::ProgramContext::min_component ( const MultiFab u,
int  comp 
) const
inline
+ Here is the call graph for this function:

◆ n_blocks()

int pops::runtime::program::ProgramContext::n_blocks ( ) const
inline
+ Here is the call graph for this function:

◆ neg_div_flux_default_into()

void pops::runtime::program::ProgramContext::neg_div_flux_default_into ( int  b,
MultiFab u,
MultiFab r 
) const
inline

r <- -div F(u) for block b – the SAME flux divergence as rhs_into but WITHOUT the model's default/composite source (Poisson frozen).

Forwards to System::block_neg_div_flux_into (the block's SourceFreeModel<Model> rhs path, bit-identical to rhs_into minus the source). The codegen lowers a hyperbolic stage that excludes the default source (P.rhs(flux=True, sources without "default"), incl. the empty list) to this, so a Lie/Strang split assembles "flux but no source" without the default source leaking in (epic ADC-399 / ADC-425, spec criterion 17). Header-inline forwarder, like rhs_into.

+ Here is the call graph for this function:

◆ neg_div_flux_into()

void pops::runtime::program::ProgramContext::neg_div_flux_into ( MultiFab r,
MultiFab fx,
MultiFab fy 
) const
inline

r <- -div(fx, fy) per conservative component (ADC-419 named fluxes): r(.,c) = -(d fx(.,c)/dx + d fy(.,c)/dy), centered FV, for every component c of r.

fx and fy hold the n_cons x- and y-flux fields a compiled Program's named-flux kernel wrote (component c = the flux of conservative component c). REUSES pops::apply_divergence component-by-component (the SAME centered stencil as divergence, the inverse of gradient – no new differencing): the ghosts are filled once per field, then each component's divergence lands in a 1-component scratch and is copied with a sign flip into r. fx / fy are non-const because the ghost fill writes their halos (the valid cells are unchanged). This semi-discrete -div F is LINEAR in the flux, so the -div of a SUM of named fluxes equals the sum of their -div (the named-flux parity guarantee).

+ Here is the call graph for this function:

◆ profile_node()

runtime::program::ProfileScope pops::runtime::program::ProgramContext::profile_node ( const std::string &  name) const
inline

RAII timer for one node: pops::runtime::program::ProfileScope s = ctx.profile_node("node:x"); times its own lifetime into the System Profiler.

For a hand-rolled C++ stage that can wrap a whole block; the generated step body cannot use it (a node's emitted C++ declarations must outlive the node), so the codegen pairs a steady_clock now() with profile_record instead.

+ Here is the call graph for this function:

◆ profile_record()

void pops::runtime::program::ProgramContext::profile_record ( const std::string &  name,
std::chrono::steady_clock::time_point  t0 
) const
inline

Record one node's elapsed time (now() - t0) under name into the System Profiler.

The generated step body captures t0 = std::chrono::steady_clock::now() BEFORE the node's statements and calls this AFTER them, so the node's C++ declarations stay at body scope (a surrounding RAII block would hide them from later nodes). No-op contribution when profiling is off (Profiler::record early-returns); the only cost is one extra clock read per node.

+ Here is the call graph for this function:

◆ profiler()

runtime::program::Profiler & pops::runtime::program::ProgramContext::profiler ( ) const
inline
+ Here is the call graph for this function:

◆ program_params()

RuntimeParams pops::runtime::program::ProgramContext::program_params ( int  b) const
inline

The CURRENT RuntimeParams of PROGRAM block b (epic ADC-479 / ADC-510, Spec 5 C5): the per-block runtime-parameter values a compiled Program's lowered source / linear-source kernel reads via params.get(<index>).

The codegen binds const pops::RuntimeParams params = ctx.program_params(<b>); ONCE per fab (outside the per-cell loop), then the device lambda captures it by value – trivially copyable, device-clean, get() is POPS_HD. b is the PROGRAM block index (P.state declaration order, the index install_program seeded), NOT routed through sys_block: the System keys the store by program index, the same index Python's params route writes via set_program_params. A block with no runtime param returns a default RuntimeParams (count 0). Forwards to System::program_params; the value reflects the LATEST set_program_params (no recompile), since the store lives on the System the captured ctx points at.

+ Here is the call graph for this function:

◆ record_scalar()

void pops::runtime::program::ProgramContext::record_scalar ( const std::string &  name,
Real  value 
) const
inline

Store a runtime Scalar value into the System diagnostics map under name (spec op 23), retrievable after the step via System::program_diagnostic / program_diagnostics (exposed to Python as sim.program_diagnostic / sim.program_diagnostics).

A pure side effect: the scalar is recorded for inspection / logging, it does not feed the numerics. Forwards to System::record_program_diagnostic.

+ Here is the call graph for this function:

◆ register_history()

void pops::runtime::program::ProgramContext::register_history ( const std::string &  name,
int  lag 
) const
inline

Register (idempotent) the history name with maximum lag lag, allocating the ring buffer WITHOUT reading it.

The codegen emits this ONCE at the top of the step body for each declared history, so the ring depth is locked before the first store (the cold-start fill then broadcasts the first stored value into every – already allocated – slot). Forwards to System::register_history. A read-only counterpart of history (no fail-loud on uninitialized).

+ Here is the call graph for this function:

◆ rhs_into()

void pops::runtime::program::ProgramContext::rhs_into ( int  b,
MultiFab u,
MultiFab r 
) const
inline
+ Here is the call graph for this function:

◆ rhs_scratch_like()

MultiFab pops::runtime::program::ProgramContext::rhs_scratch_like ( const MultiFab u) const
inline

A zero-initialized RHS scratch with the SAME layout (box array / distribution / ghosts) as u, so the subsequent axpy(u, ., r) combines identical layouts.

Records the allocation into the scratch peak-memory counters (no-op when profiling is off); scratch_state_like forwards here, so every stage / rhs scratch is counted once at its single allocation site (ADC-459).

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ rotate_histories()

void pops::runtime::program::ProgramContext::rotate_histories ( ) const
inline

Shift every history ring one macro-step (slot k <- slot k-1).

Forwards to System::rotate_histories. The codegen emits ctx.rotate_histories() as the LAST statement of the step body (after the commit), so the next step reads lag k as the value k stores ago.

+ Here is the call graph for this function:

◆ scheduler_error()

void pops::runtime::program::ProgramContext::scheduler_error ( const std::string &  what) const
inline

Fail loud: a node with an error policy was reached off its schedule cadence (a stale value would be read).

The codegen emits this on the not-due branch of an error-policy node.

◆ schur_energy()

void pops::runtime::program::ProgramContext::schur_energy ( MultiFab state,
const MultiFab state_old,
int  c_rho,
int  c_mx,
int  c_my,
int  c_E 
) const
inline

Condensed-Schur kinetic-energy increment (ADC-427): E^{n+1} = E^n + (1/2)*rho*(|v^{n+1}|^2 - |v^n|^2) IN PLACE on state, reading v^{n+1} from state (after the velocity update + extrapolation) and v^n from state_old (U^n).

rho is frozen (read from state). Reuses the native energy formula (detail::SchurEnergyKernel). Applied only when the model carries an energy component (the macro passes c_E only for an energy block).

+ Here is the call graph for this function:

◆ schur_explicit_flux()

void pops::runtime::program::ProgramContext::schur_explicit_flux ( MultiFab out,
const MultiFab state,
Real  th_dt,
int  c_mx,
int  c_my,
int  c_bz 
) const
inline

out = B^{-1} (mx, my) per cell – the EXPLICIT condensed-Schur flux F = rho*B^{-1}*v^n (= B^{-1} applied to the momentum, avoiding the divide by rho).

out has >= 2 components (Fx in comp 0, Fy in comp 1, the layout ctx.divergence reads). state carries mx / my at c_mx / c_my; B_z from the aux at c_bz; th_dt = theta*dt (w = th_dt*B_z). Reuses the native detail::SchurExplicitFluxKernel. The condensed RHS is then -Lap phi^n - theta*dt*alpha*div(F), assembled with ctx.laplacian + ctx.divergence + the affine algebra.

+ Here is the call graph for this function:

◆ schur_reconstruct()

void pops::runtime::program::ProgramContext::schur_reconstruct ( MultiFab state,
MultiFab phi,
Real  th_dt,
int  c_rho,
int  c_mx,
int  c_my,
int  c_bz 
) const
inline

Reconstruct v^{n+theta} = B^{-1}(v^n - theta*dt*grad phi^{n+theta}) and write mom = rho^n*v into state in place (rho frozen).

phi is phi^{n+theta} (its ghosts are filled here for the centered gradient); B_z from the aux at c_bz; th_dt = theta*dt. v^n is read from the state (mx/my / rho), the same closed B^{-1} (LorentzEliminator) the native reconstruction uses. The final n+1 extrapolation (factor 1/theta) is left to the caller's affine algebra.

+ Here is the call graph for this function:

◆ scratch_state_like()

MultiFab pops::runtime::program::ProgramContext::scratch_state_like ( const MultiFab u) const
inline

A zero-initialized scratch STATE with the same layout as u: an intermediate stage state of a multi-stage scheme (SSPRK/RK).

Same allocation as rhs_scratch_like; named for the codegen's intent. Starts at zero, so a stage sum_i c_i V_i is built by axpy-ing each term onto it.

+ Here is the call graph for this function:

◆ solve_fields()

void pops::runtime::program::ProgramContext::solve_fields ( ) const
inline
+ Here is the call graph for this function:

◆ solve_fields_from_blocks()

void pops::runtime::program::ProgramContext::solve_fields_from_blocks ( const std::vector< const MultiFab * > &  u_stages) const
inline

Coupled multi-block field solve (Spec 3 criterion 24, ADC-457): re-solve the elliptic fields and re-fill the shared aux from the SIMULTANEOUS stage states of MULTIPLE blocks at once – the system Poisson RHS is Sum_s elliptic_rhs_s(U_s), every coupled block reading its OWN stage state (not a single-target override).

u_stages is indexed BY BLOCK INDEX (size == n_blocks()); a nullptr entry uses that block's live state. Forwards to System::solve_fields_from_blocks. The codegen lowers P.solve_fields_from_blocks([U0, U1, ...]) to this, building the per-block pointer vector from the listed stage-state vars (their declaration order == the block index order, asserted at emit time). This is the multi-target counterpart of solve_fields_from_state.

+ Here is the call graph for this function:

◆ solve_fields_from_state() [1/2]

void pops::runtime::program::ProgramContext::solve_fields_from_state ( const std::string &  field,
int  b,
MultiFab u_stage 
) const
inline

Named multi-elliptic field solve (ADC-428): re-solve the SECOND elliptic field field from block b's stage state u_stage and write its phi (+ centered grad) into the field's OWN aux components (distinct from the shared phi/grad the default solve_fields fills).

Forwards to System::solve_fields_from_state(field, b, u_stage). The codegen lowers P.solve_fields(field=name, state=U) to this; a default (unnamed) solve_fields keeps the overload above, byte-identical.

+ Here is the call graph for this function:

◆ solve_fields_from_state() [2/2]

void pops::runtime::program::ProgramContext::solve_fields_from_state ( int  b,
MultiFab u_stage 
) const
inline

Per-stage field solve (ADC-409): re-solve the elliptic fields and re-fill the shared aux from block b's STAGE state u_stage (not its live state), so a field-coupled multi-stage Program's stage k reads phi solved from stage k's own state.

Forwards to System::solve_fields_from_state. With b = 0 and u_stage = U^n (the first stage) it matches solve_fields(); the codegen lowers every solve_fields op to this, passing the stage's state var.

+ Here is the call graph for this function:

◆ source_default_into()

void pops::runtime::program::ProgramContext::source_default_into ( int  b,
MultiFab u,
MultiFab r 
) const
inline

r <- S(u, aux) for block b – the model's default/composite SOURCE only, WITHOUT the flux divergence (the exact MIRROR of neg_div_flux_default_into).

Forwards to System::block_source_into (the block's SourceInto path, bit-identical to the source half of rhs_into). The codegen lowers a SOURCE stage (P.rhs(flux=False, sources with "default")) to this, so a Lie/Strang split assembles "the default source but no flux" without the -div F base leaking in (epic ADC-399 / ADC-430, spec: rhs flux=False is source-only). Header-inline forwarder, like neg_div_flux_default_into.

+ Here is the call graph for this function:

◆ state()

MultiFab & pops::runtime::program::ProgramContext::state ( int  b) const
inline
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ store_history()

void pops::runtime::program::ProgramContext::store_history ( const std::string &  name,
const MultiFab value 
) const
inline

Store value into the CURRENT slot of history name (ADC-406a).

Registers the ring on first use (at least a current slot; the lag the program reads via history sets the real depth) and forwards to System::store_history (which fills every slot on the first store – the cold start). The codegen emits ctx.store_history("<name>", <value>) near the end of the step body.

+ Here is the call graph for this function:

◆ sum()

Real pops::runtime::program::ProgramContext::sum ( const MultiFab u) const
inline
+ Here is the call graph for this function:

◆ sum_component()

Real pops::runtime::program::ProgramContext::sum_component ( const MultiFab u,
int  comp 
) const
inline
+ Here is the call graph for this function:

◆ sys_block()

int pops::runtime::program::ProgramContext::sys_block ( int  b) const
inline

Translate a PROGRAM block index b (P.state declaration order, what the codegen emits) to the SYSTEM block index it names (Spec 3 criterion 23, ADC-457).

install_program stored the map after matching the .so's block names to the instantiated System blocks; an EMPTY map is the identity (single-block / order-matching Program, or a ProgramContext built directly in a C++ test), so the historical positional convention is byte-identical. Every seam method taking a block index routes through here, so the System blocks may be added in ANY order vs the Program's P.state declarations.

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

The documentation for this class was generated from the following file: