Skip to content

fix: NetCDF forcing month-range off-by-one + CSV quantization matching#78

Merged
DankerMu merged 1 commit into
masterfrom
feat/netcdf-forcing-validation
Feb 14, 2026
Merged

fix: NetCDF forcing month-range off-by-one + CSV quantization matching#78
DankerMu merged 1 commit into
masterfrom
feat/netcdf-forcing-validation

Conversation

@DankerMu
Copy link
Copy Markdown
Owner

Summary

Fixes the off-by-one bug in NetcdfForcingProvider::initCmfdMonths() that caused a fatal error when END falls exactly on a month boundary (e.g. END=730 days from 20170101 → requests non-existent 201901 file).

Adds CSV-matching quantization in loadTimeStep() so that NetCDF forcing values are bit-identical to AutoSHUD-generated baseline CSV forcing.

Changes

NetcdfForcingProvider.cpp

  • initCmfdMonths(): use nextafter(sim_end_min, -inf) to treat END as exclusive bound for file discovery
  • loadTimeStep(): add rounding to match AutoSHUD CSV quantization (precip 4dp, temp 2dp, RH 4dp, wind 2dp + 0.05 min clamp, radiation integer)
  • maxTimeMin(): account for step-function semantics — last record covers one more interval beyond its timestamp

TimeSeriesData.cpp/hpp

  • Add getMaxTimeCovered() that returns maxTime + lastDt for step-function coverage

MD_readin.cpp

  • Use getMaxTimeCovered() in validateTimeStamps() for CSV forcing coverage check

Verification

10-day QHH run (END=730, START=1):

  • compare_forcing.py: all 5 variables max_abs_diff = 0 (exact match)
  • Output .dat files: binary identical (md5sum match)
  • Baseline rerun with NETCDF=1 binary: output unchanged

- NetcdfForcingProvider::initCmfdMonths: use nextafter(sim_end_min, -inf)
  to avoid requesting the month of the exclusive END boundary (fixes 201901
  file-not-found when END=730 days from 20170101)
- Add CSV-matching quantization in loadTimeStep: precip 4dp, temp 2dp,
  RH 4dp, wind 2dp+0.05 min, radiation integer — matches AutoSHUD baseline
- maxTimeMin(): account for step-function semantics (last record covers
  one more interval beyond its timestamp)
- TimeSeriesData: add getMaxTimeCovered() for CSV forcing coverage check
- MD_readin: use getMaxTimeCovered() in validateTimeStamps()
@DankerMu
Copy link
Copy Markdown
Owner Author

Review: SHUD PR #78

initCmfdMonths nextafter fix (L781-791)

Correct. END is exclusive; nextafter(sim_end_min, -inf) prevents requesting the month of the boundary day. Guard handles degenerate case.

loadTimeStep quantization (L1260-1325)

Rounding matches AutoSHUD R CSV output. Order correct: clamp NaN/negative → round → threshold. nearbyint is the right choice.

maxTimeMin step-function (L1762-1779)

Backward scan for last positive dt is robust. Correctly extends coverage for step-function semantics.

TimeSeriesData::getMaxTimeCovered

Clean addition. lastDtMin computed during file scan, no extra I/O.

Verification

10-day QHH: all 5 forcing vars max_abs_diff=0, output .dat binary identical, baseline unbroken.

@DankerMu DankerMu merged commit 202ddb2 into master Feb 14, 2026
5 checks passed
@DankerMu DankerMu deleted the feat/netcdf-forcing-validation branch February 14, 2026 01:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant