Skip to content

write_restart_file reads .variance_density instead of .directional_variance_density -- breaks when given a Spectrum/Dataset with directional data #15

Description

@cgrudz

Summary

write_restart_file (src/roguewave/wavewatch3/io.py) does the following when given a Dataset or Spectrum input:

if isinstance(spectra, (Dataset, Spectrum)):
    spectra = spectra.variance_density.values

roguewavespectrum.Spectrum.variance_density is not an alias for the full directional spectrum — for a 2D/directional spectrum it directionally-integrates and returns a 1D frequency-only marginal. Confirmed directly: Spectrum._directionally_integrate (_spectrum.py) does (data_array * self.direction_binwidth).sum(NAME_D, skipna=True) — a standard xarray .sum(dim=...) that drops the direction dimension entirely (not reduced to size 1). This is inconsistent with the sibling function in the same file, write_partial_restart_file, which correctly reads .directional_variance_density.values for the same kind of input.

Impact

Every Spectrum/Dataset this module itself produces is built from directional_variance_density, never variance_density:

  • RestartFile.__getitem__ (restart_file.py) builds its dataset with "directional_variance_density".
  • RestartFile.interpolate_in_space does the same.

So calling write_restart_file with any such Spectrum collapses the direction axis via directional integration before the shape checks run — confirmed a directional (n_points, n_freq, n_dir) spectrum comes back as (n_points, n_freq). The very next line does shape[2] != parent_restart_file.number_of_frequencies — but shape now only has 2 dimensions, so this raises IndexError: tuple index out of range instead of the intended ValueError, and the restart file is never written.

This isn't a theoretical path: io.py's own clone_restart_file helper calls write_restart_file(source_restart_file[:], target, source_restart_file, True)source_restart_file[:] returns exactly this kind of directional Spectrum via __getitem__. Also reproduces in esm-python-scripts's rewrite_restart_file.py's same-resolution branch (input_res == output_res), which passes a Spectrum from __getitem__ straight into write_restart_file.

Scope check — confirmed NOT to affect the newly-merged data-assimilation#152 driver. I checked wavespotter/data-assimilation's drivers/compute_ensemble_mean_ww3_restart_files.py on main directly: it never calls write_restart_file at all. It calls write_partial_restart_file instead, passing a raw DataArray (from xr.concat(...).mean(dim="member") on .directional_variance_density values) — which takes write_partial_restart_file's elif isinstance(spectra, DataArray) branch, bypassing the Dataset/Spectrum branch this bug lives in entirely. So the two known trigger points remain clone_restart_file (unused by any test) and esm-python-scripts's rewrite_restart_file.py (unmaintained, deprioritized per sofarocean/roguewave's current direction).

Why this was never caught

No test in tests/restart_files/test_io.py calls write_restart_file with a Spectrum or Dataset argument — coverage there is limited to write_partial_restart_file and reassemble_restart_file_from_parts. clone_restart_file, the one in-repo caller that would hit this (via __getitem__'s Spectrum output), is imported into the test helpers module but never actually called by any test.

Suggested fix

Change spectra.variance_density.values to spectra.directional_variance_density.values in write_restart_file, matching write_partial_restart_file. Add a test that calls write_restart_file with an actual Spectrum/Dataset (e.g. via clone_restart_file) rather than only a raw numpy.ndarray, to catch this class of regression.

Related

Found while investigating sofarocean/roguewave#14 and the esm-python-scripts regrid call site (same file, io.py, same "correct in one sibling function, wrong in the other" pattern as #14's depth-interpolation bug and #12's directions-axis validation bug).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions