Skip to content

First implementation of ngen BMI serialization protocol - #63

Open
robertbartel wants to merge 17 commits into
NOAA-OWP:masterfrom
robertbartel:serial_dev
Open

First implementation of ngen BMI serialization protocol#63
robertbartel wants to merge 17 commits into
NOAA-OWP:masterfrom
robertbartel:serial_dev

Conversation

@robertbartel

@robertbartel robertbartel commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Implements the (pending) ngen BMI serialization protocol. Initial implementation geared primarily to supporting hotstart scenarios that should not restore full state (i.e., resume restores of full checkpoints). Designed to prepare for future use of resume restores as well, once mechanism is in place to communicate to model which is in use.

Builds on the earlier NGWPC serialization work, present in the NGWPC/development branch, while adapting it for aforementiion ngen protocol. In particular, adheres to the same set of properties being serialized and deserialized as in the NGWPC version, excepted for deseiralization of current_time_step (left out of restore because its wrong for hotstart).

Note that it is likely other state properties should also be omitted from hotstart restore deserialization. However, that will be scoped to a different issue/PR; this is just for the initial functional port of serialization functionality compatible with the ngen protocol.

Additions

  • src/bmi_serialization.cpp, include/bmi_serialization.h, with Boost-based binary serialize/deserialize of model state, with support for stale payloads checks.
  • include/ngen_utilities.h, with details on the protocol's four reserved variable names and their required units, types and item sizes.
  • include/vecbuf.hpp, with vendored streambuf adapters to optimize archive reads/writes.
  • test/main_unit_test_serialization.c, with protocol conformance and round trip tests
  • test/main_integration_test_serialization.c, with 950 time step restart equivalence integration test.
  • define deserialization restore types for future use
  • CMake targets for test suites, with AddressSanitizer on by default for tests.

Removals

Changes

  • Get_value/Set_value and other appropriate BMI functions to support variables for protocol and through those trigger serialization and deserialization
  • Restore supporting different variations depending on restore mode, though just defaulting to hotstart for now
  • Moving the project to C++ 17
  • Requiring Boost 1.86.0 serialization as project dependency

Testing

  1. cmake -B build && cmake --build build
  2. Run from the build directory: the .run config names its inputs relative to cwd: cd build
  3. ./serialization_unit_tests: 10 checks, all passing
  4. ./serialization_integration_tests: 3 scenarios over 950 steps and 14 variables, all passing
  5. ./simple_unit_tests: existing suite, unaffected

Screenshots

Notes

  • New external Boost::serialization dependency is not header-only

Todos

  • Establish way of determining/communicating/receiving which restore method should apply
  • current_time_step is read by the physics (channel routing), not only used as an index; it thus both clearly meets the criteria to be excluded AND clearly meets the criteria to be included in the state transferred in a hotstart restore (probably this means state needs to be extended, as this property is overloaded)

Checklist

  • PR has an informative and human-readable title
  • Changes are limited to a single goal (no scope creep)
  • Code can be automatically merged (no conflicts)
  • Code follows project standards (link if applicable)
  • Passes all existing automated tests
  • Any change in functionality is tested
  • New functions are documented (with a description, list of inputs, and expected output)
  • Placeholder code is flagged / future todos are captured in comments
  • Project documentation has been updated (including the "Unreleased" section of the CHANGELOG)
  • Reviewers requested with the Reviewers tool ➡️

Testing checklist

Target Environment support

  • Windows
  • Linux

idtodd and others added 8 commits August 13, 2026 09:17
Cherry picking commit 92e10c3 from
ngwpc/development, with modifications:

- Dropped Makefile work, now that project uses CMake; added Boost and
  C++17 to CMakeLists.txt instead
- Resolved conflicts in src/bmi_topmodel.c unrelated to serialization
- Replaced Log(...) calls with fprintf(stderr, ...), since EWTS logging
  is not carried over
Cherry picking commit a064751 from
ngwpc/development.

Splits serialization_size out as its own variable and moves the capture
action from Get_value_ptr to Set_value, matching protocol semantics.
Cherry picking the serialization parts of commit
6259302 from ngwpc/development.  Only
include/vecbuf.hpp, include/bmi_serialization.h and
src/bmi_serialization.cpp are taken; the reset_time signal and the
topmodel.c/h signature refactor in that commit are left behind.

Despite its name, this commit carries most of the serialization
correctness work:

- Read the archive through membuf and an explicit length; the previous
  istringstream truncated binary payloads at the first NUL byte
