Date: 2026-07-01 Purpose: Definition of the BELFEM .bfm (HDF5) enriched-mesh file format — layout, datatypes, semantics, and the save/load contract. Module: src/mesh (cl_Mesh_BfmFile.{hpp,cpp}, cl_ProtoMesh.{hpp,cpp})
A .bfm file is an enrichment cache, not a general mesh exchange format. Maxwell runs spend significant time in the cohomology/cut, thin-shell, hanging-entity, and periodicity factories; the .bfm stores the result of that enrichment so a rerun (parameter change or restart) can load the finished mesh and skip the factories entirely.
Three principles govern the format:
The file is written and read on the master proc only (rank 0). Workers receive the mesh through the normal MPI distribution afterwards.
/meta/checksum stores the checksum of the base (pre-enrichment, post-scaling) source mesh. On a rerun, MaxwellFactory::read_mesh compares the freshly-loaded .msh checksum against the sidecar. If it matches, the .bfm is loaded and the enrichment factories are skipped; if it does not, the .bfm is regenerated. Storing the base value (not the enriched mesh's) is what makes the gate meaningful across runs.
| Convention | Meaning |
|---|---|
| id_t | uint32 — entity IDs (nodes, elements, …). 0 and gNoID are invalid IDs. |
| index_t / uint | uint32 — counts and enum values |
| real | float64 |
| uchar | uint8 — small enums, local indices, orientation codes, bit flags |
| suint | uint16 — EntityType codes in /hanging |
| string | HDF5 variable-length byte string |
| [N] | fixed-length dataset, one row per entity |
| [N][var] | HDF5 variable-length (vlen) dataset (hdf5::Dataset helper, cl_HDF5_Dataset.hpp): one variable-length row per entity |
| (opt) | group/dataset written only when non-empty; the reader guards with group_exists / dataset_exists |
Coordinate matrices are stored transposed: in memory the canonical layout is (dim × n) (column-major, one entity per column); on disk the dataset is (n × dim) — one entity per row, so the file reads naturally in HDFView.
Enum values (DomainType, ElementType, EntityType) are stored as their numeric codes; the authoritative definitions are en_DomainType.hpp, Mesh_Enums.hpp.
Groups appear in BfmFile::save() order. (opt) marks groups and datasets that are written only when non-empty; the loader guards them with group_exists / dataset_exists. Counts used below: B blocks, S sidesets, N nodes, E elements, Fc facets, Ed edges, F faces, Cp control points, H hanging entities per type, T thin shells, V vertices, C curves.
/circuit is not written today. BfmFile::save() finishes after the curve data and emits no circuit group (grep for circuit in cl_Mesh_BfmFile.cpp returns nothing). The layout below records the intended block; a reader must not expect to find it in a current file.
Notes:
Two membership relations are positional rather than ID-keyed, mirroring how the MPI distributor streams entities:
Everything else — facet master/slave, edge/face incidence, duplicates, hanging sources, periodic pairs, thin-shell blocks, curve segments — is by entity ID.
Container order in the file is the save-time container order of the rank-0 mesh, and the loader preserves it. This makes the reload deterministic (same DOF enumeration as the run that wrote the file); it does not rely on that order for correctness, since all references are ID-keyed.
What the reader (BfmFile::load) does:
What is deliberately not in the file:
| not stored | why / how it comes back |
|---|---|
| connectivity (node→element, node→facet, node→node, element→element) | recomputed by Mesh::finalize() / ConnectivityCalculator on load, and at Kernel::distribute_mesh time |
| edge directions, curved-element flags | recomputed (deterministic from node IDs / geometry) |
| element→edge/face incidence | reconstructed by node-key matching from /edges, /faces |
| regular block→material | input file is authoritative (rebuilt every run) |
| physical fields, global variables, time cursor | separate restart file (Mesh::save_fields / load_fields), checksum-guarded to the mesh identity |
| entity owners (proc_t) | assigned by the partitioner at Kernel construction on every run |
| the Maxwell topology type map | synthesized, not stored: the fresh path builds it before enrichment, so a reload must not scan the enriched mesh with Topology::run(). Topology::run_on_enriched_mesh() reproduces the pre-enrichment view by excluding everything reachable from mMesh->thin_shells() and skipping sideset-type re-detection |
Consumers must respect one invariant when extending the format: anything the fresh path derives before enrichment must either be stored or synthesized with the pre-enrichment view in mind — scanning the enriched mesh would give a different answer.