Skip to content

NestedSimulation abstraction + ERA5 regional hindcast example#233

Draft
ewquon wants to merge 56 commits into
mainfrom
eq/era5_breeze_land
Draft

NestedSimulation abstraction + ERA5 regional hindcast example#233
ewquon wants to merge 56 commits into
mainfrom
eq/era5_breeze_land

Conversation

@ewquon

@ewquon ewquon commented May 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

@glwagner @kaiyuan-cheng

Adds examples/era5_breeze.jl — a building block for a regional modeling example that will eventually couple Breeze (compressible solver, in development) to forthcoming SlabLand and SlabOcean components.

We download ERA5 reanalysis over an SGP-centered LAM bounding box (HI-SCALE 2016-09-10 case day) and interpolate onto a LatitudeLongitudeGrid sized for ~3 km horizontal cells at the domain center latitude. Tᵥ is computed as a derived field using Breeze.ThermodynamicConstants for the Rᵥ/Rd − 1 coefficient.

Punch list

  • Breeze model construction
  • open boundary conditions
  • dynamical initialization (DFI)
  • terrain
  • acoustic substepping over terrain
  • land/ocean coupling, MOST

Notes

  • Surface pressure is read onto a 3-D grid with Nz=1 rather than a 2-D (Bounded, Bounded, Flat) grid, sidestepping CliMA/Oceananigans.jl#5473 (Flat↔non-Flat interpolate! errors with a cryptic BoundsError; #5474 will convert that to a clear ArgumentError but does not lift the restriction). Mirrors the pattern in examples/ERA5_hourly_data.jl.
  • This example uses a LatitudeLongitudeGrid. A future variant on a projected Cartesian grid would benefit from the set! projection support proposed in Add map projection support to set! for RectilinearGrid targets #232.

Test plan

  • Run end-to-end against the CDS API on a machine with ~/.cdsapirc configured.

@ewquon ewquon marked this pull request as draft May 13, 2026 19:53
Comment thread examples/era5_breeze.jl Outdated
Comment thread examples/era5_breeze.jl Outdated
Comment thread examples/era5_breeze.jl Outdated
Comment thread examples/era5_breeze.jl Outdated
Comment thread examples/era5_breeze.jl Outdated
Comment thread examples/era5_breeze.jl Outdated
@ewquon ewquon force-pushed the eq/era5_breeze_land branch from 9d673f4 to 5463396 Compare May 13, 2026 20:43
@codecov

codecov Bot commented May 13, 2026

Copy link
Copy Markdown

Comment thread src/DataWrangling/DataWrangling.jl Outdated
@ewquon

ewquon commented May 14, 2026

Copy link
Copy Markdown
Collaborator Author

Okay, the model state should be ready for dynamic initialization.
era5_breeze_profiles

A couple of important notes:

  1. As noted in ERA5 pressure-level ingest: per-column geopotential and surface-pressure masking #236, we need to better address geopotential height variability and we should discuss the approach to this. I've prototyped per-column vertical interpolation to unblock the ICs work and to have as a reference when developing the data wrangling improvement.
  2. Because we don't have terrain yet, I'm using the height above ground level to interpolate onto the flat-bottomed Breeze grid. (Otherwise, portions of the Breeze domain would be essentially underground and I imagine this would be problematic for the initialization)

@glwagner

Copy link
Copy Markdown
Member

@ewquon should we add terrain? Terrain following should work for the fully compressible formulation (just not substepping)

@ewquon

ewquon commented May 14, 2026

Copy link
Copy Markdown
Collaborator Author

@ewquon should we add terrain? Terrain following should work for the fully compressible formulation (just not substepping)

Hallelujah! I'm happy to add that in. Anything that gets us closer to reality sooner rather than later is a win in my book.