- Archive time_delay_histogram, which was omitted entirely
- Size contrib_area by nstep and Q by num_delay plus the histogram
  ordinates; both counts were wrong
- Archive the array counts and reallocate on load when they change
- Refuse to serialize when running stand alone
- Prefix the payload with its length

Also drops includes left unused once istringstream went away.
Fixing conformance with ngen protocol:

- add include/ngen_utilities.h holding the reserved name, type, unit and
  item count tables, indexed by a shared enum
- rename involved variables to have `ngen::` prefix
- resolve the variables in Get_var_units, which is the engine's entire
  support probe and previously could never find them
- tweak types of serialization_size and serialization_create to int
- keep the variables out of output_var_names and input_var_names, and
  leave Get_var_grid and Get_var_location failing for them
- don't fail Get_var_nbytes or Get_value_ptr during restore, when they
  are needed but no buffer exists yet
Declare a layout version through BOOST_CLASS_VERSION and check it before
archiving anything, so a payload written by an incompatible build is
rejected rather than misread.  Boost stores the version in the archive
itself and rejects newer ones on its own; this catches older ones.

Also construct the archives inside the try blocks.  The binary_iarchive
constructor validates the header, so a foreign or corrupt payload was
escaping deserialize_topmodel as an exception and terminating the
process instead of returning BMI_FAILURE.
New serialization_unit_tests target covering the engine's support probe
(the exact reserved unit strings), the other introspection calls, the
save/restore sequence, free being safe whenever issued, and rejection of
corrupt or stale payloads.

The expected names, types and units are spelled out in the test rather
than read from ngen_utilities.h, so a wrong table is caught instead of
agreeing with itself.
New serialization_integration_tests target, kept separate so it does not
slow the other targets.  Runs the full 950 step forcing record from
inputs.dat three ways -- straight through, with one restart, and with
three -- and requires every output variable to match exactly at every
step.

Each restart writes the state to a file, finalizes and frees the model,
then builds and initializes a new one to restore into, so state left
dangling in the previous instance is caught rather than read by luck.

The test was checked against a deliberately broken serializer: omitting
sbar is caught at the first step after each restart.  The header notes
what the current fixture cannot reach, since it has no channel delay.
Writing all model state to snapshots but restoring differently based on
designated restore mode, corresponding to either a resume (full restore
of all state including config and clock) or a hotstart (restore of state
of model properties appropriate to seed a new, separate simulation)
@robertbartel robertbartel added the enhancement New feature or request label Aug 15, 2026
@robertbartel
robertbartel marked this pull request as draft August 15, 2026 03:30
@robertbartel

Copy link
Copy Markdown
Contributor Author

I've been developing this against a stale copy of the ngen serialization protocol branch and will need to reassess. I doubt it'll need a complete re-do, but moving to Draft while I sort that out.

Steps back to applying every archived value, as upstream did, keeping only
the exclusion of current_time_step.  May not be right, but figuring out
will be done later; for now, follow NGWPC implementation.
Follow updated protocol which makes ngen::serialization_size 64 bit, so no
longer carries a separate length value.
Make sure CI actually uses the new tests.
Block and fail if trying to set serialization size in the middle of a restore,
when there exists a buffer and so, by definition, already must exist a size
value that is more correct and should not be changes (for as long as said buffer
exists).
Avoid issues with (g,s)et_value_at_indices and use of backing pointer with
special serialization protocol variables by disallowing their use with the
indices-specific functions.
@robertbartel
robertbartel marked this pull request as ready for review August 17, 2026 16:53
@robertbartel

Copy link
Copy Markdown
Contributor Author

Ok, I've adjusted the implementation to be against the correct target ( 🤦 ). Worth noting: I left the commit history intact, rather than folding my corrections into prior commits (I tweaked the commit messages a bit though).

Comment thread .github/workflows/build_and_run_unit_test.yml Outdated
Comment thread src/bmi_serialization.cpp Outdated
PhilMiller
PhilMiller previously approved these changes Aug 17, 2026

@PhilMiller PhilMiller left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modulo the concerns around the todos, the code looks good to me. I'll approve, given that this is not in Draft mode, but I'll let you decide whether to merge or not.

Create shareable action for building Boost serialization dependency and
using it rather than explicit steps across workflows (though moving location
in ngen_integration due to some workspace hacking).
Avoid side effects of workspace-replacement trickery, which was causing cache saves to
fail, by manually splitting.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants