Typed bricks (pops.lib)¶
pops.lib is a catalog of typed brick descriptors and IR macros. It computes nothing in
Python: a descriptor names a brick and carries its metadata (a native C++ id, a runtime
scheme string, requirements, capabilities); the codegen and runtime consume it.
The four brick kinds¶
Kind |
Meaning |
Example |
|---|---|---|
NativeBrick |
a C++ type already in |
|
GeneratedBrick |
a DSL-authored brick compiled to C++ |
a custom source / local matrix |
MacroBrick |
a Python function that builds Program IR |
|
ExternalCppBrick |
a user C++ brick registered by id |
|
A descriptor carries brick_type (native / generated / macro / external_cpp),
native_id, scheme, requirements, capabilities, options, and available. A
catalogued brick with no native symbol yet is emitted with available=False and an empty
native_id – never a fabricated id.
Namespaces¶
The brick catalogs live in the Spec 5 central packages (criterion 7: pops.lib is presets only).
pops.numerics carries riemann (Rusanov/HLL/HLLC/Roe/User), reconstruction
(FirstOrder/MUSCL/WENO5/WENO5Z/User), limiters (Minmod/VanLeer; MC/Superbee are catalogued but
not yet wired), spatial (FiniteVolumeResidual/FluxDivergence/SourceAssembly/FiniteVolume), and
projections (positivity native). pops.fields.catalog carries the elliptic-field bricks
(GeometricMG native; Poisson/Helmholtz/EllipticSolve catalogued). pops.solvers carries solvers
(CG/BiCGStab/GMRES/Richardson native free functions; Newton/FixedPoint catalogued; Schur) and
preconditioners (GeometricMG native; identity/jacobi/block_jacobi catalogued). pops.lib.time
keeps the time-scheme macros (forwarding to pops.time std).
import pops
d = pops.numerics.riemann.HLLC()
d.brick_type # 'native'
d.native_id # 'pops::HLLCFlux'
d.scheme # 'hllc'
pops.solvers.GMRES().native_id # 'pops::gmres_solve' (a native free function)
pops.fields.catalog.Poisson().available # False (no standalone native type yet; solved via GeometricMG)
The native ids resolve to real symbols in include/pops (verified by the test suite); see
Native numerics: Riemann solvers and reconstruction for the FV solver/reconstruction bricks.
Macros (time schemes)¶
pops.lib.time.* are MacroBricks: they build a Program from operator names and add no runtime
stepper of their own. They forward to the pops.time std library, so a library scheme and a
hand-written Time programs produce the same IR.
T = pops.lib.time.predictor_corrector(P, "plasma",
fields_operator="fields_from_state",
explicit_rate_operator="explicit_rate",
implicit_operator="implicit_operator")
External C++ bricks¶
A user ships a brick in a standalone .so that registers a manifest entry at static-init time
with the C++ macro POPS_REGISTER_BRICK(id, category, requirements)
(include/pops/runtime/program/external_brick.hpp) and exports a C function
const char* pops_brick_manifest() returning JSON over the process-global BrickRegistry.
pops.descriptors.load_cpp_library(path) dlopens the .so, reads that manifest, and registers the ids
in an in-process catalog; pops.numerics.riemann.User(id) (and the category-agnostic
pops.descriptors.external(id)) then surface an external_cpp descriptor carrying the manifest’s
requirements/capabilities. An id that was never loaded raises a clear LookupError
(“external brick ‘x’ not loaded; call pops.descriptors.load_cpp_library(…)”) rather than fabricate a
descriptor.
n = pops.descriptors.load_cpp_library("./my_bricks.so") # registers every brick the .so manifests
d = pops.numerics.riemann.User("my_hllc")
d.brick_type # 'external_cpp'
d.native_id # 'my_hllc'
d.requirements # {'capabilities': ['pressure', 'wave_speeds']}
Static dispatch of an external Riemann brick¶
A riemann brick is not only catalogued, it is DISPATCHED into the finite-volume machinery in the
same type system as a native flux (pops::HLLCFlux), statically and with no per-cell string lookup.
The brick .so does the static instantiation itself: it defines a NumericalFlux functor and uses
POPS_DEFINE_EXTERNAL_RIEMANN_BRICK(id, Flux, Model, requirements)
(include/pops/runtime/program/external_riemann_brick.hpp), which registers the manifest and emits a
flat-array pops_brick_residual entry point that calls build_block<Limiter, UserFlux> – the user
flux is a compile-time template argument, fully inlined, exactly the leaf the native string ladder
routes to. The host ExternalBrickHandle dlopens the .so, reads + registers its manifest, and
resolves that entry point ONCE; the only runtime string left is the limiter (a 4-way if resolved
once at install, never per cell).
// my_riemann.cpp -- compiled to my_riemann.so against the pops headers
struct MyRiemann {
template <class Model>
POPS_HD typename Model::State operator()(const Model& m, const typename Model::State& UL,
const pops::Aux& AL, const typename Model::State& UR,
const pops::Aux& AR, int dir) const { /* ... */ }
};
namespace user { using Model = pops::CompositeModel<pops::Euler, pops::NoSource, pops::BackgroundDensity>; }
POPS_DEFINE_EXTERNAL_RIEMANN_BRICK("my_riemann", MyRiemann, user::Model, "pressure,wave_speeds");
POPS_DEFINE_BRICK_MANIFEST(); // exports pops_brick_manifest() once per .so
Status¶
The descriptor catalog, the native ids, the time macros and external C++ bricks are in place:
pops.descriptors.load_cpp_library + the C++ BrickRegistry / POPS_REGISTER_BRICK / POPS_DEFINE_BRICK_MANIFEST
register and surface a brick, and POPS_DEFINE_EXTERNAL_RIEMANN_BRICK + ExternalBrickHandle
statically dispatch an external Riemann brick’s flux (CPU/OpenMP Kokkos validated on macOS; the
GPU/GH200 dispatch is a ROMEO follow-up). pops.compile_library(name, objects, emit=True) compiles a
reusable brick library to a real .so (Kokkos toolchain) that exports its ABI key + brick metadata;
pops.read_library_manifest reads it back (with a hard ABI guard) and
pops.compile_problem(..., libraries=[...]) consumes it. The specialization modes (native / library
/ specialized / auto) are follow-ups.