Skip to content

ERA-5 model levels: loader passes the lowest full-level z where _calculategeoh expects the true surface geopotential (~+10 m column height bias) #812

Description

Context

Follow-up to #806, as discussed in #811. #811 fixes the north–south mirror of the geopotential cube; this issue tracks the smaller residual item acknowledged there, so it doesn't get lost when #806 is closed by the merge.

The defect

_load_model_level passes z[0] — the lowest full model level after the axis fix — as z_surface to _calculategeoh, whose contract is the true 2D surface geopotential. The hydrostatic integration therefore starts from the lowest-full-level height instead of the terrain surface.

Measured effect (same harness, raw file, and frozen environment as the #806 / #811 measurements, RAiDER at #811 head 919d6b6): every reconstructed column carries a near-uniform vertical offset of +9.44 to +10.20 m (mean +9.81 m) against a true-surface reference — the height of the lowest full level above ground. Within-column drift is ≤ 0.43 m (std), so the error is essentially a rigid upward shift of each column. Details: reports/ml_offset_regression_pr811.md.

Why the loader can't do better today

Since f81225b (#751), the fetch step recomputes the full-level z cube with calcgeoh and overwrites the downloaded surface z in the stored file. The true surface field is discarded at fetch time, so no loader-side change alone can recover it for freshly fetched files.

Proposed fix

  1. Fetch: keep the downloaded surface geopotential as its own variable (e.g. z_surface) alongside the recomputed full-level cube, instead of overwriting it.
  2. Loader: use z_surface when present; fall back to the current post-Fixes a north–south flip of the geopotential cube in the ECMWF/ERA-5 model-level reader. #811 behavior (z[0], lowest full level) for existing cached files, so nothing breaks.
  3. Test: extend test/test_ecmwf_levels.py with a fixture carrying a distinct z_surface, asserting the surface height matches the terrain to well under the ~10 m lowest-level offset.

Interaction with #805

The reworked ML fetch in #805 (checked at its current head 6586c94) also replaces the downloaded surface z with the recomputed cube, in both its single-request and batch paths. So whichever fetch implementation lands first, the acceptance criterion is the same: fetched model-level files retain the true surface geopotential as a separate variable, and the loader prefers it. If #805 merges first, item 1 above moves into that fetch path.

I'm happy to submit a PR for this once #811 is merged.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions