HDF Results Query Architecture¶
This decision resolves the exploratory work tracked in #306–#309.
Conceptual basis and acknowledgments¶
Gyan Basyal (@gyanz) and his
Rivia library provided the conceptual basis
for RAS Commander's spatial results query work. Rivia's elegant point, profile,
velocity, and flow query functions inspired the design of HdfResultsQuery.
We gratefully acknowledge Gyan and WEST Consultants, Inc., credited by Rivia's
license.
The page below records the later bounded-read and deferred-selection decision
for HdfResultView; that class and the spatial HdfResultsQuery namespace
serve different purposes. Rivia's lazy HDF datasets are also a useful comparison
for selective reads, while RAS Commander's view adds explicit source-change
checks and bounded conversion/reduction APIs.
Related libraries and worked examples compares these approaches and demonstrates Rivia's mesh-face discharge and WSE profile queries. It also credits pyHMT2D's influence on spatial probing and identifies workflows where pydsstools or pyHMT2D is a useful companion.
Decision¶
ras-commander remains the semantic HEC-RAS layer and provides three levels of
result access:
- Existing eager pandas, xarray, and GeoPandas APIs remain the default.
- Direct selections, geometry-free summaries, and public batch iterators support bounded extraction without a new dependency.
HdfResultViewis an opt-in, source-backed query object for deferred selection, batching, reduction, and conversion to established containers.
DuckDB is not a core dependency and a raw DuckDBPyRelation is not a core
return type. Database/session ownership, SQL schema evolution, durable
materialization, and cross-product RAS/HMS relations belong in a separate
connector or the existing ras2cng integration. That connector should consume
get_mesh_summary_values(), iter_mesh_timeseries(), or
HdfResultView.to_arrow() rather than duplicate HEC-RAS paths and version
semantics.
File lifetime and consistency¶
An HdfResultView owns no live HDF handle. It stores a resolved source path,
dataset path, shape, dtype, selection, and a size/mtime fingerprint. Every
operation reopens the file read-only and checks that fingerprint before and
after reading, failing if the source moved or ordinarily changed.
This makes views pickleable and safe to pass between ordinary processes while
preventing a long-lived object from silently mixing two result-file states.
An iterator keeps one read-only handle open only while it is being consumed. Callers should finish or close an iterator before replacing a result HDF.
Selection and batching¶
Time and spatial integer/forward-slice selections are applied directly to the
h5py.Dataset. Integer indexes remain length-one slices so xarray schemas are
stable. Batches are time-major because HEC-RAS commonly stores 2D results as
(time, spatial_element) with one-timestep chunks.
The default batch target is 16 MiB of values. Callers can set an explicit row count or byte target. Materialization preserves the source dtype unless a conversion is requested. Full-period max, min, mean, and argmax reductions use the same bounded iterator and ignore NaN/infinite samples. Integer source data is promoted to Float64 for value reductions; an explicit reduction dtype must be floating point so nonfinite filtering cannot be corrupted by an integer cast. Argmax always returns source time indexes as Int64.
Eager conversion reads the selected slab once and trims it in memory.
Streaming, reductions, and shape use a bounded active-window scan when
truncate=True; all-zero selections keep their full selected time extent.
The public iter_mesh_timeseries() convenience iterator is deliberately
untruncated so it emits every selected source timestep exactly once.
Representative qualification¶
A read-only Windows qualification used a 482.6 MB computed Bald Eagle example
plan. Its Water Surface dataset contains 865 timesteps by 89,879 cells as
Float32, chunked (1, 89879). Each scenario ran in a fresh process. The first
pass is only a cold-cache candidate—the operating-system cache was not flushed.
| Scenario | Cold candidate | Warm | Peak RSS delta (cold / warm) |
|---|---|---|---|
| Lazy view creation | 0.0022 s | 0.0025 s | 1.5 / 1.5 MiB |
| Direct one-timestep slice | 0.0099 s | 0.0094 s | 3.5 / 3.5 MiB |
| Eager whole-array then slice | 1.284 s | 1.306 s | 315 / 318 MiB |
| Bounded raw maximum | 1.360 s | 1.358 s | 51.7 / 51.2 MiB |
| Eager raw maximum | 1.380 s | 1.351 s | 403 / 374 MiB |
A post-review rerun added the public default eager truncate=True scenario
after restoring its single-read implementation. It completed in 1.318/1.323 s
with 371/365 MiB peak RSS delta, eliminating the 2.95 s double-read regression
identified during independent QAQC. Direct/eager slice checksums and
bounded/eager maximum checksums remained identical.
The direct and eager timestep checksums matched. The bounded and eager raw
maximum checksums also matched. These timings are directional rather than a
cross-machine performance guarantee; rerun
scripts/benchmarks/benchmark_hdf_result_reads.py on representative local
plans.
A 5-by-4 synthetic benchmark also exercised cold-candidate and warm passes. At that scale eager reads were faster because file-open and labeling overhead dominates; this is why the established eager API remains the default and the view/iterator are opt-in tools for large or selectively queried results.
Geometry boundary¶
get_mesh_summary_values() returns identifiers, values, times, and HDF
metadata only. Existing get_mesh_summary() adds geometry once per call, after
the values have been assembled. SQL ingestion and aggregate analytics should
use the geometry-free form; map production can opt into the spatial form.
DuckDB and HDF extensions¶
The production connector boundary is a separate optional package or ras2cng:
- Arrow batches are the preferred in-process interchange when PyArrow is installed.
- Parquet/DuckDB materialization is preferred for repeated queries.
- A direct community HDF extension such as
h5dbremains experimental until its dependency/supply-chain posture, compound-type behavior, HEC null-value handling, and numerical equivalence are independently audited. - The connector must retain source fingerprints, plan/mesh identifiers, units, timestamps, and the distinction between native summaries and raw reductions.
This boundary keeps ras-commander lightweight while giving RAS and HMS tools
a reusable semantic input surface for a future shared SQL schema.