Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning

### Changed

- Canonical authoring now keeps one explicit projection/construction route: use
`pops.physics.Model.lower()` for advanced Module inspection and
`MomentModel.build()` for recorded moment specifications. The duplicate facade aliases were
removed instead of deprecated.
- Private uniform and AMR Python runtime wrappers no longer expose `add_block`; native-brick and
compiled-package installation share the existing type-dispatched `add_equation` seam used below
`pops.bind`, while public authoring remains `Case.block(...)`.
- Strict AMR checkpoint payload v7 now persists the accepted shared-interface flux audit together
with Program clocks, histories, tagging state, conservative ledger and synchronization report.
Restart validates every fragment's topology epoch, level pair, exact clock window, resolved
Expand Down
2 changes: 1 addition & 1 deletion docs/CODE_DOCUMENTATION_CONVENTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ Prefer:
|---|---|---|
| Folder/file | Architectural role, boundaries | `runtime/System` orchestrates, does not contain the physics formulas. |
| Class | Usage, contract, invariants, constraints | `AmrSystem` orchestrates a common AMR hierarchy. |
| Public method | User/API contract, `@param`, `@return`, `@throws` if useful | `add_block` validates resolved block metadata. |
| Public method | User/API contract, `@param`, `@return`, `@throws` if useful | `Case.block` validates resolved block metadata. |
| Complex block | Why the order of operations matters | Poisson then aux then RHS. |
| Line | Rare, only bug/trick | `local_size()==0` MPI guard. |

Expand Down
4 changes: 3 additions & 1 deletion docs/design/pybind-binding-audit.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# Pybind and native component boundary

This note defines the final binding boundary. Python authoring never selects a native algorithm with
a string and never calls `System.add_block`. A typed descriptor contributes a versioned
a string, and the private Python runtime wrappers do not expose `System.add_block` or
`AmrSystem.add_block`; `pops.bind` uses their single type-dispatched `add_equation` seam. A typed
descriptor contributes a versioned
`ComponentManifest`; resolution authenticates its small interfaces and produces an immutable route
identity. Pybind materializes the already-resolved plan and does not reinterpret scientific intent.

