Python API¶
This page documents the stable user-facing shape of pops. The implementation
also contains runtime seams used by tests and bindings; those are not the
authoring path.
For the rules, see public API contract.
Main flow¶
import pops
from pops.codegen import Production
compiled = pops.compile(case, backend=Production())
sim = pops.bind(compiled, state=initial_state, params=params)
sim.run(t_final=1.0, cfl=0.4)
pops.compile validates and lowers the Case. pops.bind attaches data and
constructs the C++ runtime from the case layout.
Top-level assembly¶
- class pops.Case(layout=None, name=None)[source]¶
Bases:
objectA typed, inert top-level assembly: layout + blocks + fields + params + aux + outputs.
Case(layout=Uniform(CartesianMesh()), name="plasma")then chained:case = (pops.Case(name="plasma") .block("ne", physics=model, spatial=pops.FiniteVolume()) .field(pops.fields.FieldProblem(unknown=phi, equation=eq, solver=mg)) .time(pops.time.Program(...)))
Each assembly setter RETURNS the case so calls chain. The default
layoutis a single-levelUniformover a defaultCartesianMesh.validate()runs structural checks; multi-block assemblies, named non-Poisson elliptic fields and output / checkpoint policies all lower (C3 / C1-System / C4);compile/bindflow them onto the runtime. AMR per-level output (Plotfile/ level filtering) is the one remaining deferral (ADC-511).A Case CONTAINS descriptors (the layout, the blocks’ physics, the field problems) but is NOT itself a
pops.descriptors.Descriptor(Spec 5 sec.6 table / sec.15). It exposes the same inspectable surface –requirements/capabilities/options/available/validate/inspect/lower– implemented DIRECTLY here, so it duck-types as a route-describing object without inheriting a descriptor identity.- property amr¶
The AMR refinement-policy handle (raises
ValueErrorif layout is not AMR).
- available(context=None)[source]¶
An EXPLAINABLE availability status, computed from the parts (no runtime).
- block(name, physics, spatial=None)[source]¶
Declare a physics block
name(itsphysicsmodel is REQUIRED). Chains.
- explain_routes()[source]¶
Return a printable route matrix sourced from the C++ authoritative facts (sec.13.12.1).
Spec 5 sec.13.12.1 (criterion #37): a
feature x layout x backend x platform -> status / limitationmatrix whose capability VALUES come from the C++ core (_pops.module_capabilities()), NOT a Python-derived walk. Each transport feature (uniform / amr / mpi / gpu / stride / named_fields / partial_imex_mask) is reported with the nativeavailable/unavailablestatus the built module decides, plus the route facts the descriptor catalog adds (the layouts this case can author). Inert: it imports_popsLAZILY and reads metadata only – no compile, bind or run.When
_popsis absent or predatesmodule_capabilities(old build), the matrix reports every native status asunknownwith that reason rather than fabricating a value.
- field(field_problem)[source]¶
Register an elliptic
FieldProblem(keyed on its name). Chains.
- lower(context=None)[source]¶
The inert lowering record for the assembly (metadata only; no computation).
Mirrors the descriptor
lower()shape so a Case duck-types as a route-describing object, without being apops.descriptors.Descriptor. It runs no numeric loop and touches no runtime;pops.compiledoes the real lowering throughcompile_problem.
- native_id = None¶
A Case names a pure-Python assembly, not a single native C++ symbol.
- output(policy)[source]¶
Attach an output / checkpoint policy. Chains.
The policy is stored on the Case;
bind()flows it onto the boundSystemandrun()fires it at each policy cadence through the existing write / checkpoint writers (ADC-509). AMR per-level output (Plotfile/ level filtering) remains deferred (ADC-511).
- param(name, default=None, *, kind=<object object>)[source]¶
Declare a runtime/const parameter and its default value. Chains.
The KIND is a TYPED param object (Spec 5 sec.7), not a
kind=string:case.param(pops.physics.RuntimeParam("alpha", 1.0))/case.param(pops.physics.ConstParam("gamma", 1.4))/case.param("alpha", 1.0)(const shorthand). A barekind="const"/"runtime"keyword is REJECTED – the string route is removed; the typed sugars lower to the SAME{default, kind}record.
- validate(context=None)[source]¶
Structural validation; raise LOUD on a structural error (never fake success).
Checks the layout, that there is at least one block each with a physics model, that every field problem is itself valid, and that no block and field share a name. Multi-block assemblies, named non-Poisson elliptic fields and output / checkpoint policies now LOWER (C3 / C1-System / C4); each output entry is only confirmed to be a real policy descriptor here (a non-policy object is rejected loud). AMR per-level output is deferred (ADC-511).
Runtime facade returned by bind¶
The object returned by pops.bind is the simulation facade. User code should
obtain it from bind, not by hand-building runtime classes.
- class pops.System(config=None, mesh=None, **cfg_kw)[source]¶
Bases:
_SystemInstall,_SystemUnifiedInstall,_SystemAuxState,_SystemDiagnostics,_SystemIOThe system/coupler: composes blocks, shares a Poisson, advances the whole.
Low-level runtime. The documented PUBLIC path is the typed
pops.Caseassembly lowered bypops.compileand wired bypops.bind->sim.run(...); the per-stepstep_cfl/step/step_adaptivemethods (andadd_block/add_equation/set_poisson) are the low-level seampops.bindbuilds on and the tests use, not the recommended front door.add_block takes a composed model (pops.Model(…)) + Spatial / Explicit / IMEX objects. Everything else (set_poisson, set_density, step, step_cfl, step_adaptive, diagnostics, primitives eval_rhs/get_state/set_state) is forwarded to the compiled facade.
GEOMETRY: the choice lives in a MESH object passed as mesh= (pops.CartesianMesh / pops.PolarMesh), NOT in the scheme (pops.FiniteVolume stays reconstruction + Riemann + variables). Default (mesh=None or pops.CartesianMesh) = square domain, bit-identical to the history. pops.PolarMesh (global ring) is WIRED in System.step (Phase 2b): polar ExB transport + polar Poisson + aux in local basis (e_r, e_theta). Limits: scalar ExB transport, single-rank, no cart<->polar coupling.
- static abi_key()[source]¶
Module ABI key (compiler, C++ standard, signature of the pops headers). Compared to that of a native loader by add_native_block. Also exposed as a class attribute (the __getattr__ delegate only covers instances), so pops.System.abi_key() works.
- property amr¶
The AMR runtime inspection surface does not apply to a uniform
System.Systemis single-level: it carries no AMR hierarchy, sosim.amr(the live patch / regrid / ghost / reflux / checkpoint reports of Spec 5 sec.8.12) is anAmrSystem-only handle. Build anpops.AmrSystemfor a refined run, or use the STATIC authoring reportpops.inspect_amr(layout)for a layout descriptor. Accessing it raises a clearAttributeError(sourced in__getattr__so the message is single).
- block_names()[source]¶
Names of the added blocks, in order (useful for a Python integrator).
Delegates to the C++ block registry (single source), so it includes the blocks loaded via add_dynamic_block (.so JIT) and add_compiled_block (.so AOT), not only add_block.
- profile(profile=None)[source]¶
Typed profiling context manager (Spec 5 sec.12.5, criteria 41-44).
Usage:
with sim.profile(pops.Profile.Basic()) as prof: sim.run(0.1) print(prof.summary())
@p profile is a
pops.Profilelevel (Profile.Basic()/Profile.Advanced()); with no argument the level comes fromPOPS_PROFILE(unset /off-> Basic()). The manager enables the native profiler on entry and disables it on exit, so a plain run (nowith sim.profile()) leaves profiling off – the off-by-default contract.prof.summary()returns apops.PerformanceSummary.
- run(t_end, cfl=None, max_steps=1000000, output_dir=None)[source]¶
Advance up to t_end by CFL steps (sugar: while time() < t_end: step_cfl(cfl)).
@p cfl: Courant number passed to step_cfl. When omitted (None) it defaults to the CFL pinned by an installed
CompiledTime(cfl=X)cadence, else 0.4 – so a numeric cadence cfl actually takes effect on a baresim.run(t_end)rather than being silently ignored. @p max_steps: guard (avoids an infinite loop if dt -> 0). @p output_dir: when output / checkpoint policies were flowed onto this System (pops.bindfrom a Case with.output(policy)), the directory the run writes them to; each policy fires at its own cadence through the existing write()/checkpoint writers (C4 / ADC-509). Defaults to the current directory when policies are present and output_dir is omitted. Returns the number of steps taken. cf. DSL_MODEL_DESIGN.md section 6.
- class pops.AmrSystem(config=None, **cfg_kw)[source]¶
Bases:
_AmrSystemEquation,_AmrSystemIORefined counterpart of System : one or SEVERAL blocks carried on an AMR hierarchy.
SINGLE-BLOCK (1 add_block) : historical AmrCouplerMP path (dynamic regrid, reflux). MULTI-BLOCK (>= 2 add_block) : N blocks co-located on ONE SHARED AMR hierarchy (AmrRuntime engine), SYSTEM Poisson with co-located SUMMED right-hand side (Sum_b q_b n_b), conservation PER BLOCK. The blocks may have DIFFERENT SPATIAL SCHEMES, a per-block TEMPORAL TREATMENT (explicit / imex), MULTIRATE (substeps / stride), COUPLED inter-species SOURCES and the multi-block production DSL. In multi-block the block NAME indexes set_density(name) / mass(name) / density(name).
UNION-OF-TAGS REGRID (multi-block + regrid_every > 0) : the shared hierarchy is re-gridded from the UNION of the tags of all blocks. Two criteria compose (cell-by-cell OR) :
PER-BLOCK VARIABLE (set_refinement(threshold, variable=, role=)) : refine where the SELECTED variable of a block exceeds threshold. Default = component 0 (historical density), bit-identical ; ADC-296 lets you select it per block by name (variable=) or physical role (role=), resolved against the block’s conserved variables (a block lacking the name/role raises, no silent component-0 fallback). Non-default selector is multi-block only (mono-block / compiled .so : component 0 only) ;
grad phi(set_phi_refinement(grad_threshold)) : refine where the norm of the gradient of the electrostatic potential exceeds grad_threshold (diocotron ring edge). Disabled by default (grad_threshold <= 0). MULTI-BLOCK only.
regrid_every == 0 -> FROZEN hierarchy (regrid never called, bit-identical).
- add_block(name, model, spatial=None, time=None)[source]¶
Installs an evolved block composed of NATIVE BRICKS on the shared AMR hierarchy.
Low-level runtime seam. The documented PUBLIC path is the typed
pops.Case(layout=AMR(...))assembly lowered bypops.compileand wired bypops.bind(which calls this internally);add_blockstays for that seam and the tests.Refined counterpart of System.add_block. The 1st add_block opens the single-block path (AmrCouplerMP : dynamic regrid, reflux) ; each subsequent add_block co-locates one more block on THE SAME hierarchy (AmrRuntime engine, system Poisson with summed right-hand side). In multi-block the name indexes set_density(name) / mass(name) / density(name). The arguments are marshaled to the C++ facade (AmrSystem::add_block), which validates the block against the model. For a compiled DSL model (.so) or a dispatch on the model type, use add_equation.
@param name unique name of the block. @param model an pops.Model(…) (ModelSpec : composed native bricks). @param spatial spatial discretization, an pops.Spatial(…) / pops.FiniteVolume(…) (default
minmod + rusanov + conservative). Limiter (none / minmod / vanleer / weno5 ; weno5 = 3 ghosts, the coupler allocates its levels at Limiter::n_ghost and the regrid inherits n_grow()), Riemann flux (rusanov / hll / hllc / roe) and reconstructed variables (conservative / primitive).
- @param time temporal treatment, an pops.Explicit (default) / pops.IMEX / pops.SourceImplicit.
Carries substeps, stride (multirate hold-then-catch-up), the implicit mask (implicit_vars / implicit_roles) and the Newton options, threaded to the C++. newton_diagnostics is wired in native multi-block and rejected at the C++ build in single-block (the coupler does not aggregate a report).
- @throws TypeError if time is an pops.Split / pops.Strang (Schur-condensed source stage) :
go through add_equation(…, time=pops.Strang(…)) (amr-schur path).
spatial.positivity_floor > 0 (ADC-259) floors the Density-role face states AND the coarse-fine fine ghost means to >= floor on the AMR transport (Zhang-Shu, parity with the uniform System). Guarantee = face / ghost-state Density positivity only (order-1 fallback), NOT updated-mean nor pressure positivity. A model without a Density role rejects it at the first step. The COMPILED .so path carries it too now (ADC-322): a loader regenerated against the current headers marshals the floor (add_equation on a CompiledModel, add_native_block).
- add_coupling(coupling)[source]¶
Add a generic inter-species COUPLED SOURCE (pops.dsl.CoupledSource(…).compile(…)) on the SHARED AMR hierarchy (MULTI-BLOCK), refined counterpart of System.add_coupling. The source is transported as bytecode and interpreted on the C++ side (AmrSystem.add_coupled_source; no per-cell Python callback). The coupling frequency (CoupledSource.frequency) is honored: constant -> dt bound dt <= cfl/mu; Expr -> PER-CELL frequency mu(U) evaluated on the COARSE grid at each step_cfl (the freq_prog_* vectors are forwarded). Must be called BEFORE the first step (the source is frozen then injected at the lazy build of the runtime engine).
- property amr¶
The live AMR runtime inspection handle (Spec 5 sec.8.12), an
pops.runtime.amr.AmrRuntimeView.Bound to THIS built hierarchy:
sim.amr.patch_table()/sim.amr.hierarchy_snapshot()/sim.amr.explain_regrid()/explain_ghosts()/explain_reflux()/explain_checkpoint()return short, inert reports of the patches that actually exist, the regrid cadence in force, and the ghost / reflux / checkpoint route limitations. The view READS the runtime (the box accessors + the retained config); it builds / allocates / steps NOTHING.System.amrdoes not exist: the inspection surface is AMR-specific (a uniform System carries no hierarchy). Usepops.inspect_amr(layout)for the STATIC authoring report.
- coarse_local_boxes()[source]¶
Number of coarse (base) boxes owned by this MPI rank (ADC-319 diagnostic).
The base level is a MultiFab whose boxes are spread across ranks by a DistributionMapping. Returns this rank’s owned-fab count (level-0 local_size()). With distribute_coarse=True the base is split into several boxes round-robin, so each rank owns a strict subset and the coarse transport is distributed; a replicated or single-box base owns the full count on every rank. Compare with coarse_total_boxes() and pops.n_ranks() to confirm MPI strong-scaling of the base. Triggers the lazy build like n_patches().
- coarse_total_boxes()[source]¶
Total number of coarse (base) boxes across all ranks (ADC-319 diagnostic).
Identical on every rank (BoxArray size, no communication). With distribute_coarse=True this is the number of round-robin base tiles; with a single-box or replicated base it is 1. A rank distributes the coarse transport when coarse_local_boxes() < coarse_total_boxes(). Triggers the lazy build like n_patches().
- explain_bind(compiled)[source]¶
A printable
pops.codegen.inspect_report.BindReportof @p compiled vs this AMR sim (Spec 5 sec.12.1, criterion #15). INERT parity withSystem.explain_bind: reads the artifact’s DECLARED bind inputs (compiled.arguments()) and the blocks / named aux wired on this AmrSystem, then reuses ADC-463collect_missing_arguments()to report PROVIDED vs still-REQUIRED per group. It binds nothing and mutates nothing – a read-only bind plan.
- field(name)[source]¶
Return the solved potential of a NAMED elliptic field (ADC-428) as an (n, n) array.
Read-back of a second elliptic field declared via m.elliptic_field and lowered on the AMR layout: solves the hierarchy fields if needed (so it is current even before any step) then reads the field’s coarse potential. AMR counterpart of reading System.aux_field(block, name) for an elliptic field. @throws if the field is unregistered (or the system runs the single-block coupler, which carries no named field).
- patch_rectangles()[source]¶
Physical rectangles (x0, y0, width, height) of the current fine patches, in [0, L]^2.
Converts patch_boxes() (index space, inclusive corners) into physical coordinates. The level spacing is dx = L / (n << level) (ratio 2 per level) ; a patch [ilo..ihi] x [jlo..jhi] covers (ihi - ilo + 1) cells in x from x0 = ilo * dx (and likewise in y). Grid convention ne[j, i] -> index 0 = x (i), index 1 = y (j), consistent with density() and an imshow with extent [0, L, 0, L]. Convenient to plot the REAL patches (e.g. matplotlib Rectangle) without rebuilding a density proxy. Returns a list of (x0, y0, w, h), one per fine patch (all fine levels combined). Query (between steps) : triggers the lazy build like n_patches(), no cost on the hot path.
- profile(profile=None)[source]¶
Typed AMR / MPI profiling context manager (Spec 5 sec.12.5, criterion 43).
Usage:
sim.set_refinement(threshold) # regrid_every > 0 in the config with sim.profile(pops.Profile.Basic()) as prof: for _ in range(n_steps): sim.step_cfl(0.4) print(prof.summary().by_amr_mpi()) # regrid / fill_boundary / average_down timings
@p profile is a
pops.Profilelevel ; with no argument it comes fromPOPS_PROFILE(unset /off-> Basic()). The manager enables the native AMR profiler on entry and disables it on exit (off-by-default contract).prof.summary().by_amr_mpi()surfaces the AMR phase timings + counters as soon as a regrid / solve fired under the multi-block engine.
The full runtime classes still expose lower-level methods because the C++/pybind layer and tests use them. They are intentionally not examples for user authoring.
Native brick composition¶
Native bricks are already-compiled C++ pieces that can be assembled into a
model. They are useful presets, but the public run flow is still Case -> compile -> bind -> run.
- pops.Model(state, transport, source, elliptic)[source]¶
Compose a model (ModelSpec) from state, transport, source, elliptic bricks.
Validates the state <-> transport consistency (Scalar with ExB; compressible FluidState with CompressibleFlux; isothermal with IsothermalFlux) and carries the parameters into the spec.
- class pops.FluidState(kind='compressible', gamma=1.4, cs2=0.5, vacuum_floor=0.0)[source]¶
Bases:
objectFluid state. kind = “compressible” (gamma) or “isothermal” (cs2).
vacuum_floor (isothermal only, ADC-77): quasi-vacuum density floor. When > 0 the model computes the velocity as u = m/max(rho, vacuum_floor), bounding the wave speed and the advective flux where the flow evacuates the background (rho -> ~0). It does NOT modify the conserved state (only the velocity estimate). 0 (default) = inactive (bit-identical). This is independent of the spatial positivity_floor (the Zhang-Shu reconstruction limiter): the two address different failure modes and must be enabled separately. Honored on the native pops.Model(…) / System / AmrSystem path; the compiled/DSL path (pops.CompositeModel / JIT / AOT) does not carry it yet, so set it on the native path.
- classmethod compressible(gamma=1.4)[source]¶
Typed constructor for the COMPRESSIBLE fluid state (Spec 5 sec.14.2.5).
pops.FluidState.compressible(gamma=1.4)is the typed equivalent ofpops.FluidState(kind="compressible", gamma=1.4): it builds the SAME inert state object (kind=”compressible”, carrying gamma -> spec.gamma via Model) instead of selecting the kind with a magic string. Pairs with CompressibleFlux (4 variables [rho, rho_u, rho_v, E]).
- classmethod isothermal(cs2=0.5, vacuum_floor=0.0)[source]¶
Typed constructor for the ISOTHERMAL fluid state (Spec 5 sec.14.2.5).
pops.FluidState.isothermal(cs2=0.5, vacuum_floor=0.0)is the typed equivalent ofpops.FluidState(kind="isothermal", cs2=0.5, vacuum_floor=0.0): it builds the SAME inert state object (kind=”isothermal”, carrying cs2 -> spec.cs2 and vacuum_floor -> spec.vacuum_floor via Model). Pairs with IsothermalFlux (3 variables [rho, rho_u, rho_v]). See the class docstring for the vacuum_floor (ADC-77) semantics.
- class pops.ExB(B0=1.0)[source]¶
Bases:
objectScalar advection by the E x B drift (magnetic field B0).
- class pops.CompressibleFlux[source]¶
Bases:
objectCompressible Euler flux (gamma comes from the FluidState state).
- class pops.IsothermalFlux[source]¶
Bases:
objectIsothermal Euler flux (cs2 comes from the FluidState state).
- class pops.PotentialForce(charge=1.0)[source]¶
Bases:
objectPotential force (q/m) rho E on the momentum (+ work if 4 vars).
- class pops.MagneticLorentzForce(charge=1.0)[source]¶
Bases:
objectMAGNETIC Lorentz force q (v x B_z) on the momentum (native C++ brick pops::MagneticLorentzForce, exposed to the Python API by the 2026-06 audit).
EXPLICIT regime (moderate omega_c): pointwise algebraic term, no work (F . v = 0, energy unchanged). Reads B_z from the aux channel (canonical component 3): call
sim.set_magnetic_field(Bz)to populate it. Requires a fluid transport >= 3 variables (momentum on 2 axes); rejected on a scalar. The STIFF regime (large omega_c) goes through the condensed stage pops.CondensedSchur (Schur), NOT through this explicit brick.charge= q/m, sign included (same convention as PotentialForce).
- class pops.PotentialMagneticForce(charge=1.0)[source]¶
Bases:
objectElectrostatic force + magnetic Lorentz SUMMED: (q/m) rho E + q (v x B_z) (native C++ brick CompositeSource<PotentialForce, MagneticLorentzForce>, the full magnetized diocotron force). Same q/m for both forces (same species). Reads B_z (set_magnetic_field); requires a fluid transport >= 3 variables.
charge= q/m, sign included.
Mesh and layouts¶
- class pops.mesh.cartesian.CartesianMesh(n=64, L=1.0, periodic=True)[source]¶
Bases:
MeshDescriptorCARTESIAN mesh (implicit default): square domain [0, L]^2, n x n cells.
pops.System(mesh=pops.mesh.CartesianMesh(n, L, periodic))is STRICTLY equivalent (bit-identical) topops.System(n=n, L=L, periodic=periodic). Provided for symmetry withpops.mesh.PolarMesh(the geometry choice is explicit on both sides).
- class pops.mesh.layouts.Uniform(mesh, embedded_boundary=None)[source]¶
Bases:
MeshDescriptorA single-level (uniform) mesh layout.
- class pops.mesh.layouts.AMR(base, max_levels=2, ratio=2, regrid=None, patches=None, refine=None, nesting=None, checkpoint=None, output=None)[source]¶
Bases:
MeshDescriptorAn adaptively refined mesh layout (Spec 5 sec.5.10 / sec.8.5).
AMR(base=mesh, max_levels=2, ratio=2, regrid=RegridEvery(20), patches=PatchLayout(...), refine=TagUnion(...), nesting=ProperNesting(...), checkpoint=CheckpointPolicy(...)).Declares its limitations explicitly: the current native AMR route supports
max_levels <= 2atratio == 2(seepops.mesh.amr.NATIVE_MAX_LEVELS/NATIVE_RATIOS); a request beyond that is refused byavailable()/validate()with a clear message instead of being silently clamped.
AMR policies live under pops.mesh.amr.
Fields¶
- class pops.fields.FieldProblem(name=None, unknown=None, equation=None, inputs=(), coefficients=None, bcs=(), nullspace=None, outputs=None, postprocess=None, solver=None)[source]¶
Bases:
DescriptorA typed elliptic field problem: an unknown solved from an
Equation.FieldProblem(unknown=phi, equation=(-laplacian(phi) == rhs), solver=GeometricMG()). Theinputs/coefficients/bcs/nullspace/outputs/postprocessfields are typed descriptors (frompops.fields); they are stored and surfaced, not interpreted, here.validate()rejects a non-Equationequation (a Python bool produced by==on plain values is the common mistake) and a missing solver.- solve(schedule, policy)[source]¶
Record an inert field-solve cadence (a schedule + a not-due policy).
Pairs a typed
pops.time.Schedule(WHEN to solve – e.g.every(4)to solve every fourth macro-step, orwhen(cond)) with a typedFieldSolvePolicy(HoldPrevious/Recompute) deciding what happens on a step where the solve is not due. The pair is stored ascadenceand surfaced byinspect(); the method returnsselfso it chains after construction.This is AUTHORING metadata only. It deliberately does NOT lower the cadence into the Program / codegen: honouring a non-trivial field-solve cadence at runtime (the cached field carry, the residual gate) is the codegen-adjacent follow-up. The cadence is recorded and inspectable, never silently executed.
- Parameters:
schedule – A typed
pops.time.Schedule(built withevery/when/always/ …). A bareintorstris rejected with a clearTypeError– the cadence is a typed object, not a free string or count.policy – A typed
FieldSolvePolicy(HoldPreviousorRecompute). A string such as"hold"is rejected with a clearTypeError.
- Returns:
self, so the call chains after construction.- Return type:
- Raises:
TypeError – When @p schedule is not a
pops.time.Scheduleor @p policy is not aFieldSolvePolicy.
- class pops.fields.PoissonProblem(name=None, unknown=None, equation=None, inputs=(), coefficients=None, bcs=(), nullspace=None, outputs=None, postprocess=None, solver=None)[source]¶
Bases:
FieldProblemA standard Poisson problem
-laplacian(phi) == rhs.Refines
FieldProblemby requiring the equation’s principal operator to be an elliptic Laplacian / div(coeff*grad) (so a non-elliptic equation is rejected up front).
- class pops.fields.ScreenedPoissonProblem(name=None, unknown=None, equation=None, inputs=(), coefficients=None, bcs=(), nullspace=None, outputs=None, postprocess=None, solver=None)[source]¶
Bases:
PoissonProblemA screened Poisson problem
-laplacian(phi) + k*phi == rhs(Debye screening).Requires a zeroth-order reaction term (
k*phi) on top of the principal operator.
- class pops.fields.AnisotropicPoissonProblem(name=None, unknown=None, equation=None, inputs=(), coefficients=None, bcs=(), nullspace=None, outputs=None, postprocess=None, solver=None)[source]¶
Bases:
PoissonProblemAn anisotropic Poisson problem
-div(A grad phi) == rhs(variable / tensor coefficient).Requires a
div(coeff*grad(phi))principal operator (the variable-coefficient form).NOTE: the form is authorable + validated here, but lowering a variable / tensor coefficient in the elliptic codegen is the coordinated follow-up (ADC-491); today the native elliptic operator solves a constant-coefficient
div(eps grad).
Boundary conditions, right-hand-side descriptors, coefficients, nullspaces, and
field cadence policies live in pops.fields.bcs, pops.fields.rhs,
pops.fields.coefficients, pops.fields.nullspace, and pops.fields.policies.
Numerics¶
Numerical choices are descriptors.
from pops.numerics.riemann import HLL, HLLC, Roe, Rusanov
from pops.numerics.reconstruction import MUSCL, WENO5Z
from pops.numerics.reconstruction.limiters import Minmod, VanLeer
from pops.numerics.terms import Flux, SourceTerm
The top-level pops.FiniteVolume(...) function remains the current bridge to
the runtime spatial brick.
Time¶
pops.time is the language:
- class pops.time.Program(name)[source]¶
Bases:
_ProgramCore,_ProgramLocal,_ProgramSolve,_ProgramAuthoring,_ProgramPasses,_ProgramInspectA compiled time program (builder mode). Holds the SSA value list and the committed blocks. The Python object only BUILDS the IR; it is never executed numerically during
sim.step. Authoring methods come from the mixins; C++ emission is delegated topops.codegen.program_codegenviaemit_cpp_program().- emit_cpp_program(model=None)[source]¶
Generate the C++ source of a problem.so implementing this Program (codegen).
Thin authoring entry point: delegates to the free function
pops.codegen.program_codegen.emit_cpp_program(), imported lazily so thepops.timepackage never importspops.codegen/_popsat module scope. See the codegen function for the full lowering contract.
- class pops.time.CompiledTime(substeps=1, stride=1, cfl='default')[source]¶
Bases:
objectRecord of a compiled Program’s macro-step cadence (substeps / stride).
A compiled Program OWNS the whole step body: it is installed via sim.install_program and driven by sim.step(dt). Its cadence is applied to the System with sim.set_program_cadence(substeps, stride) (call it after install_program); a CompiledTime just records those values. The compiled program is NOT attached via sim.add_equation(time=CompiledTime(…)) – that path is rejected with an explicit error (the transport policy passed to add_equation is a native pops.Explicit/etc.; the compiled program is installed separately). substeps and stride are wired (ADC-411) as a SYSTEM-level orchestration AROUND the opaque program closure (System.set_program_cadence, mirroring the native per-block advance loop): substeps=n runs the program n times over eff_dt/n; stride=M runs the whole program once per M macro-steps with eff_dt = M*dt (GLOBAL hold-then-catch-up, the clock still ticks every macro-step).
- Two semantic limits to keep in mind (cf. system_stepper.hpp):
substeps > 1 is bit-exact vs native pops.Explicit(substeps=n) ONLY for an UNCOUPLED / transport-only program: program_step_(h) re-runs the WHOLE program (its solve_fields included), whereas native substeps subdivides ONLY the transport (solve_fields runs once).
stride here is GLOBAL (a compiled program is one whole-system closure), so it equals native per-block stride only for a single-block system (or all blocks sharing the stride).
A NUMERIC cfl (e.g. cfl=0.5) is wired: it is applied at RUNTIME by sim.run(cfl=…), whose cfl defaults to the installed cadence’s cfl when the caller passes none, so a bare sim.run(t_end) after bind(…, cadence=CompiledTime(cfl=0.5)) advances at cfl=0.5 (the per-block CFL dt is computed in adc_cpp and drives the installed Program). A self-computed cfl SUB-PROGRAM (cfl=”program”) is still deferred (the Program would export its own dt bound); it fails loud rather than being silently ignored.
Ready schemes are in pops.lib.time.
Solvers¶
from pops.solvers.elliptic import FFT, GeometricMG
from pops.solvers.krylov import BiCGStab, CG, GMRES, Richardson
from pops.solvers.nonlinear import Newton
Users configure provided compiled solvers. They do not author solver loops in Python.
Compilation and inspection¶
- class pops.codegen.loader.CompiledProblem(so_path, program, model, abi_key, cxx, std, libraries=None, problem_hash=None, cache_key=None, compile_command=None, generated_sources=None, codegen_env=None)[source]¶
Bases:
objectResult of
pops.compile_problem(...); a generatedproblem.so(a compiled time Program) plus the metadata to install + reproduce it. Install it withsim.install_program(compiled.so_path)AFTER the physical block has been added (sim.add_equation/sim.add_block); the Program then drivessim.step(dt)entirely in C++ viaProgramContext.The
.sois compiled against the pops headers with the SAME Kokkos toolchain as the loaded _pops module (cf.pops_loader_build_flags), so its ABI key matches andSystem::install_programaccepts it.os.fspath(compiled)returnsso_path(it can be passed where a path is expected).- arguments()[source]¶
The runtime inputs this artifact expects at
System.install(Spec 5 sec.12.2, #44-45).Returns an
pops.codegen.inspect_compiled.Argumentslisting – WITHOUT any bind or runtime data – the instances (state space / components / required), params (type / kind / required), aux (layout / required), solvers (problem / solver), outputs and the runtime layout the artifact expects. Sourced from the carried Program (the blocks it commits, the field solves it performs) and the physical model (its state / params / aux). It is DISTINCT fromrequirements()-style compile constraints:argumentslists what you must SUPPLY to bind. It allocates and reads nothing.
- property cache_key¶
The (problem source | abi_key | backend/target) cache key of the
.so(sec.12.4, #48).The sha256 the out-of-source build cache keys the artifact on; reproducing it requires the same program, headers, compiler and C++ standard.
Nonefor an externally built handle.
- property codegen_dir¶
Directory the compiled
.so(and any generated source) lives in (sec.12.4, #48).The out-of-source cache directory the .so was written to (
os.path.dirname(so_path)); the generated.cpp– whencompile_problem(debug=True)wrote one – sits beside it.Noneonly if the handle carries noso_path.
- property codegen_env¶
The resolved codegen
POPS_*environment snapshot that governed this compile (sec.12.4).A
pops.codegen.env.CodegenEnvrecording the EFFECTIVE settings (env defaults already overridden by any explicit argument): log level, codegen dir, keep-generated, dump-IR / dump-CPP, cache dir, profile, autotune level, and the UNSAFECodegenEnv.jit_backdoorgate. Surfaced ininspect()so the active env state is never hidden (criterion #47).Nonefor a handle built outsidecompile_problem.
- property compile_command¶
The REDACTED compiler invocation that built the
.so(sec.12.4, #49).A single command string with the ephemeral temp source replaced by the generated-source path and any secret-looking token masked (cf.
_redact_compile_command).Noneon a cache HIT (the .so was not rebuilt this call – a documented absence, never a fabricated command) or for an externally constructed handle. Recompile withforce=Trueto populate it.
- dump_cpp(target)[source]¶
Write the generated C++ source of the problem
.so(REUSES the existing emit).Calls the EXISTING
Program.emit_cpp_program(model=...)codegen (the same sourcecompile_problemcompiles) and writes it. @p target is a directory (the source is written as<program_name>.cppinside it) OR a path ending in.cpp(written verbatim); the parent directory must exist. Returns the written file path. The carried model is passed so a Program whose IR names a model source / linear kernel lowers (without it such a Program raises the SAME NotImplementedError the compile path raises – it is not faked). Raises a clear error if this handle carries no Program.
- dump_ir(path=None)[source]¶
Write the serialized Program IR (JSON) – the SAME serialization
_ir_hashdigests.EXPOSES the existing codegen: the lowered
pops.time.Program’s_serialize()blob (its nodes, commits, block order, optional dt bound) as indented, sort-keyed JSON – byte-stable run to run, the WHAT the.sowas built from. Writes to @p path if given (returns the path), else returns the JSON string. Raises a clear error if this handle carries no Program.
- dump_schedule(path=None)[source]¶
Write the schedule / commit order of the Program (the block advance order).
EXPOSES the lowered schedule WITHOUT running it: the committed blocks in the runtime block index order (
_block_indices: the order the Program first declares each block viaP.state, the orderinstall_programbinds them), each with the IR id of its committed State value. A plain, deterministic text listing. Writes to @p path if given (returns the path), else returns the string. Raises a clear error if this handle carries no Program.
- estimate_memory(mesh, *, platform=None, layout=None)[source]¶
A FORMULA-based memory estimate on
mesh(Spec 5 sec.12.3, #46).Returns an
pops.codegen.inspect_compiled.MemoryEstimate: the state / field-output / aux / RHS-scratch / state-scratch / scalar-field / Krylov / multigrid / AMR-patch / halo / MPI-buffer byte budgets, computed as a FORMULA over the mesh shape and the artifact’s static cost (Program.estimate) + component counts. It NEVER allocates aMultiFab; every assumption is inMemoryEstimate.assumptionsand the estimate is CONSERVATIVE. @p mesh anpops.mesh.CartesianMesh(or an int / 2-tuple of extents); @p platform an optional hint ("mpi"adds the halo-exchange buffer); @p layout an optionalpops.mesh.layouts.AMR/Uniformfor an AMR hierarchy estimate (conservative; full-refinement worst case).
- property generated_sources¶
The generated source files written for inspection (sec.12.4, #49).
The
.cppfilescompile_problem(debug=True)(orPOPS_KEEP_GENERATED) persisted next to the.so(the default keeps the source only in a TemporaryDirectory, so this is empty unless one of those was set). A list (possibly empty), neverNone.
- inspect()[source]¶
A printable
pops.codegen.inspect_report.CompiledReportof this artifact (sec.12.1).The
print(compiled.inspect())summary: name, backend, platform, layout, blocks (+ state / components), fields (+ solver), program (+ commits), the REQUIRED runtime inputs (states / params / aux fromarguments()), the on-disk artifacts (so_path / abi_key / cache_key) and the bind-pending status line. It AGGREGATES the metadata this handle already carries (no compile / bind / runtime read);to_dict()serialises it.
- inspect_amr(layout=None)[source]¶
STATIC AMR report on this compiled artifact (Spec 5 sec.8.12 / sec.8.4).
A compiled time
Programcarries NO AMR layout descriptor (it lowers a whole-system time program, a single-levelSystemconcept today –AmrSystemhas noinstall_programseam). So this delegates to the top-levelpops.inspect_amr()on an EXPLICITlayoutargument (anpops.mesh.layouts.AMR/Uniformdescriptor), and withlayout=Nonereturns the native AMR envelope report – never a fabricated hierarchy the artifact does not carry. @p layout an optional AMR / Uniform layout descriptor (default: the native envelope).
- inspect_capabilities()[source]¶
The descriptor capability rows relevant to THIS compiled artifact (sec.12.1).
Delegates to the top-level
pops.inspect_capabilities()machinery (the descriptor-sourced capability matrix) and SCOPES it to the descriptor categories a compiled time Program can select at bind – the Riemann / reconstruction / limiter / projection bricks and the mesh layouts (the solver / field catalogs are bind inputs, kept too). Returns the same printablepops.CapabilityMatrix. PURE: it imports only the inert authoring catalogs, never_pops(cf.pops.inspect_capabilities()).
- manifest()[source]¶
The RICH self-describing manifest of this artifact (Spec 5 sec.13.12, #36).
Returns a
pops.external.CompiledArtifactManifest: the ABI identity (abi_key/required_headers_sig), the model name, the blocks / variables / roles, the required aux, the const / runtime params, the ghost depth, the field outputs and thesupports_*capability flags (uniform / AMR / MPI / GPU known from the backend caps; stride / partial-IMEX mask / named fields honestlyNoneuntil the C++ codegen emits them). It AGGREGATES the metadata this handle already carries (viaarguments()+ the carried model +abi_key); it binds, dlopens and runs nothing. The widening of the thinpops.external.CompiledManifest(a brick-id / category list) into the full artifact self-description Spec 5 sec.13.12 requires.
- property problem_hash¶
Stable hash of the program SOURCE the
.sowas compiled from (sec.12.4, #48).The sha256 of the emitted C++ program text – the cache identity (the WHAT).
Nonefor a handle built outsidecompile_problem(it records no source hash); useprogram_hashfor the in-memory Program’s IR hash, which is always available.
- requirements()[source]¶
The COMPILE-TIME constraints of this artifact (sec.12.1), DISTINCT from
arguments().Returns a
pops.codegen.inspect_report.RequirementsReport: the model capabilities the lowered route relies on (wave_speeds/hllc_star_state/roe_dissipation, read from the carried model’s emitted flags), the required descriptors (the spatial scheme is a BIND input, reported as such), and the layout / backend / ABI constraints. A piece genuinely unknowable from today’s metadata is stated honestly inunknown, never fabricated.arguments()lists what you SUPPLY at bind;requirementslists what the compiled route NEEDS from the model + toolchain.
- runtime_param_routes()[source]¶
(per_block, defaults)routing the Program’s RUNTIME parameters to the per-PROGRAM-blockset_program_paramsvectors (ADC-510): per_block maps a program block index to its param names in within-block index order (matching the.sometadata + the lowered read), defaults a name to its declaration value. Built via the SAMEprogram_param_entriesthe codegen emits. No bind.
- scratch_plan()[source]¶
The scratch-buffer liveness plan of this artifact’s time Program (Spec 5 sec.13.11.3, #38).
Returns a
pops.codegen.scratch_plan.ScratchPlan: the per-category scratch counts (state / rhs / scalar-field), the PROVABLY-reusable buffers (scratch nodes whose SSA live ranges are disjoint, so the codegen may share one buffer), the REJECTED reuse (with the reason – a still-live occupant or an aux/field barrier) and the PERSISTENT Krylov / multigrid solver buffers. Computed by a liveness analysis over the carried Program IR (Program.scratch_liveness/buffer_reuse_report); the step-body reuse is EXACT, the persistent solver counts are conservative and labelled so. It NEVER binds, dlopens or allocates – it is inspectable BEFORESystem.install. Raises a clear error if this handle carries no Program.
- class pops.codegen.loader.CompiledModel(so_path, backend, adder, cons_names, cons_roles, prim_names, n_vars, gamma, n_aux, params, caps, abi_key, model_hash, cxx, std, target='system', hllc=False, roe=False, aux_extra_names=None, wave_speeds=False, elliptic_field_names=None)[source]¶
Bases:
objectResult of
m.compile(...): packages the produced.so+ EVERYTHING needed to wire it correctly (dispatch adder, ABI diagnostic, reproducibility). Replaces the historical pair (str so_path, adder_for(backend)) with a single object.The metadata is NOT re-read from the
.so: Python already holds names/roles/gamma/n_aux/params (the HyperbolicModel carries them); CompiledModel just exposes them for dispatch (add_equation) and diagnostics. cf. DSL_MODEL_DESIGN.md section 3.- check_runtime(n=16, state=None, raise_on_error=True, rtol=1e-08, atol=1e-10)[source]¶
RUNTIME re-verification of a CompiledModel ALONE (audit balance, GENERICITY pt 9): without the original dsl.Model, the FORMULAS are no longer re-verifiable (symbolic check_model), but the .so itself is – we install it in an EPHEMERAL System (n x n periodic, neutral Poisson, minmod+rusanov) and delegate to System.check_model (finite state, residual -div F + S finite, positivity by roles, round-trip of THE MODEL conversions).
@p state: dict {conservative variable name: ndarray (n, n)} to control the tested state. None -> SMOKE state by ROLES (Density = 1 + gaussian bump, Momentum* = 0, Energy = 2.5, other components = 0.5) – enough to exercise flux/source/conversions; provide state= for a precise physical regime. @return the dict from System.check_model.
- inspect_amr(layout=None)[source]¶
STATIC AMR report on this compiled MODEL (Spec 5 sec.8.12 / sec.8.4).
A
CompiledModelis a per-block physics.so;target='amr_system'means it is loadable on the native AMR hierarchy (add_equation), but the model carries NO layout descriptor (the levels / ratio / regrid policy belong to theAmrSystemit is installed in, not to the model). So this delegates to the top-levelpops.inspect_amr()on an EXPLICITlayoutargument and returns the native AMR envelope report forlayout=None; for a target=’system’ model the report still describes the native AMR envelope (a model is AMR-installable only with target=’amr_system’). It never fabricates a hierarchy. @p layout an optional AMR / Uniform layout descriptor (default: the native envelope).
- property runtime_param_names¶
this is the ORDER of the indices on the C++ side (RuntimeParams) AND the order expected by System.set_block_params(name, values) (P7-b). Empty if the model has only const params.
- Type:
Names of the model’s RUNTIME parameters (kind=’runtime’), SORTED
Useful environment variables are documented in environment variables.
Profiling¶
- class pops.Profile(level='basic')[source]¶
Bases:
objectA typed profiling level (Spec 5 sec.12.5). Inert: it carries no timers.
Use the named constructors rather than a string flag:
with sim.profile(pops.Profile.Basic()) as prof: sim.run(0.1) print(prof.summary())
Basicsurfaces the coarse phase timings + the kernel/step counters;Advancedalso asks for the per-program-node timings and the scheduler/memory counters (which only populate under a compiled step on a Kokkos build – declared unavailable, never faked, otherwise).- classmethod Advanced()[source]¶
Per-program-node timings + scheduler / memory counters (Kokkos-gated; honest about gaps).
- property advanced¶
True for the Advanced level (asks for the per-node / scheduler / memory views).
- classmethod from_env(default=None)[source]¶
Resolve the level from
POPS_PROFILE(sim.profile() with no argument).Unset /
0/off-> @p default (a Basic() when @p default is None);advanced/full-> Advanced(); anything else -> Basic(). Returns None when the env asks for OFF and no @p default is given, so the caller can leave profiling disabled.
- class pops.PerformanceSummary(report, profile=None)[source]¶
Bases:
objectA printable, typed wrapper around the native profile report (Spec 5 criteria 41-43).
Built from the string
System.profile_report()returns (and theProfilelevel the run requested). It exposes the report as a structured dict (to_dict()/to_json()) and typed views:by_program_node(),by_native_brick(),by_solver(),by_elliptic(),by_amr_mpi(),by_memory(). Views read the parsed native tables; a view the build does not surface returns an_Unavailablesentinel (bool(view) is False) rather than a faked zero.- by_amr_mpi()[source]¶
AMR / MPI phase timings + counters: the distributed-runtime dimension (criterion 43).
Spec 5 sec.12.5 requires time attributable to AMR / MPI alongside the program-node, native-brick, solver, and memory views. The distributed AMR runtime spends its non-numeric time in named phases the C++ profiler can scope and count:
regrid– rebuilding the patch hierarchy (timing scope + a per-run count);fill_boundary/halo_exchange– the cross-rank ghost-cell halo exchange;reflux– the coarse-fine flux correction at refinement boundaries;average_down– restricting fine-level data onto the coarse level;mpi_reductions/mpi_messages– MPI collective / point-to-point counts.
A scope whose name contains one of the timing tokens is surfaced as a timing entry (
{count, total_s, mean_s, min_s, max_s}); a counter whose name contains one of the counter tokens is surfaced as an int. Returns{name: int | timing-dict}with only the phases the run actually produced.On the common host / serial / non-AMR build NONE of these scopes or counters exist – no C++ path emits them yet (the native regrid / halo / reduction timers are a documented follow-up in
include/pops/runtime/programand the AMR runtime). When the report carries none of them this view declares itself_Unavailablehonestly rather than fabricating a zero, exactly likeby_native_brick()/by_memory()on a host build. It surfaces real numbers as soon as a distributed AMR run under profiling emits the scopes.
- by_elliptic()[source]¶
Elliptic-solver counters: the most actionable view given the elliptic-solve dominance.
The elliptic field solve is 96-99.9% of step cost (Spec 5 sec.13.11.1), yet
by_solveronly exposes the opaquefield_solvephase. This view breaks it down with the native counters the System reads back at the field_solve seam (ADC-479 criteria 42/43):mg_cycles– total geometric-multigrid V-cycles over the run;krylov_iters– total Krylov iterations (0 on the default Poisson path: it uses multigrid / a direct FFT, never a Krylov elliptic solver);mg_levels– multigrid hierarchy depth (a structural constant, reported as the peak);elliptic_bottom– coarsest-grid (bottom) solve self-time, as a timing entry{count, total_s, mean_s, min_s, max_s}.
Returns
{name: int | timing-dict}with only the counters / scope the run actually produced. A direct FFT solver yields zeros (no cycles / levels / bottom solve), and a build whose_popspredates these counters (nomg_cyclesetc.) declares the view unavailable rather than faking.
- by_memory()[source]¶
Scratch-memory counters: allocation count + the largest single scratch buffer (bytes).
Reads
scratch_allocs/scratch_peak_bytes(program_context.hppcount_scratch). These move only under a compiled step on a Kokkos build; on a native host step neither counter is created, so this view declares itself unavailable rather than faking a 0.
- by_native_brick()[source]¶
Per-native-brick timings.
The native runtime times Program nodes and coarse phases, not individual bricks: there is no per-brick scope to read. Declared unavailable rather than faked (the per-brick granularity is a documented follow-up wired through the compiled ProgramContext).
- by_program_node()[source]¶
Per-program-node timings (the
node:<name>scopes the compiled step emits).Keys are the bare node names (
rhs2,solve_fields1, …). Empty on a native step (no compiled Program); populated under a compiled.sostep.
- by_solver()[source]¶
Solver-attributable timings: the elliptic field-solve phase + any solve_fields node.
Reads the coarse
field_solvephase and thenode:solve_fields*program nodes. Empty when no field solve ran under profiling.
- property raw_report¶
The exact string the native profiler returned (the source of truth).
- to_dict()[source]¶
The full structured report: level + scopes + counters + total, plus the typed views.
by_native_brick/by_amr_mpi/by_memoryserialise their availability honestly (an unavailable view records{"available": False, "reason": ...}).
Profiling is off by default and should be enabled explicitly with
sim.profile(...) or POPS_PROFILE.
Experimental namespace¶
pops.experimental is not production API. Pages may mention it for debugging,
but tutorials must not use it as a solver route.