Fix two ALGO_BIOMD defects, and run the MD algorithms on MD data - #147
Merged
Merged
Conversation
There was a problem hiding this comment.
🟢 Approval recommended
The changes are test/CI-focused, internally consistent (API + integration harness), and the added metrics/coverage align with the PR goals without introducing correctness regressions in the reviewed diffs.
Pull request overview
This PR expands SZ3’s test coverage to exercise ALGO_BIOMD and ALGO_BIOMDXTC on real molecular-dynamics trajectory layouts ({frames, atoms, 3}), adds an MD trajectory fetch/convert path to the integration suite, and records compression ratio + timing metrics as CI artifacts to help detect regressions beyond error-bound correctness.
Changes:
- Added a new GTest module (
test_biomd.cpp) that round-trips synthetic trajectories through the public SZ3 API across both BioMD algorithms, including fill-frame edge cases and determinism. - Extended the integration driver to support
mdtraj:<dataset>datasets via a new fetch/convert script, and added seven MD datasets todatasets.jsonand the integration workflow matrix. - Recorded per-case compression ratio and compress/decompress timing via a
METRICS ...line, aggregated intointegration_metrics.csv, and uploaded as a workflow artifact.
File summaries
| File | Description |
|---|---|
| tools/test/modules/test_biomd.cpp | New unit tests covering BioMD/BioMDXTC on trajectory-shaped data and important edge cases (fill frames, low element counts, determinism). |
| tools/test/integration/test_sz3_executable.py | Measures compress/decompress time, enforces ratio ≥ 1, and emits a parseable METRICS line for aggregation. |
| tools/test/integration/integration_test_driver.py | Adds mdtraj: dataset preparation, captures test stdout for metrics parsing, and writes integration_metrics.csv. |
| tools/test/integration/fetch_md_trajectory.py | New utility to download/checksum/convert MDAnalysisData trajectories into raw float32 field files used by the integration suite. |
| tools/test/integration/datasets.json | Registers seven MD trajectory datasets (including limited-frame variants for very large trajectories). |
| .github/workflows/integration_test.yml | Adds workflow concurrency, runs MD trajectory datasets in the matrix, installs MDAnalysis, and uploads metrics artifacts. |
Review details
- Files reviewed: 6/6 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
SZBioMDDecomposition::save() writes firstFillFrame_ and fillValue_ unconditionally, but only compress_2d and compress_3d assign them. A 1D compression therefore put 12 bytes of whatever the object's memory last held into the stream: the same input compressed five times produced five different files. PR #145 gave the same two members an initialiser in SZBioMDXtcDecomposition, which was written from a copy of this file, and left the original. load() then took back everything it had charged. c_pos is the cursor before the reads, so c_pos - c is negative and the last line added the 37 bytes the bounded read() calls had just subtracted. Every later parse -- the encoder's load, the bin count, the decode -- ran on a budget that large again. The Xtc class has no such line. A sweep over the eleven algorithms at one, two and three dimensions puts the determinism defect in ALGO_BIOMD's 1D path alone; of the seventeen places that adjust remaining_length by hand, this is the only one that subtracts a difference taken in the wrong order. test_decomposition_save_load.cpp covers both halves of the contract for both decompositions at all three dimensionalities. Determinism is checked by constructing the decomposition over storage filled with two different patterns and comparing what save() writes, which does not depend on the allocator handing back dirty memory. Reverting either fix fails exactly the cases that fix addresses: the 1D determinism case, and the three accounting cases with "advanced 37 bytes but charged 0". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every SDRBench field the suite covers is 1D or 2D, and every 3D one is a climate or turbulence
field, so ALGO_BIOMD and ALGO_BIOMDXTC have never seen the {frames, atoms, xyz} layout they were
written for -- which is what GROMACS hands them through the HDF5 filter.
fetch_md_trajectory.py downloads the seven MDAnalysisData benchmark trajectories from the figshare
files that package points at, checks each against a recorded sha256, and writes the raw arrays the
rest of the suite reads. Taking the URLs rather than the package keeps the test data to immutable
files; reading DCD, XTC and NetCDF still needs MDAnalysis.
The published frame counts for nhaa and yiip are each one short of what the files hold. The script
validates the shape it is told against the shape it reads, which is how that surfaced.
nhaa and yiip are 911M and 302M elements in full, past what a job can do in half an hour, so those
two are cut to their leading 200 and 100 frames. The rest are 2M to 56M.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nothing exercised the fill-frame path. SZBioMDXtcDecomposition skips trailing frames whose values are all equal and writes them back on decompression, and no scientific field has such a frame; a trajectory buffer holding fewer frames than it has room for does. Seven cases over both algorithms: the trajectory layout at four error bounds, trailing fill frames, every frame after the first being fill, a single frame, 1D and 2D input, input shorter than one triplet, and that the same input compresses to the same bytes. Reconstruction rounds to float, so the bound holds to 1.0014x here and to 1.0071x for ALGO_BIOMDXTC through the HDF5 filter, where each chunk quantizes on its own. The cases allow 1.01x. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nothing in the suite looked at either, so a change that halved the ratio or doubled the time passed as long as the error bound held. The per-bit comparison the Huffman decode loop briefly carried cost ALGO_NOPRED 6% of its decompression speed and CI said nothing. Each case now reports its ratio and both timings, the driver collects them into a CSV, and the workflow keeps it as an artifact so two runs can be compared. A ratio below one fails outright: no ratio is guaranteed, but a compressor that grows its input is broken. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
std::max and std::fill come from <algorithm>, which libstdc++ happens to pull in through one of the other headers and libc++ does not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An adversarial read of the suite found four cases that could not report the defect they name.
The determinism case ran only at {5, 777, 3}. compressMultiFrame assigns every member save()
writes, so the 3D header is a function of the input whatever the memory held; the members that
go unassigned are on the 1D and 2D paths. It now runs all three, and fails on master until the
ALGO_BIOMD fix.
Both fill-frame cases used 1.25f and -3.5f, which sit on the eb = 1e-3 grid, so the quantizer
reproduced them exactly whether or not the frames were skipped -- deleting
findFillValueAndFirstFilledFrame left the suite green. The values are off-grid now and the tail
has to cost less than quantizing it.
{1, 1024, 3} is a 2D case: Config::setDims drops a dimension of 1. It keeps its shape, which is
what a filter chunking one frame at a time produces, and says so; TwoFrameTrajectory covers the
smallest shape that does reach the multi-frame path.
kBoundSlack was 1.01, tighter than the eb * 1.1 LinearQuantizer accepts, so a fixture with
coordinates in Angstrom rather than nanometres would have failed on a conforming result.
Also: the download retried only on an exception, and a connection dropping mid-transfer usually
closes cleanly, so a short file failed the one-shot checksum with no retry left. The checksum is
inside the loop now and a bad file is removed. The metrics are written from a finally, so a
dataset that fails to arrive still leaves the artifact behind. And the HDF5 filter reports a size
and a ratio per chunk mode, which the baseline keys by harness -- the path a simulation writes
through was the one not being watched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ayzk
added a commit
that referenced
this pull request
Sep 17, 2026
Brings in #147: the two ALGO_BIOMD defects, the seven MD trajectory datasets, the two test files and the compression baseline gate. Both conflicts were each side adding something. The workflow keeps fz's unbuffered python, which is what leaves a readable log when a job is killed, and takes master's metrics artifact. run_test keeps fz's flushed prints and takes master's tuple return along with the baseline and metrics functions built on it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
GROMACS is about to depend on
ALGO_BIOMDandALGO_BIOMDXTCthrough the HDF5 filter. Nothing ranthem on a molecular dynamics trajectory in the layout they were written for, and two defects in
ALGO_BIOMDwere live.Two defects in SZBioMDDecomposition
The same input does not compress to the same file.
save()writesfirstFillFrame_andfillValue_unconditionally; onlycompress_2dandcompress_3dassign them. A 1D compressionputs 12 bytes of whatever the object's memory last held into the stream. Master, one input, five
runs:
A trajectory cannot be checksummed and a pipeline cannot be re-run reproducibly. PR #145 gave the
same two members an initialiser in
SZBioMDXtcDecomposition, whose first line isBased on SZBioMDDecomposition.hpp. The copy was fixed and the original was not.load()gives back the bytes it charged.Measured: the cursor advances 37 bytes and
remaining_lengthis charged 0. Everything parsedafterwards runs on a budget 37 bytes larger than the buffer holds — the accounting #145 existed to
fix, left in place in the algorithm GROMACS uses.
Neither is present anywhere else. Four compressions of one input, each of the seven algorithms
at one, two and three dimensions:
ALGO_BIOMDat 1D gives four distinct outputs, the other twentycombinations are deterministic. Of the seventeen places in
include/SZ3that adjustremaining_lengthby hand,this is the only one that subtracts a difference taken in the wrong order.
What was not covered
SZBioMDXtcDecompositionreads its dimensions as{frames, atoms, xyz}and takes a different pathfor 3D. Every MD field the suite had — the three EXAALT sets — is 1D or 2D, and every 3D field is a
climate or turbulence field. Nothing had three as its last dimension, so this never ran:
Master also had no decomposition unit tests at all.
Seven trajectories
fetch_md_trajectory.pydownloads the MDAnalysisData benchmark set from the figshare files thatpackage points at, verifies each against a recorded sha256, and writes
xyz.f32as{frames, atoms, 3}plus a 2D file per coordinate. Using the URLs rather than the package keepsthe data to immutable files; MDAnalysis is still needed to read DCD, XTC and NetCDF.
md-peg-1chainmd-ifabp-watermd-adk-equilibriummd-cg-fibermd-membrane-peptidemd-nhaa-equilibriummd-yiip-equilibriumThree are GROMACS XTC.
nhaaandyiipare 911M and 302M elements in full, past what a job can doin half an hour, so they are cut to a leading slice. The published frame counts for those two are
each one short of what the files hold; the script validates the shape it is told against the shape
it reads, which is how that surfaced.
Tests
test_decomposition_save_load.cppcovers both halves of the save/load contract for both MDdecompositions at one, two and three dimensions. Determinism is checked by constructing the
decomposition over storage filled with two different patterns and comparing what
save()writes,which does not depend on the allocator returning dirty memory — compressing twice on the heap fires
at 4096 elements and not at 12288 on the same build. Reverting either fix fails exactly the cases
that fix addresses and nothing else: the 1D determinism case, and the three accounting cases with
advanced 37 bytes but charged 0.test_biomd.cppcovers the two algorithms through the public API: the trajectory layout at fourerror bounds, trailing fill frames, every frame after the first being fill, one frame per chunk, the
smallest multi-frame shape, 1D and 2D input, input shorter than one triplet, and determinism at all
three dimensionalities.
An adversarial read of the first version of that file found four cases that could not report the
defect they name, all fixed here: the determinism case ran only at 3D, where the header is a
function of the input whatever the memory held; both fill-frame cases used values sitting on the
quantisation grid, so deleting
findFillValueAndFirstFilledFrameleft the suite green;{1,1024,3}is a 2D case because
setDimsdrops a dimension of 1; andkBoundSlackwas tighter than theeb * 1.1LinearQuantizeraccepts.Compression ratio and timing
Nothing in the suite looked at either, so a change that halved the ratio passed as long as the error
bound held. Every case now reports its compressed size, ratio and both timings; the driver collects
them into
integration_metrics.csvand the workflow keeps it as an artifact.Sizes are checked against
compression_baseline.jsonwith a 5% band in either direction —compression is deterministic, so a size that moved means the output moved, and an improvement is
worth recording too. A key with no baseline is reported, not failed, so a new dataset does not break
its first run.
update_compression_baseline.pymerges the artifacts of a run into the file. Thebaseline is keyed by harness, so the HDF5 filter path is watched separately from the CLI.
Timings are recorded and not gated; runner-to-runner variance needs a few runs of data before a
threshold means anything.
Verification
Linux, gcc 13.3, Release with
BUILD_TESTING=ON: 0 warnings, all tests pass. The integration driveron
md-peg-1chain: 64/64, both algorithms and both harnesses at four error bounds over the 3D andthe 2D field. The HDF5 filter passes on the 3D layout in all three chunk modes. All seven
trajectories download, verify and convert. The baseline gate was exercised in all three states:
absent, matching, and drifted (which fails the case and the run).
The compressed format is unchanged for 2D and 3D; 1D
ALGO_BIOMDstreams differ from master only inthe 12 bytes that were indeterminate there, and master's own 1D streams differ from each other.
🤖 Generated with Claude Code