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
25 changes: 19 additions & 6 deletions docs/design/SPECIFICATION_TECHNIQUE_FINALE_POPS_ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -705,9 +705,13 @@ doivent couvrir exactement la hiérarchie.

Le provider natif livré matérialise le coeur maillage/stockage en 2D et ses kernels de transfert,
correction conservative et sous-cyclage AMR exigent un ratio de transition égal à 2. La correction
coarse/fine reste l'unique ledger de flux détenu par PoPS : aucune interface externe `Reflux`
n'existe, car déléguer ce dépôt créerait une seconde autorité conservative. Une autre dimension ou un autre
ratio est refusé pendant la résolution ou le bind avec les capacités observées. Le coeur de
coarse/fine reste l'unique ledger de flux détenu par PoPS. L'interface native `Reflux` ne peut
déléguer qu'un kernel local et non collectif : PoPS lui fournit les flux coarse/fine déjà intégrés
dans le temps et ramenés sur la même face coarse ; le kernel écrit la correction locale
`side * (fine - coarse) / dx`. PoPS conserve exclusivement la topologie d'interface, le ledger, la
réduction MPI, la transaction et l'application à l'état. Un provider `Reflux` ne devient donc jamais
une seconde autorité conservative. Une autre dimension ou un autre ratio est refusé pendant la
résolution ou le bind avec les capacités observées. Le coeur de
planification ne normalise jamais la demande vers ce sous-ensemble. Défensivement,
`AmrProgramContext` revalide aussi chaque transition à sa construction et refuse un ratio différent
de 2 avant le premier pas : cette limite appartient au provider natif reflux/average-down installé,
Expand Down Expand Up @@ -1405,14 +1409,23 @@ paramètres, interfaces, requirements, capabilities, effets, layouts, clocks, d
restart et points d'entrée.

Le même catalogue génère les IDs et tables C/POD versionnées des interfaces natives (flux numérique,
ghost boundary, closure de champ, tagging, clustering, transfert, solveur de champ, writer et
topologie de champ). Le reflux conservatif reste une autorité interne pilotée par le flux ledger ;
aucune table externe `Reflux` n'est annoncée. Chaque famille possède sa propre version d'interface, indépendante de la version
ghost boundary, closure de champ, tagging, clustering, transfert, kernel local de reflux, solveur de
champ, writer et topologie de champ). Le reflux conservatif complet reste une autorité interne
pilotée par le flux ledger ; la table externe `Reflux` ne couvre que la transformation locale,
non collective, de flux intégrés en correction non appliquée. Chaque famille possède sa propre version d'interface, indépendante de la version
du protocole enveloppe. Le loader authentifie identité sémantique, manifest, digest du catalogue,
taille/header de table et opérations requises avant de conserver le handle de bibliothèque. Les tables
sont résolues une fois à l'installation ; aucun `dlsym`, nom de classe ou dispatch Python n'entre dans
une boucle de cellules.

Le contrat `Reflux` v1 est volontairement livré avant son branchement dans
`PreparedAmrProgramRefluxTransition` : catalogue, manifest, loader et consumer typé peuvent qualifier
un conformer, mais le runtime AMR continue d'utiliser son kernel interne tant qu'un adaptateur préparé
ne peut pas fournir les vues locales sans dupliquer le ledger ni transférer l'autorité collective. Une
configuration AMR ne prétend donc pas encore avoir sélectionné un provider `Reflux` externe. Cette
première qualification est limitée à la cible 2D, `float64`, CPU déjà admise par le loader de
composants ; elle ne constitue pas une promesse GPU.

Les champs sémantiques inconnus, capacités sans preuve, collisions d'identité et entry points manquants
sont refusés. Un vieux manifest n'est pas « réparé » silencieusement.

Expand Down
98 changes: 97 additions & 1 deletion include/pops/runtime/config/generated_component_abi.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ extern "C" {
#endif

#define POPS_COMPONENT_API_SYMBOL_V1 "pops_component_interface_v1"
#define POPS_COMPONENT_CATALOG_SHA256_V1 "84c68fcee96663f71e7e7fa7589ec1ddee0d1037e741a678e4afd83c9749620a"
#define POPS_COMPONENT_CATALOG_SHA256_V1 "5c991781aadafd3165dccb4642086c8b20fbd4a83ee8e462f1e29078ecb0d1c4"
#define POPS_COMPONENT_PROTOCOL_ABI_V1 1u
#define POPS_COMPONENT_COMMON_ABI_V1 1u

Expand All @@ -26,6 +26,7 @@ typedef enum PopsNativeInterfaceIdV1 {
POPS_NATIVE_INTERFACE_TAGGER_V2 = 3,
POPS_NATIVE_INTERFACE_CLUSTERING_V1 = 4,
POPS_NATIVE_INTERFACE_TRANSFER_V1 = 5,
POPS_NATIVE_INTERFACE_REFLUX_V1 = 6,
POPS_NATIVE_INTERFACE_FIELD_SOLVER_V2 = 7,
POPS_NATIVE_INTERFACE_WRITER_V1 = 8,
POPS_NATIVE_INTERFACE_FIELD_TOPOLOGY_V2 = 9,
Expand Down Expand Up @@ -446,6 +447,41 @@ typedef struct PopsTransferApiV1 {
PopsTransferApplyFnV1 apply;
} PopsTransferApiV1;

// Reflux providers are patch-local numerical kernels only. PoPS retains sole ownership of the
// time-integrated flux ledger, interface topology, MPI reduction, transaction and state update.
// Each face contains coarse/fine fluxes already integrated in time and averaged onto the same
// coarse face. The provider writes, but never applies, side*(fine-coarse)/dx into `correction`.
typedef enum PopsRefluxFaceSideV1 {
POPS_REFLUX_FACE_LOW_V1 = -1,
POPS_REFLUX_FACE_HIGH_V1 = 1
} PopsRefluxFaceSideV1;
typedef struct PopsRefluxFaceV1 {
uint32_t struct_size;
const char* interface_identity;
int32_t axis;
PopsRefluxFaceSideV1 side;
double inverse_coarse_cell_spacing;
PopsConstFieldViewV1 coarse_integrated_flux;
PopsConstFieldViewV1 fine_integrated_flux;
PopsFieldViewV1 correction;
} PopsRefluxFaceV1;
typedef struct PopsRefluxRequestV1 {
uint32_t struct_size;
const char* transition_identity;
int32_t parent_level;
int32_t child_level;
size_t face_count;
const PopsRefluxFaceV1* faces;
PopsLogicalTimeV1 logical_time;
PopsExecutionContextV1 execution;
} PopsRefluxRequestV1;
typedef int32_t (*PopsRefluxApplyInterfaceBatchFnV1)(
void*, const PopsRefluxRequestV1*, PopsComponentStatusV1*);
typedef struct PopsRefluxApiV1 {
PopsComponentTableHeaderV1 header;
PopsRefluxApplyInterfaceBatchFnV1 apply_interface_batch;
} PopsRefluxApiV1;

typedef struct PopsFieldPatchMetadataV1 {
uint32_t struct_size;
size_t global_patch_index;
Expand Down Expand Up @@ -724,6 +760,7 @@ inline constexpr size_t generated_native_interface_table_size(
case POPS_NATIVE_INTERFACE_TAGGER_V2: return sizeof(PopsTaggerApiV2);
case POPS_NATIVE_INTERFACE_CLUSTERING_V1: return sizeof(PopsClusteringApiV1);
case POPS_NATIVE_INTERFACE_TRANSFER_V1: return sizeof(PopsTransferApiV1);
case POPS_NATIVE_INTERFACE_REFLUX_V1: return sizeof(PopsRefluxApiV1);
case POPS_NATIVE_INTERFACE_FIELD_SOLVER_V2: return sizeof(PopsFieldSolverApiV2);
case POPS_NATIVE_INTERFACE_WRITER_V1: return sizeof(PopsWriterApiV1);
case POPS_NATIVE_INTERFACE_FIELD_TOPOLOGY_V2: return sizeof(PopsFieldTopologyApiV2);
Expand All @@ -739,12 +776,71 @@ inline constexpr const char* generated_native_interface_table_name(
case POPS_NATIVE_INTERFACE_TAGGER_V2: return "PopsTaggerApiV2";
case POPS_NATIVE_INTERFACE_CLUSTERING_V1: return "PopsClusteringApiV1";
case POPS_NATIVE_INTERFACE_TRANSFER_V1: return "PopsTransferApiV1";
case POPS_NATIVE_INTERFACE_REFLUX_V1: return "PopsRefluxApiV1";
case POPS_NATIVE_INTERFACE_FIELD_SOLVER_V2: return "PopsFieldSolverApiV2";
case POPS_NATIVE_INTERFACE_WRITER_V1: return "PopsWriterApiV1";
case POPS_NATIVE_INTERFACE_FIELD_TOPOLOGY_V2: return "PopsFieldTopologyApiV2";
}
return nullptr;
}
inline bool generated_native_interface_table_is_complete(
PopsNativeInterfaceIdV1 id, const void* table, size_t table_size) noexcept {
if (table == nullptr)
return false;
switch (id) {
case POPS_NATIVE_INTERFACE_NUMERICAL_FLUX_V1: {
if (table_size < sizeof(PopsNumericalFluxApiV1)) return false;
const auto* api = static_cast<const PopsNumericalFluxApiV1*>(table);
return api->evaluate_faces != nullptr;
}
case POPS_NATIVE_INTERFACE_GHOST_BOUNDARY_V1: {
if (table_size < sizeof(PopsGhostBoundaryApiV1)) return false;
const auto* api = static_cast<const PopsGhostBoundaryApiV1*>(table);
return api->apply_region_batch != nullptr;
}
case POPS_NATIVE_INTERFACE_FIELD_BOUNDARY_CLOSURE_V1: {
if (table_size < sizeof(PopsFieldBoundaryClosureApiV1)) return false;
const auto* api = static_cast<const PopsFieldBoundaryClosureApiV1*>(table);
return api->residual != nullptr && api->jvp != nullptr;
}
case POPS_NATIVE_INTERFACE_TAGGER_V2: {
if (table_size < sizeof(PopsTaggerApiV2)) return false;
const auto* api = static_cast<const PopsTaggerApiV2*>(table);
return api->tag_batch != nullptr;
}
case POPS_NATIVE_INTERFACE_CLUSTERING_V1: {
if (table_size < sizeof(PopsClusteringApiV1)) return false;
const auto* api = static_cast<const PopsClusteringApiV1*>(table);
return api->cluster != nullptr;
}
case POPS_NATIVE_INTERFACE_TRANSFER_V1: {
if (table_size < sizeof(PopsTransferApiV1)) return false;
const auto* api = static_cast<const PopsTransferApiV1*>(table);
return api->apply != nullptr;
}
case POPS_NATIVE_INTERFACE_REFLUX_V1: {
if (table_size < sizeof(PopsRefluxApiV1)) return false;
const auto* api = static_cast<const PopsRefluxApiV1*>(table);
return api->apply_interface_batch != nullptr;
}
case POPS_NATIVE_INTERFACE_FIELD_SOLVER_V2: {
if (table_size < sizeof(PopsFieldSolverApiV2)) return false;
const auto* api = static_cast<const PopsFieldSolverApiV2*>(table);
return api->solve != nullptr;
}
case POPS_NATIVE_INTERFACE_WRITER_V1: {
if (table_size < sizeof(PopsWriterApiV1)) return false;
const auto* api = static_cast<const PopsWriterApiV1*>(table);
return api->verify != nullptr && api->publish != nullptr && api->discard != nullptr && api->rollback != nullptr;
}
case POPS_NATIVE_INTERFACE_FIELD_TOPOLOGY_V2: {
if (table_size < sizeof(PopsFieldTopologyApiV2)) return false;
const auto* api = static_cast<const PopsFieldTopologyApiV2*>(table);
return api->prepare_topology != nullptr;
}
}
return false;
}
} // namespace pops::component
#endif
// clang-format on
6 changes: 3 additions & 3 deletions include/pops/runtime/config/generated_component_catalog.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -301,9 +301,9 @@ inline constexpr int kComponentCatalogSchemaVersion = 1;
inline constexpr int kComponentManifestSchemaVersion = 2;
inline constexpr int kRouteRegistryVersion = 2;
inline constexpr int kCapabilityVocabularyVersion = 2;
inline constexpr const char* kComponentCatalogSha256 = "84c68fcee96663f71e7e7fa7589ec1ddee0d1037e741a678e4afd83c9749620a";
inline constexpr const char* kComponentCatalogSemanticSha256 = "c0e14d4a3dd082612d052b2ce293e17de712c6916e39f430d0ff311ed0f24ef8";
inline constexpr const char* kRouteRegistrySignature = "v2:c0e14d4a3dd082612d052b2ce293e17de712c6916e39f430d0ff311ed0f24ef8";
inline constexpr const char* kComponentCatalogSha256 = "5c991781aadafd3165dccb4642086c8b20fbd4a83ee8e462f1e29078ecb0d1c4";
inline constexpr const char* kComponentCatalogSemanticSha256 = "b29e5d1d811234bd55bfce83ffdf810ed95868392d1ac1da5644f8886620e129";
inline constexpr const char* kRouteRegistrySignature = "v2:b29e5d1d811234bd55bfce83ffdf810ed95868392d1ac1da5644f8886620e129";
inline constexpr const char* kComponentManifestSemanticFields[] = {
"schema_version",
"uri",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Generated from component catalog 84c68fcee96663f71e7e7fa7589ec1ddee0d1037e741a678e4afd83c9749620a; DO NOT EDIT.
// Generated from component catalog 5c991781aadafd3165dccb4642086c8b20fbd4a83ee8e462f1e29078ecb0d1c4; DO NOT EDIT.
// POPS_DEFINE_ROUTE_ACCESSORS must be defined by the including behavior header.
POPS_DEFINE_ROUTE_ACCESSORS(riemann, RiemannRouteId, kRiemannRoutes, kRiemann);
POPS_DEFINE_ROUTE_ACCESSORS(limiter, LimiterRouteId, kLimiterRoutes, kLimiter);
Expand Down
75 changes: 75 additions & 0 deletions include/pops/runtime/dynamic/component_consumers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,81 @@ inline int apply_transfer(const PopsTransferApiV1& api, void* state,
return api.apply(state, &request, &status);
}

template <class Left, class Right>
inline bool same_reflux_face_shape(const Left& left, const Right& right) {
if (left.dimension != right.dimension || left.component_count != right.component_count ||
left.scalar_type != right.scalar_type || left.memory_space != right.memory_space)
return false;
for (std::int32_t axis = 0; axis < 3; ++axis)
if (left.extents[axis] != right.extents[axis] ||
left.ghost_lower[axis] != right.ghost_lower[axis] ||
left.ghost_upper[axis] != right.ghost_upper[axis])
return false;
return true;
}

inline int apply_reflux_interface_batch(const PopsRefluxApiV1& api, void* state,
const PopsRefluxRequestV1& request,
PopsComponentStatusV1& status) {
require_operation(api.apply_interface_batch != nullptr, "apply_interface_batch");
if (request.struct_size < sizeof(PopsRefluxRequestV1) ||
!component_text(request.transition_identity) || request.parent_level < 0 ||
request.child_level != request.parent_level + 1 || request.face_count == 0 ||
request.faces == nullptr || request.logical_time.level != request.parent_level)
throw std::invalid_argument("reflux request is incomplete");
validate_logical_time(request.logical_time);
validate_noncollective_execution_context(request.execution);

std::unordered_set<std::string> identities;
for (std::size_t index = 0; index < request.face_count; ++index) {
const auto& face = request.faces[index];
if (face.struct_size < sizeof(PopsRefluxFaceV1) || !component_text(face.interface_identity) ||
!identities.insert(face.interface_identity).second || face.axis < 0 || face.axis >= 2 ||
(face.side != POPS_REFLUX_FACE_LOW_V1 && face.side != POPS_REFLUX_FACE_HIGH_V1) ||
!std::isfinite(face.inverse_coarse_cell_spacing) || face.inverse_coarse_cell_spacing <= 0.0)
throw std::invalid_argument("reflux face descriptor is incomplete");

validate_execution_field(request.execution, face.coarse_integrated_flux,
"reflux coarse integrated flux");
validate_execution_field(request.execution, face.fine_integrated_flux,
"reflux fine integrated flux");
validate_execution_field(request.execution, face.correction, "reflux correction");
const auto centering_axis = 1u << static_cast<unsigned>(face.axis);
if (face.coarse_integrated_flux.centering != POPS_FIELD_CENTERING_FACE_V1 ||
face.fine_integrated_flux.centering != POPS_FIELD_CENTERING_FACE_V1 ||
face.coarse_integrated_flux.centering_axes != centering_axis ||
face.fine_integrated_flux.centering_axes != centering_axis ||
face.correction.centering != POPS_FIELD_CENTERING_CELL_V1 ||
face.correction.centering_axes != 0 ||
face.coarse_integrated_flux.ownership != POPS_FIELD_OWNERSHIP_RUNTIME_BORROWED_V1 ||
face.fine_integrated_flux.ownership != POPS_FIELD_OWNERSHIP_RUNTIME_BORROWED_V1 ||
face.correction.ownership != POPS_FIELD_OWNERSHIP_RUNTIME_BORROWED_V1 ||
!same_reflux_face_shape(face.coarse_integrated_flux, face.fine_integrated_flux) ||
!same_reflux_face_shape(face.coarse_integrated_flux, face.correction) ||
face.coarse_integrated_flux.extents[face.axis] != 1 ||
std::string(face.coarse_integrated_flux.layout_identity) !=
face.correction.layout_identity ||
std::string(face.coarse_integrated_flux.patch_identity) != face.correction.patch_identity)
throw std::invalid_argument(
"reflux face fluxes and correction disagree on shape, centering or ownership");
for (std::int32_t axis = 0; axis < face.coarse_integrated_flux.dimension; ++axis)
if (face.coarse_integrated_flux.ghost_lower[axis] != 0 ||
face.coarse_integrated_flux.ghost_upper[axis] != 0)
throw std::invalid_argument("reflux face views cannot carry ghost cells");
}

status = unwritten_component_status();
const int code = api.apply_interface_batch(state, &request, &status);
if (!component_status_is_well_formed(status))
throw std::runtime_error("native Reflux component returned an invalid status");
if ((code == 0) != (status.code == 0) ||
(code == 0 && status.action != POPS_COMPONENT_CONTINUE_V1) ||
(code != 0 && status.action == POPS_COMPONENT_CONTINUE_V1) ||
(code != 0 && !component_text(status.reason)))
throw std::runtime_error("native Reflux component returned an inconsistent outcome");
return code;
}

inline std::string writer_geometry_key(const char* layout, std::int32_t level) {
return std::string(layout) + "\n" + std::to_string(level);
}
Expand Down
3 changes: 3 additions & 0 deletions include/pops/runtime/dynamic/component_loader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,9 @@ class LoadedComponent final {
if ((header->prepare == nullptr) != (header->destroy == nullptr))
throw std::runtime_error(
"native component interface prepare/destroy callbacks must be paired");
if (!generated_native_interface_table_is_complete(row.interface_id, row.table,
row.table_size))
throw std::runtime_error("native component interface table misses a required operation");
}
for (const auto& required : expected.interfaces) {
bool found = false;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Generated by scripts/generate_component_catalog.py from catalog 84c68fcee96663f71e7e7fa7589ec1ddee0d1037e741a678e4afd83c9749620a; DO NOT EDIT.
// Generated by scripts/generate_component_catalog.py from catalog 5c991781aadafd3165dccb4642086c8b20fbd4a83ee8e462f1e29078ecb0d1c4; DO NOT EDIT.
// This file is the sole Python/native request marshaller. init_component_loader.cpp only registers it.

#include <pops/runtime/dynamic/component_consumers.hpp>
Expand Down
12 changes: 10 additions & 2 deletions python/pops/_generated_component_interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

NATIVE_COMPONENT_ABI_VERSION = 1
NATIVE_COMPONENT_COMMON_ABI_VERSION = 1
NATIVE_COMPONENT_CATALOG_SHA256 = '84c68fcee96663f71e7e7fa7589ec1ddee0d1037e741a678e4afd83c9749620a'
NATIVE_COMPONENT_CATALOG_SEMANTIC_SHA256 = 'c0e14d4a3dd082612d052b2ce293e17de712c6916e39f430d0ff311ed0f24ef8'
NATIVE_COMPONENT_CATALOG_SHA256 = '5c991781aadafd3165dccb4642086c8b20fbd4a83ee8e462f1e29078ecb0d1c4'
NATIVE_COMPONENT_CATALOG_SEMANTIC_SHA256 = 'b29e5d1d811234bd55bfce83ffdf810ed95868392d1ac1da5644f8886620e129'
NATIVE_TAGGING_PROGRAM_ABI = {'version': 1,
'execution_modes': {'native_backend': 1, 'host': 2},
'collective_scopes': {'none': 0},
Expand Down Expand Up @@ -72,6 +72,14 @@
'hot_path': True,
'facets': ('stencil', 'lowering'),
'operations': ('apply',)},
{'id': 6,
'name': 'reflux',
'uri': 'pops://interfaces/reflux',
'version': 1,
'cpp_table': 'PopsRefluxApiV1',
'hot_path': True,
'facets': ('stencil', 'lowering', 'effects'),
'operations': ('apply_interface_batch',)},
{'id': 7,
'name': 'field_solver',
'uri': 'pops://interfaces/field-solver',
Expand Down
3 changes: 2 additions & 1 deletion python/pops/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,13 +170,14 @@ def resolve(name: str) -> ComponentInterface:
Tagger = resolve("tagger")
Clustering = resolve("clustering")
Transfer = resolve("transfer")
Reflux = resolve("reflux")
FieldSolver = resolve("field_solver")
Writer = resolve("writer")
FieldTopology = resolve("field_topology")


__all__ = [
"ComponentInterface", "resolve", "NumericalFlux", "GhostBoundary",
"FieldBoundaryClosure", "Tagger", "Clustering", "Transfer",
"FieldBoundaryClosure", "Tagger", "Clustering", "Transfer", "Reflux",
"FieldSolver", "Writer", "FieldTopology",
]
4 changes: 2 additions & 2 deletions python/pops/model/_generated_component_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

COMPONENT_CATALOG_SCHEMA_VERSION = 1
COMPONENT_MANIFEST_SCHEMA_VERSION = 2
COMPONENT_CATALOG_SHA256 = '84c68fcee96663f71e7e7fa7589ec1ddee0d1037e741a678e4afd83c9749620a'
COMPONENT_CATALOG_SEMANTIC_SHA256 = 'c0e14d4a3dd082612d052b2ce293e17de712c6916e39f430d0ff311ed0f24ef8'
COMPONENT_CATALOG_SHA256 = '5c991781aadafd3165dccb4642086c8b20fbd4a83ee8e462f1e29078ecb0d1c4'
COMPONENT_CATALOG_SEMANTIC_SHA256 = 'b29e5d1d811234bd55bfce83ffdf810ed95868392d1ac1da5644f8886620e129'
COMPONENT_INTERFACE_SPECS = ({'name': 'requirement', 'method': 'requirements', 'required_args': 0},
{'name': 'lowering', 'method': 'lower', 'required_args': 1},
{'name': 'stencil', 'method': 'stencil', 'required_args': 0},
Expand Down
Loading
Loading