Comment thread examples/era5_breeze.jl
Comment thread docs/make.jl Outdated
Comment thread src/DataWrangling/set_region_data.jl Outdated
#
# Despite the name (chosen for consistency with `ocean_simulation` /
# `atmosphere_simulation`), `child_simulation` returns an `AbstractModel` — the
# Breeze `atmosphere_simulation` helper returns a model, not a `Simulation`,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this true? Would it be possible to have consistent naming? It might get confusing if the behavior is bit lost in here in a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually would be nice to not need extensive commenting

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for probing this @simone-silvestri. Turns out this is totally unused code — I'm tossing it

Comment thread src/EarthSystemModels/NestedSimulations/child_simulation.jl Outdated
Comment thread src/EarthSystemModels/NestedSimulations/child_simulation.jl Outdated
Comment thread src/EarthSystemModels/NestedSimulations/child_simulation.jl Outdated
Comment thread src/EarthSystemModels/NestedSimulations/child_simulation.jl Outdated
Comment thread src/EarthSystemModels/NestedSimulations/interpolated_fts_boundary.jl Outdated
Comment thread src/EarthSystemModels/NestedSimulations/interpolated_fts_boundary.jl Outdated
Comment on lines +6 to +16
# The child carries the actual prognostic state and the Open BCs / interior
# `Relaxation` forcings that reference the parent's `FieldTimeSeries`. Wrapping
# the child in `NestedModel` lifts the parent-sync into the integrator itself
# (where it belongs — it's not output behavior), so a plain
# `Simulation(NestedModel(parent, child))` just works.
#
# All Oceananigans model-protocol calls (`clock`, `grid`, `fields`,
# `prognostic_fields`, `update_state!`, ...) forward to the child via
# `Base.getproperty` plus a small set of explicit dispatches. Only
# `time_step!` carries new behavior — step the child, then advance the parent
# by the elapsed Δt.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all this probably belongs in a docs page?

Comment on lines +47 to +52
@inline function Base.getproperty(nm::NestedModel, name::Symbol)
if name === :parent || name === :child
return getfield(nm, name)
end
return getproperty(getfield(nm, :child), name)
end

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so the property of a nested model are redirected to the child? Maybe the nested model should have its own clock? Also this means that nested.grid is the grid of the child model?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The nested model clock behavior will be updated when I address #330

nested.grid is the grid of the child model

Yes

Comment thread src/EarthSystemModels/NestedSimulations/interpolated_fts_boundary.jl Outdated
Comment on lines +86 to +89
# Surface freshwater fluxes are meaningless for a volumetric atmosphere acting as
# a nesting parent; default to `nothing` there. The surface_*_flux accessors and
# `extract_field_time_series` already handle the Nothing branch.
function default_freshwater_flux(grid, times; volumetric=false)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ERA5 does not come with 2D freshwater fluxes even if other fields are 3D?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ERA5 does accumulate the surface quantities that contribute to the freshwater flux. This comment and code design is narrowly focused (only lateral, no surface BCs yet) and is confusing. I will fix

ewquon and others added 2 commits June 8, 2026 16:10
…nostics)

Factor the moist-thermodynamic-state → Breeze CompressibleDynamics prognostic
(ρ, θˡⁱ, qᵗ) conversion into one Field-based helper:

- declare the `breeze_prognostic_state` generic in Atmospheres (exported and
  re-exported from NumericalEarth), mirroring `atmosphere_simulation`
- add the method in NumericalEarthBreezeExt, dispatched on
  Breeze.ThermodynamicConstants

In examples/era5_breeze.jl, route both the parent FieldTimeSeries population and
the child initial condition through it (dropping the duplicated ρ/θˡⁱ/qᵗ
formulas), and switch the Davies relaxation to the `parent_forcings` helper.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- In `populate_parent_snapshot!`, pull the pressure-level variables from one
  `MetadataSet` (#235) instead of repeating the `Metadatum` kwargs per variable.
- Bundle the nine parent-side `FieldTimeSeries` into a single `parent_series`
  NamedTuple (the `metadata_field.jl` idiom) rather than nine identical
  allocations; update the downstream BC / Davies / fill references.
- TODO the wholesale collapse into a `dates`-spanning `FieldTimeSeries(pl, grid)`
  once terrain support (#241) lands.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread src/EarthSystemModels/NestedSimulations/child_simulation.jl Outdated
Comment thread src/EarthSystemModels/NestedSimulations/child_simulation.jl Outdated
Comment thread src/EarthSystemModels/NestedSimulations/child_simulation.jl Outdated
Comment thread src/EarthSystemModels/NestedSimulations/interpolated_fts_boundary.jl Outdated
ewquon and others added 12 commits June 8, 2026 23:17
Cleanup comments, drop leading underscore from helper functions

Co-authored-by: Simone Silvestri <silvestri.simone0@gmail.com>
Co-authored-by: Eliot Quon <eliot@aeolus.earth>
`child_simulation` (and its `build_child_model` / `parent_variables` helpers,
including the Breeze-ext methods) had zero callers: the example and the test
suite wire `parent_boundary_conditions` + `parent_forcings` + `NestedSimulation`
directly, and per the design notes its single `variables` tuple can't serve both
the BC (prognostic) and forcing (specific) key conventions. Drop the two files
plus their includes/exports.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add two testsets to test_nested_simulation.jl:
- unit test for `breeze_prognostic_state` — the dry/p=pˢᵗ limit exactly, and the
  moist + condensate case against the documented (ρ, θˡⁱ, qᵗ) formulas;
- integration test wiring a Breeze `AtmosphereModel` as a `NestedSimulation` child
  via `atmosphere_simulation(…).model` + `parent_boundary_conditions`, asserting the
  #220 contract (Simulation → .model is an AbstractModel) and a stable few-step run.

Both verified on CPU; the integration set runs per `test_architectures` (GPU on GPU CI).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per review (Simone), flip the kwarg polarity so the default surface atmosphere
reads `two_dimensional = true` and the 3D nesting-parent form is
`two_dimensional = false`. Behavior is unchanged — the default path (2D surface,
for ocean / sea-ice coupling) is identical. Simplify the helper comments and
update the field-set testset accordingly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per review (Simone): the freshwater (precipitation) flux is a surface (2D) field
that datasets such as ERA5 provide regardless of whether the dynamics fields are
3D. So `default_freshwater_flux` no longer branches on `two_dimensional` — it
always builds the 2D `PrescribedPrecipitationFlux`. `two_dimensional` now controls
only the velocity/tracer/pressure dimensionality; omit the flux explicitly with
`freshwater_flux = nothing` (e.g. a NestedSimulation parent that needs no surface
coupling). Extend the field-set test: a 3D atmosphere keeps its surface freshwater
flux, and opt-out is via the keyword.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…on rename

Oceananigans 0.110 renamed Open→NormalFlow and OpenBoundaryCondition→
NormalFlowBoundaryCondition with no alias, breaking the lateral-BC helper
under the main-merge compat bump. Update the helper, the Interpolated
wrapper docs, and the nested-simulation tests; narrow Oceananigans compat
to 0.110.1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The atmosphere→exchange state interpolation read dynamics.reference_state.density,
which is `nothing` for CompressibleDynamics (prognostic density, no anelastic
reference state), breaking AtmosphereLandModel coupling of a compressible Breeze
atmosphere. Use Breeze's dynamics-agnostic dynamics_density / surface_pressure
accessors instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Regression for the interpolate_state! compressible fix: an AtmosphereLandModel
wrapping a Breeze CompressibleDynamics atmosphere constructs and wraps as a
NestedSimulation child (exercises the coupled update_state! → interpolate_state!
path). Construction-level — stepping the coupled child awaits a Breeze
energy-flux/qᵛ fix for the compressible path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

build all examples add this label to build all the examples in the PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants