Public API contract¶
This page is the contract for the documented Python API. It is deliberately smaller than the implementation surface: internal seams may exist because the runtime needs them, but user documentation must not present them as alternate front doors.
Rule¶
Python describes a compiled problem. C++/Kokkos/MPI executes it.
The public flow is:
case = pops.Case(layout=layout, name="run_name")
compiled = pops.compile(case, backend=Production())
sim = pops.bind(compiled, state=initial_state, params=params)
sim.run(t_final=1.0, cfl=0.4)
No Python callback is allowed in a cell loop, stage loop, Krylov loop, field solve, halo exchange, reduction, AMR tag pass, regrid, reflux, output writer, or checkpoint writer. If an operation is public, it must lower to a compiled C++ route.
Public layers¶
Package |
Role |
|---|---|
|
Physics authoring facade. It builds model/operator IR and lowers to |
|
Operator-first model IR: states, fields, operators, handles, signatures, capabilities. |
|
Temporal language: |
|
Ready-made time-program macros such as |
|
Mesh descriptors: |
|
Elliptic field problems: |
|
PDE discretisation descriptors: Riemann fluxes, reconstruction, limiters, RHS terms, projections. |
|
Algebraic problem descriptions: linear problems, matrix-free operators, norms, reductions. |
|
Compiled solver descriptors supplied by the core: elliptic, Krylov, nonlinear, Schur, preconditioners. |
|
C++ lowering, compile backends, optimization descriptors, inspection and generated-source access. |
|
Output/checkpoint policies. |
|
References to compiled external C++ bricks with manifests. |
|
Debug and experimental helpers. Not stable and not production documentation. |
pops.lib is for provided presets and ready-made assemblies. It is not a dump
for core primitives. In particular, solver descriptors live in pops.solvers,
not pops.lib.solvers.
Typed choices¶
Strings name objects chosen by the user. Typed objects choose behavior.
Valid string uses:
Model("euler")
case.block("ions", physics=ions)
RuntimeParam("nu", default=0.1)
PoissonProblem(name="phi", unknown="phi")
T.state("U", block="ions")
Invalid public style:
# Do not document this style.
solver = "geometric_mg"
target = "amr_system"
method = "ssprk3"
kind = "runtime"
Documented style:
from pops.codegen import Production
from pops.mesh.layouts import AMR, Uniform
from pops.solvers.elliptic import GeometricMG
from pops.lib.time import ssprk3
from pops.params import RuntimeParam, Positive
backend = Production()
solver = GeometricMG()
nu = RuntimeParam("nu", default=0.1, domain=Positive())
If a page needs to mention a lowered native token, it must describe it as an internal lowering detail, not as user syntax.
Handles, not string references¶
Strings may introduce names. Later references should use handles returned by the authoring API.
rate = model.rate("explicit_rate", equation=...)
T = Program("step")
U = T.state("U", block="plasma")
fields = T.solve_fields(U.n)
R = T.call(rate, U.n, fields)
T.define(U.next, U.n + T.dt * R)
T.commit("plasma", U.next)
The current implementation still contains internal string tokens in lowering helpers. They are not part of the documented user API.
Time schemes¶
pops.time is the language. It contains Program, time-version handles,
schedules, histories, and analysis/optimization passes.
Ready schemes are in pops.lib.time:
from pops.time import Program
from pops.lib.time import ssprk3
T = Program("advance")
ssprk3(T, "plasma")
For manual programs, use temporal handles:
from pops.time import Program
from pops.numerics.terms import Flux
T = Program("ssprk3_manual")
U = T.state("U", block="plasma")
f0 = T.solve_fields(U.n)
k0 = T.rhs(state=U.n, fields=f0, terms=[Flux()])
T.define(U.stage(1), U.n + T.dt * k0)
f1 = T.solve_fields(U.stage(1))
k1 = T.rhs(state=U.stage(1), fields=f1, terms=[Flux()])
T.define(U.stage(2), 0.75 * U.n + 0.25 * (U.stage(1) + T.dt * k1))
f2 = T.solve_fields(U.stage(2))
k2 = T.rhs(state=U.stage(2), fields=f2, terms=[Flux()])
T.define(U.next, (1.0 / 3.0) * U.n + (2.0 / 3.0) * (U.stage(2) + T.dt * k2))
T.commit("plasma", U.next)
U.n, U.stage(k), U.next, and U.prev are IR handles. They do not hold
runtime arrays.
Mesh layouts¶
The mesh layout is not a backend and not a target string.
from pops.mesh.cartesian import CartesianMesh
from pops.mesh.layouts import Uniform, AMR
from pops.mesh.amr import Refine, RegridEvery, PatchLayout
mesh = CartesianMesh(n=128, L=1.0, periodic=True)
uniform = Uniform(mesh)
amr = AMR(
mesh,
max_levels=2,
ratio=2,
regrid=RegridEvery(8),
patches=PatchLayout(coarse_max_grid=32),
refine=Refine.on("density").above(0.05),
)
pops.compile derives the runtime route from the layout. The user does not pass
a target.
AMR compatibility¶
AMR is part of the public assembly model, not a separate scripting path. A
feature documented for Case must either work on both Uniform and AMR, or
declare a precise mathematical incompatibility in its descriptor. A missing
Python binding, missing codegen branch, or missing runtime plumbing is not a
valid public limitation.
The AMR route uses:
typed
AMRlayout descriptors;typed refinement and patch policies;
per-block compiled model loaders;
runtime C++/Kokkos/MPI data structures;
C++ tag, regrid, halo, reflux, field, output, and diagnostic paths.
No AMR page should instruct users to instantiate AmrSystem directly as the
front door.
Inspection¶
Inspectable objects should print concise summaries and expose structured reports:
print(case)
print(compiled)
print(sim)
compiled.inspect()
compiled.arguments()
compiled.estimate_memory(grid=mesh, platform=...)
compiled.dump_ir()
compiled.dump_cpp()
Profiling is off by default:
with sim.profile(pops.Profile.Advanced()) as prof:
sim.run(t_final=1.0, cfl=0.4)
prof.summary().print()
A report may say a counter is unavailable for the current build. It must not fake a zero.