Skip to content

Fix CMIP7 variables written as double instead of float, causing fill value precision drift - #570

Merged
rbeucher merged 3 commits into
mainfrom
fix/cmip7-dtype-fillvalue-drift
Jul 29, 2026
Merged

Fix CMIP7 variables written as double instead of float, causing fill value precision drift#570
rbeucher merged 3 commits into
mainfrom
fix/cmip7-dtype-fillvalue-drift

Conversation

@rbeucher

Copy link
Copy Markdown
Member

Summary

siconc, and likely most/all CMIP7 output variables, were being written as double even though the source model data is float32. Two problems:

  • Doubles storage for no precision gain.
  • The fill value is computed while the data is still float32, then the data gets upcast to double afterward - but the fill value attribute doesn't get upcast with it. float32(1e20) promoted to double is 1.0000000200408773e+20, not exactly 1e20, so exact-equality masking against _FillValue silently fails and masked cells show up as spikes instead of being masked out in plots.

Root cause: the CMORiser picks output dtype from the CMOR table's "type" field, defaulting to "double" when missing. CMIP7 tables never set "type" per-variable (CMIP6's do), so every CMIP7 variable fell through to that default regardless of the source dtype.

Changes

  • sea_ice.py, ocean.py, atmosphere.py: default to the source array's own dtype instead of hardcoding double; re-cast _FillValue/missing_value to the final dtype whenever a cast does happen.
  • driver.py: switched fill-value masking in the iris conversion path (to_iris()) from exact equality to tolerance-based comparison, as a safety net.

Test plan

  • pixi run -e test pytest tests/unit/ - 1552 passed, 2 skipped
  • Spot-check a CMIP7 siconc output file to confirm it's now float and masks correctly in plots

…ion drift

CMIP7 CMOR tables don't set a per-variable "type" (unlike CMIP6), so the
dtype-casting fallback in the sea-ice/ocean/atmosphere CMORisers always
hit its hardcoded "double" default, upcasting every CMIP7 variable to
float64 regardless of the source data's actual precision.

That upcast also happened after _FillValue/missing_value were computed
against the pre-cast dtype, so the sentinel value silently drifted from
the data's fill cells once promoted to float64 (float32(1e20) != 1e20
after promotion), breaking exact-equality masking downstream.

Fall back to the source array's own dtype instead of hardcoding double,
re-cast fill/missing value attrs to the final dtype whenever a cast does
happen, and switch to tolerance-based fill-value masking in the iris
conversion path as a safety net.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 75.0%. Comparing base (a980f9d) to head (e12c592).

Additional details and impacted files
@@           Coverage Diff           @@
##            main    #570     +/-   ##
=======================================
+ Coverage   74.9%   75.0%   +0.1%     
=======================================
  Files         36      36             
  Lines       7503    7517     +14     
  Branches    1409    1415      +6     
=======================================
+ Hits        5616    5634     +18     
+ Misses      1589    1586      -3     
+ Partials     298     297      -1     
Flag Coverage Δ
unit 75.0% <100.0%> (+0.1%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

rbeucher and others added 2 commits July 29, 2026 10:15
…butes()

Adds regression tests for the CMIP7 dtype fallback fix: no per-variable
type preserves the source dtype, an explicit double upcasts and
re-casts _FillValue/missing_value to match, and valid_min/valid_max are
cast to the same target dtype before range checking.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@rbeucher

Copy link
Copy Markdown
Member Author

Fix #569

@rbeucher
rbeucher merged commit 91ed497 into main Jul 29, 2026
4 checks passed
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