Motivation
#200 introduces three *PrescribedRadiation constructors (JRA55PrescribedRadiation, ECCOPrescribedRadiation, OSPapaPrescribedRadiation) covering MIP-protocol global ocean spin-ups, ECCO state-estimate-driven runs, and single-column observational forcing respectively. Missing from the family is the natural radiation analogue for regional hindcast forcing at mesoscale resolution.
The data plumbing is already in place: src/DataWrangling/ERA5/ERA5.jl:87–88, 117–118 already maps :downwelling_shortwave_radiation → "surface_solar_radiation_downwards" (ssrd) and :downwelling_longwave_radiation → "surface_thermal_radiation_downwards" (strd) for both the CDS API and the NetCDF short names. What's missing is the convenience constructor that wires those up into a PrescribedRadiation.
Target use case
Regional / mesoscale hindcast simulations (typically ~O(1) km grids) where the user is replaying a specific past period with a regional ocean model initialized from GLORYS and forced by ERA5 atmosphere. This is a different regime from the existing *PrescribedRadiation constructors:
- Not OMIP-protocol-bound, so JRA55-do's MIP-comparability isn't a constraint.
- Not budget-closed multi-decadal, so ECCO is the wrong tool.
- Not single-column, so OSPapa is the wrong tool.
Why ERA5 specifically for this regime
- Self-consistency with GLORYS. GLORYS12V1 is a NEMO 1/12° ocean reanalysis forced at the surface by ERA5 (older versions used ERA-Interim). Using ERA5 radiation in a hindcast initialized from GLORYS continues the same forcing the ocean state was assimilated under, minimizing spurious spin-up drift. Pairing ERA5 atmosphere with JRA55-do radiation introduces a forcing mismatch the ocean would adjust away from over weeks of integration.
- Higher native resolution than JRA55. 0.25° vs. 1.25° — the downscaling factor to 2–3 km is ~10× rather than ~50×, and coastal/orographic gradients are better preserved at the input.
- Hourly output vs. JRA55's 3-hourly. Matters for diurnal-cycle fidelity in the SW driver — relevant for boundary-layer evolution, sea-breeze onset, and daytime heating of the upper ocean mixed layer.
Proposed API
Mirror the JRA55 template at src/DataWrangling/JRA55/JRA55_prescribed_radiation.jl:
ERA5PrescribedRadiation(architecture = CPU(), FT = Float32;
dataset = ERA5Hourly(),
bounding_box = nothing,
start_date = first_date(dataset, :downwelling_shortwave_radiation),
end_date = last_date(dataset, :downwelling_shortwave_radiation),
time_indexing = Cyclical(),
ocean_surface = SurfaceRadiationProperties(0.05, 0.97),
sea_ice_surface = SurfaceRadiationProperties(0.7, 1.0),
stefan_boltzmann_constant = default_stefan_boltzmann_constant,
kw...)
Returns a PrescribedRadiation backed by ERA5 SW/LW FieldTimeSeries — interchangeable with the other *PrescribedRadiation constructors at the EarthSystemModel API level.
Implementation notes
Three things that go beyond a straight copy of the JRA55 template:
-
Unit conversion: J/m² → W/m². ERA5 stores ssrd/strd as accumulated energy (J/m²) over the previous hour, not instantaneous flux. Need to divide by 3600 to get W/m². Add a unit-conversion type (e.g., InverseAccumulationInterval(3600)) following the pattern of the existing Kelvin, InverseGravity, etc. in src/DataWrangling/metadata.jl, and a corresponding convert_units dispatch in src/DataWrangling/metadata_field.jl. Wire it via conversion_units(::ERA5RadiationMetadata).
-
Bounding-box plumbing. Regional hindcast users almost never want global ERA5 radiation — they want a domain-bounded subset matching their model footprint. The BoundingBox(longitude=…, latitude=…) machinery is the right entry point; the constructor should forward bounding_box to both the CDS download request and the FieldTimeSeries allocation.
-
CDS download dispatch. ext/NumericalEarthCDSAPIExt.jl already handles surface-data requests via reanalysis-era5-single-levels; adding the two radiation variable names to the existing ERA5_dataset_variable_names and verifying the request handler covers them should suffice — they're surface variables, not pressure-level, so no new request structure is needed.
File layout
src/DataWrangling/ERA5/ERA5_prescribed_radiation.jl — the constructor, mirroring JRA55_prescribed_radiation.jl
- Include from
src/DataWrangling/ERA5/ERA5.jl
- Export from the ERA5 submodule and re-export from
src/NumericalEarth.jl
Acceptance criteria
Related
Out of scope
- CERES-based bias correction of ERA5 radiation (a separate, larger effort)
- Operational / near-real-time ERA5 access for forecasting (the operational ECMWF IFS would be the right product for that, not ERA5)
- ERA5-Land radiation variants
Motivation
#200 introduces three
*PrescribedRadiationconstructors (JRA55PrescribedRadiation,ECCOPrescribedRadiation,OSPapaPrescribedRadiation) covering MIP-protocol global ocean spin-ups, ECCO state-estimate-driven runs, and single-column observational forcing respectively. Missing from the family is the natural radiation analogue for regional hindcast forcing at mesoscale resolution.The data plumbing is already in place:
src/DataWrangling/ERA5/ERA5.jl:87–88, 117–118already maps:downwelling_shortwave_radiation → "surface_solar_radiation_downwards"(ssrd) and:downwelling_longwave_radiation → "surface_thermal_radiation_downwards"(strd) for both the CDS API and the NetCDF short names. What's missing is the convenience constructor that wires those up into aPrescribedRadiation.Target use case
Regional / mesoscale hindcast simulations (typically ~O(1) km grids) where the user is replaying a specific past period with a regional ocean model initialized from GLORYS and forced by ERA5 atmosphere. This is a different regime from the existing
*PrescribedRadiationconstructors:Why ERA5 specifically for this regime
Proposed API
Mirror the JRA55 template at
src/DataWrangling/JRA55/JRA55_prescribed_radiation.jl:Returns a
PrescribedRadiationbacked by ERA5 SW/LWFieldTimeSeries— interchangeable with the other*PrescribedRadiationconstructors at theEarthSystemModelAPI level.Implementation notes
Three things that go beyond a straight copy of the JRA55 template:
Unit conversion: J/m² → W/m². ERA5 stores
ssrd/strdas accumulated energy (J/m²) over the previous hour, not instantaneous flux. Need to divide by 3600 to get W/m². Add a unit-conversion type (e.g.,InverseAccumulationInterval(3600)) following the pattern of the existingKelvin,InverseGravity, etc. insrc/DataWrangling/metadata.jl, and a correspondingconvert_unitsdispatch insrc/DataWrangling/metadata_field.jl. Wire it viaconversion_units(::ERA5RadiationMetadata).Bounding-box plumbing. Regional hindcast users almost never want global ERA5 radiation — they want a domain-bounded subset matching their model footprint. The
BoundingBox(longitude=…, latitude=…)machinery is the right entry point; the constructor should forwardbounding_boxto both the CDS download request and theFieldTimeSeriesallocation.CDS download dispatch.
ext/NumericalEarthCDSAPIExt.jlalready handles surface-data requests viareanalysis-era5-single-levels; adding the two radiation variable names to the existingERA5_dataset_variable_namesand verifying the request handler covers them should suffice — they're surface variables, not pressure-level, so no new request structure is needed.File layout
src/DataWrangling/ERA5/ERA5_prescribed_radiation.jl— the constructor, mirroringJRA55_prescribed_radiation.jlsrc/DataWrangling/ERA5/ERA5.jlsrc/NumericalEarth.jlAcceptance criteria
ERA5PrescribedRadiation(arch; bounding_box, start_date, end_date)returns a workingPrescribedRadiationwhosetime_step!updates the SW/LW fields correctlyOceanOnlyModel(ocean; atmosphere, radiation)whereatmosphereis anERA5PrescribedAtmosphere(when that exists) or aJRA55PrescribedAtmosphere(today), exercising the new top-level radiation slot from (0.4.0) Add radiation as a top-level EarthSystemModel component #200test/test_radiations.jl(or a newtest/test_era5_radiation.jl) using a smallbounding_boxto cap CDS download cost in CIRelated
*PrescribedRadiationpattern this slots into)BoundingBoxplumbing already oneq/era5_docsERA5PrescribedAtmospherefollow-up (regional ERA5 atmospheric forcing has the same plumbing rationale; not in scope here)Out of scope