Expand Down
3 changes: 2 additions & 1 deletion python/pops/codegen/program_codegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,8 @@ def emit_cpp_program(
the blocks are first declared via ``T.state``). The .so also exports its block NAMES in that
order (``pops_program_block_count`` / ``pops_program_block_name``); ``System::install_program``
binds them to the instantiated System blocks BY NAME (Spec 3 criterion 23, ADC-457), so the
System blocks (``sim.add_equation`` / ``sim.add_block``) may be added in ANY order -- a Program
System blocks (through the private ``sim.add_equation`` install seam) may be added in ANY
order -- a Program
block whose name has no instantiated System block fails loud (``Program requires block instance
'<name>', but simulation did not instantiate it``). A block declared but never committed is a
READ-ONLY block (allowed; e.g. a passive field whose charge couples the others through the shared
Expand Down
4 changes: 0 additions & 4 deletions python/pops/moments/hierarchy.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,10 +282,6 @@ def build(self, name: Any = "moments", *, frame: Any = None) -> Any:
self._apply_poisson(m, registered)
return m

def check(self, name: Any = "moments") -> Any:
"""Alias of :meth:`build` (build + the engine's own validation on construction)."""
return self.build(name)

# --- internals ----------------------------------------------------------
def _apply_poisson(self, m: Any, registered: dict[str, Any]) -> None:
"""Author ``-laplacian(phi) == eps * M00`` and its gradient outputs."""
Expand Down
3 changes: 2 additions & 1 deletion python/pops/numerics/reconstruction/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ def _weno5(name: str, epsilon: Any = None) -> Any:

``None`` (the default) keeps the native ``kWenoEpsilon`` literal -- the descriptor options are
unchanged (omit-when-default) and the emitted stencil is bit-identical. A finite positive value
is carried in the descriptor options and threaded to the native ``Weno5::eps`` by ``add_block``.
is carried in the descriptor options and threaded to the native ``Weno5::eps`` by the
private ``add_equation`` installation seam.
On AMR, descriptor availability is conditional on the resolved coarse/fine authority: it must
certify order 5 and ghost depth 3. The builtin capability family selects its conservative
order-5 route from that resolved requirement; an insufficient external provider is refused
Expand Down
7 changes: 1 addition & 6 deletions python/pops/physics/board.py
Original file line number Diff line number Diff line change
Expand Up @@ -1162,14 +1162,9 @@ def lower(self) -> Any:
compiled = pops.compile(resolved)

``pops.compile`` captures the operator-first Module and validates ONCE internally; ``lower``
(and its ``to_module`` alias) stay ADVANCED / inspection-only. Identical to :pyattr:`module`."""
stays ADVANCED / inspection-only and is identical to :pyattr:`module`."""
return self.module

# Spec 5 sec.11 alias: physics.Model.to_module() == physics.Model.lower(). ADVANCED / inspection only
# (ADC-557): the standard case.block(model=m) -> pops.compile flow captures the Module itself;
# neither is REQUIRED (pops.compile does the lowering once, internally).
to_module = lower

# --- introspection ---

# --- internals ---
Expand Down
104 changes: 7 additions & 97 deletions python/pops/runtime/_amr_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
``_amr_system_equation`` (add_equation + named-aux), ``_amr_system_io`` (private accepted-state
codec and restore transaction), ``_amr_system_program`` (compiled time-Program install / params / transaction)
and ``_amr_system_install`` (the ``pops.bind`` install seam + field-solver / aux helpers)
mixins; this module composes them and keeps the constructor plus native block/coupling glue.
mixins; this module composes them and keeps the constructor plus coupling glue.
"""

from __future__ import annotations
Expand All @@ -15,19 +15,12 @@
from pops.runtime import _threading
from pops.runtime._lifecycle import (
FROZEN_STRUCTURAL as _FROZEN_STRUCTURAL,
RETIRED_NATIVE_PASSTHROUGH as _RETIRED_NATIVE_PASSTHROUGH,
freeze_error as _freeze_error,
guard_assembling as _guard_assembling,
_LifecycleMixin,
)
from pops.runtime._numeric import native_real
from pops.runtime._engine_descriptors import Spatial, Explicit
from pops.runtime.defaults import (
NEWTON_DEFAULT_ABS_TOL,
NEWTON_DEFAULT_DAMPING,
NEWTON_DEFAULT_FD_EPS,
NEWTON_DEFAULT_MAX_ITERS,
NEWTON_DEFAULT_REL_TOL,
)
from pops.runtime._amr_system_equation import _AmrSystemEquation
from pops.runtime._amr_system_install import _AmrSystemInstall
from pops.runtime._amr_system_io import _AmrSystemIO
Expand Down Expand Up @@ -294,94 +287,6 @@ def coarse_total_boxes(self) -> Any:
"""
return self._s.coarse_total_boxes()

def add_block(self, name: Any, model: Any, spatial: Any = None, time: Any = None) -> Any:
"""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`` assembly
resolved with ``pops.resolve(case, layout=...)``, compiled with ``pops.compile(plan)`` and
wired by ``pops.bind`` (which calls this internally); ``add_block`` stays private.

Refined counterpart of System.add_block. Every block count uses the same AmrRuntime engine;
subsequent blocks are co-located on the shared hierarchy and contribute to the summed
system-Poisson 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 private ``ModelSpec`` engine value composed from native bricks.
@param spatial private engine adapter lowered from ``pops.numerics.FiniteVolume(...)``
(default minmod + rusanov + conservative). The native seam accepts limiter tokens
none / minmod / vanleer / weno5, Riemann fluxes rusanov / hll / hllc / roe, and
conservative / primitive variables. This low-level WENO5 stencil route is not an AMR
availability guarantee: a resolved Case also requires an owner-qualified coarse/fine
provider certified for order 5 and ghost depth 3. The native catalogue contains that
provider and resolves it from the reconstruction requirements; no lower-order
coarse/fine fallback is permitted.
@param time private engine policy. Public authoring uses an explicit ``pops.Program`` or a
``pops.lib.time`` factory. The installed typed Program is the sole time authority.
Until the AMR target provides a typed local implicit primitive, non-empty partial masks,
non-default Newton controls, and Newton diagnostics fail closed. The spatial runtime
never stores them or manufactures an implicit step/report.
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).
"""
_guard_assembling(self, "add_block") # frozen once pops.bind completes (ADC-592)
spatial = spatial if spatial is not None else Spatial()
time = time if time is not None else Explicit()
# positivity_floor (ADC-259) IS now wired on the AMR transport (Density-role face states +
# C/F fine ghost means). Threaded to AmrSystem::add_block below; the compiled .so path carries
# it too (ADC-322, regenerated loader). The C++ side rejects it on a model without a Density role.
spatial_options: dict[str, bool | float] = {
"wave_speed_cache": bool(getattr(spatial, "wave_speed_cache", False)),
}
if getattr(spatial, "weno_epsilon", None) is not None:
spatial_options["weno_epsilon"] = native_real(
spatial.weno_epsilon, where="AmrSystem.add_block.weno_epsilon"
)
# Forward the complete authoring request to the native contract. Cadence remains meaningful
# to Program/CFL normalization; unsupported partial masks and non-default Newton requests
# fail closed there instead of becoming inert spatial-runtime state.
self._s.add_block(
name,
model,
spatial.limiter,
spatial.flux,
spatial.recon,
time.kind,
getattr(time, "substeps", 1),
getattr(time, "stride", 1),
getattr(time, "implicit_vars", []),
getattr(time, "implicit_roles", []),
getattr(time, "newton_max_iters", NEWTON_DEFAULT_MAX_ITERS),
native_real(
getattr(time, "newton_rel_tol", NEWTON_DEFAULT_REL_TOL),
where="AmrSystem.add_block.newton_rel_tol",
),
native_real(
getattr(time, "newton_abs_tol", NEWTON_DEFAULT_ABS_TOL),
where="AmrSystem.add_block.newton_abs_tol",
),
native_real(
getattr(time, "newton_fd_eps", NEWTON_DEFAULT_FD_EPS),
where="AmrSystem.add_block.newton_fd_eps",
),
native_real(
getattr(time, "newton_damping", NEWTON_DEFAULT_DAMPING),
where="AmrSystem.add_block.newton_damping",
),
getattr(time, "newton_diagnostics", False),
native_real(
getattr(spatial, "positivity_floor", 0.0),
where="AmrSystem.add_block.positivity_floor",
),
**spatial_options,
)

def field(self, name: Any) -> Any:
"""Return the solved potential of a NAMED elliptic field as a ``(ny, nx)`` array.

Expand Down Expand Up @@ -496,6 +401,11 @@ def program_report(self) -> Any:
return build_program_report(self)

def __getattr__(self, attr: Any) -> Any:
if attr in _RETIRED_NATIVE_PASSTHROUGH:
raise AttributeError(
"AmrSystem.%s is not an authoring route; declare the block with "
"pops.Case.block(...)" % attr
)
# RUNTIME FREEZE (ADC-592): once bound, refuse a native STRUCTURAL setter reached through the
# passthrough (install_program / ...) with the bind-vocabulary
# RuntimeError, so the bypass is closed even under a prebuilt .so whose C++ setters are not yet
Expand Down
1 change: 0 additions & 1 deletion python/pops/runtime/_amr_system_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ def set_history_persistence(self, *args: Any, **kwargs: Any) -> Any: ...
def last_restart_regrid_receipt(self) -> Any: ...

def add_equation(self, *args: Any, **kwargs: Any) -> Any: ...
def add_block(self, *args: Any, **kwargs: Any) -> Any: ...
def set_poisson(self, *args: Any, **kwargs: Any) -> Any: ...
def _set_poisson_native(self, *args: Any, **kwargs: Any) -> Any: ...
def set_density(self, *args: Any, **kwargs: Any) -> Any: ...
Expand Down
11 changes: 6 additions & 5 deletions python/pops/runtime/_amr_system_equation.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,11 @@ def add_equation(

Dispatch:

- a private ``ModelSpec`` -> add_block (native bricks composed on the hierarchy);
- a private ``ModelSpec`` -> the native ``AmrSystem::add_block`` ABI (bricks composed on
the hierarchy);
- a CompiledModel(backend='production', target='amr_system') installs a package whose loader
inlines add_compiled_model(AmrSystem&), so the block runs
the SAME AMR hierarchy as add_block (conservative reflux, regrid), ZERO-COPY.
the same AMR hierarchy as the native-brick ABI (conservative reflux, regrid), ZERO-COPY.

The ``time`` value carried by a block is immutable Program-authoring metadata, not an
executable method in the AMR spatial runtime. The compiled ``pops.Program`` installed after
Expand All @@ -110,15 +111,15 @@ def add_equation(
Newton controls, or diagnostics fails closed until a typed implicit Program primitive exists.
It never reaches a private backward-Euler/Newton engine.
``recon="primitive"`` and fluxes ``roe`` / ``hllc`` use the same compiled spatial dispatch as
``add_block``. The low-level dispatch also contains the WENO5-Z stencil and its three-cell
the native-brick branch. The low-level dispatch also contains the WENO5-Z stencil and its three-cell
halo, but the resolved Case route accepts it only when the owner-qualified coarse/fine
provider certifies order 5 and ghost depth 3. The native catalogue resolves that provider
from the reconstruction requirements and never lowers the coarse/fine interface order
silently.

MULTIRATE CADENCE (stride) and PARTIAL IMEX MASK (implicit_vars / implicit_roles):

- private ``ModelSpec`` path: FORWARDED to ``AmrSystem::add_block``. Cadence remains part of
- private ``ModelSpec`` path: forwarded to ``AmrSystem::add_block``. Cadence remains part of
Program/CFL normalization; non-empty masks and non-default Newton requests fail closed
until the AMR target exposes their typed Program primitive;
- CompiledModel production path (.so): explicitly REJECTED (ValueError). The flat ABI of the
Expand Down Expand Up @@ -150,7 +151,7 @@ def add_equation(
where="AmrSystem.add_equation.substeps",
)

# --- ModelSpec: native bricks composed -> add_block (existing path) ---
# --- ModelSpec: native bricks composed through the sole Python dispatch seam ---
# Forward the complete authoring request to the native contract. Unsupported masks and
# Newton controls are rejected there rather than retained by the spatial runtime.
if isinstance(model, ModelSpec):
Expand Down
3 changes: 2 additions & 1 deletion python/pops/runtime/_bricks_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,8 @@ def Model(state: Any, transport: Any, source: Any, elliptic: Any) -> Any:
Validates the state <-> transport consistency (Scalar with ExB; compressible FluidState with
CompressibleFlux; isothermal with IsothermalFlux) and carries the parameters into the spec.

The returned ``ModelSpec`` is the BOUNDED LEGACY BRIDGE for the native ``add_block`` path (a
The returned ``ModelSpec`` is the bounded private bridge for the native-ABI branch of
``add_equation`` (a
flat C++ POD of brick tags + parameters); it is NOT the target representation. The target
representation of a model is the operator-first ``pops.model.Module`` (compiled to a Problem)
and its self-describing ``ModuleManifest`` (ADC-585). The POD remains an explicitly private
Expand Down
4 changes: 2 additions & 2 deletions python/pops/runtime/_bricks_scheme.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,8 @@ class Spatial:
``pops.numerics.reconstruction.FirstOrder()`` -> none, ``.limiters.Minmod()`` /
``.VanLeer()``, ``.WENO5()`` / ``.WENO5Z()`` -> weno5, ``.MUSCL(limiter=...)`` -> its limiter.
weno5 = WENO5-Z, order 5 in smooth regions, 5-point stencil (3 ghosts), oscillation-free
capture near a front; only the native ``add_block`` path exposes it (the compiled .so paths
allocate 2 ghosts -> explicit rejection).
capture near a front; only the private native-``ModelSpec`` branch of ``add_equation``
exposes it (the compiled .so paths allocate 2 ghosts -> explicit rejection).
- ``flux``: a ``pops.numerics.riemann`` descriptor lowering to "rusanov" | "hll" | "hllc" |
"roe".
Rusanov() = minimal generic (requires only max_wave_speed, any model).
Expand Down
Loading
Loading