Skip to content

Package changes for the SpeedyWeather coupling (extension lives in SpeedyWeather) - #16

Merged
maximilian-gelbrecht merged 36 commits into
mainfrom
mg/speedy-update
Oct 1, 2026
Merged

maximilian-gelbrecht merged 36 commits into
mainfrom
mg/speedy-update

Conversation

@maximilian-gelbrecht

@maximilian-gelbrecht maximilian-gelbrecht commented Sep 11, 2026 •

Copy link
Copy Markdown
Collaborator

Package-side changes for coupling NumericalRadiation to SpeedyWeather.jl. The coupling code itself lives in SpeedyWeather as the package extension SpeedyWeatherNumericalRadiationExt (SpeedyWeather branch mg/numericalradiation-extension, on top of the Radiation bundle of SpeedyWeather/SpeedyWeather.jl#1252); this PR makes the package usable from there and keeps the full tests and validation of the coupling here. It collapses the former stack #16, #17, #19 into one PR.

Tests

  • test/test_ecckd_radiation.jl (the scheme type), test/test_host_interface.jl (host-style construction of ColumnAtmosphere from views, zero-allocation shortwave call with a caller-owned scratch, two-stream singularity scan).
  • test/speedyweather/: the full coupling tests against SpeedyWeather's extension (test_speedyweather_analytic_band_longwave.jl, test_speedyweather_clear_sky_ecckd.jl), in an environment of their own (SpeedyWeather ≥ 0.23 from its branch, Julia ≥ 1.11) with a dedicated CI job; the core suite runs on Julia 1.10 and 1.13.

Example and validation: examples/speedyweather_ecckd.jl (one-band vs ecCKD budgets), validation/speedyweather_ecckd_budget.jl, speedyweather_ecckd_benchmark.jl, speedyweather_nan_detector.jl; results and design in docs/plans/ecckd_speedyweather.md, speedyweather_upstream.md, extension_upstream.md.

Merged with main as of 9c1e494; main's fix of the shortwave two-stream pole (54b6a75) supersedes the guard this work had added for the same NaN.


Changes to src/ compared to main, and why

Companion to ecckd_speedyweather.md and
extension_upstream.md. The guiding rule for the
SpeedyWeather coupling is to keep changes to the package's core small and to
prefer the host side (SpeedyWeather's SpeedyWeatherNumericalRadiationExt, where
the coupling code lives) whenever that is possible without giving up
performance. This section lists every change to src/ that PR #16 makes relative
to main (as of 9c1e494, 2026-09-29), what forced it, what the alternative
would have been, and how it is tested. Nothing here changes numerical results
of existing code paths; every change is a type-parameter relaxation, an optional
argument, or an addition.

# Change File Lines Results changed
1 AtmosphereProfile: one array-type parameter per vector src/column_views.jl ~10 no
2 ColumnAtmosphere: one array-type parameter per array src/runtime_interfaces.jl ~12 no
3 Optional caller-owned ShortwaveColumnScratch argument of radiative_fluxes!(…, CloudlessShortwave(), …) src/solvers/cloudless_shortwave.jl ~8 no
4 ClearSkyEcCKDRadiation and default_ozone_profile: the configured clear-sky ecCKD column scheme src/ecckd_radiation.jl (new), src/NumericalRadiation.jl ~105 no (addition)
5 AnalyticBandLongwave: ocean_emissivity and land_emissivity fields (default 1), closes #9 src/longwave/williams_longwave.jl ~8 no

Three further changes that this work carried at some point were superseded by
main and are no longer part of the diff; they are listed at the end.

1. AtmosphereProfile{NF, VT, VQ, VG} (was {NF, V})

File: src/column_views.jl.

What forced it. The analytic-band adapter builds an AtmosphereProfile from
views into SpeedyWeather arrays. Since SpeedyWeather 0.22 the temperature and
humidity views come from stepped three-dimensional arrays
(get_prognostic_step(vars.grid.temperature, ...)) while the geopotential view
comes from a plain two-dimensional field. Their SubArray types differ, and
the old struct forced all three vectors to share one type V; construction
threw a MethodError in convert.

Alternative considered. Copying the geopotential into a scratch view of the
same shape as the temperature step view. That costs a copy per column per
step for a type-system artefact, and the scratch array would have to mirror
the time-step layout of the prognostic arrays.

Why acceptable. Only the type parameters changed; the constructor converts
surface_pressure, rain_rate and CO₂ to NF as before. No code in the
package dispatched on the second parameter (radiative_transfer_column.jl and
williams_longwave.jl use AtmosphereProfile{NF}).

Tests. The coupling test test/test_speedyweather_analytic_band_longwave.jl
constructs the profile from real SpeedyWeather views (through SpeedyWeather's
extension); the solver suite is unchanged.

2. ColumnAtmosphere{FT, PL, PI, TL, TI, G, S, Geo, C} (was {FT, A, G, S, Geo, C})

File: src/runtime_interfaces.jl.

What forced it. The same problem for the staged interface: layer
temperature is a view into a stepped prognostic array, layer pressure a view
into a (npoints, nlayers) work array, interface quantities views into
(npoints, nlayers + 1) work arrays. Three different SubArray types where
the struct demanded one.

Alternative considered. Copying the layer temperature into a work array
every column. Same objection as in 1.

Why acceptable. heating_rates!, optical_properties! and the solvers read
the arrays element-wise and convert to their working precision; none of them
dispatched on the array type. The docstring states that FT is the element
type of temperature_layers.

Tests. test/test_host_interface.jl, "ColumnAtmosphere from differently
shaped host views": optics, fluxes and heating rates from views of a 2D/3D host
layout are identical to those from plain vectors.

3. Optional scratch argument of radiative_fluxes!(…, CloudlessShortwave(), …)

File: src/solvers/cloudless_shortwave.jl.

What forced it. The Rayleigh (scattering) path of the clear-sky shortwave
solver needs seven work vectors. Before main's refactor they were allocated
per g-point and per column (about 400 allocations per column per step for the
32-g-point model); main now allocates one ShortwaveColumnScratch per
radiative_fluxes! call, i.e. seven vectors per column per step. Inside
SpeedyWeather's fused column kernel that would be the only allocation in the
hot loop on CPU (and allocation is not possible inside a GPU kernel), and the
extension cannot avoid it without dropping Rayleigh scattering.

Alternative considered. This work originally carried its own
CloudlessShortwaveWorkspace type and a rewritten accumulation; at the first
merge of main that was dropped in favour of main's type.

What changed. radiative_fluxes! gained an optional last positional
argument scratch::ShortwaveColumnScratch, defaulting to the allocation
main already did. A host passes seven column views through the type's
positional constructor. No other line of the solver changed.

Tests. test/test_host_interface.jl, "CloudlessShortwave with a
caller-owned scratch": identical fluxes with and without the argument, zero
allocations with it, a scratch built from views.

4. ClearSkyEcCKDRadiation (new file src/ecckd_radiation.jl, one include)

What forced it. With the coupling moved into SpeedyWeather, the extension there
adds methods to this package's scheme types instead of defining wrapper types of
its own (AnalyticBandLongwave needed nothing). The ecCKD path had no scheme
type: EcCKDTabulatedGasOpticsModel is a coefficient table, and the coupling
needs configuration with no other home, the mole fractions of the gases a host
does not carry (ozone, further gases) and the surface emissivity, implicitly
also the solver pair. ClearSkyEcCKDRadiation bundles gas optics,
mole_fractions (numbers or functions of pressure, o3 and co2 defaults,
every further gas of the model required) and surface_emissivity; constructors
from a tabulated model, from a number format and reference model pair, and a
converting {FT} form; default_ozone_profile moved here from the extension.

Alternative considered. Keeping a SpeedyWeather-side type (needs an exported
type, an ArgumentError stub and a src file in SpeedyWeather for what is
NumericalRadiation configuration), or extending the coefficient table directly
with hard-coded defaults (no user configuration, and a type change once clouds
or emissivities become configurable).

Why acceptable. Purely additive, host-neutral (Breeze needs the same
configuration), no existing code path changes. The SpeedyWeather extension
imports the name at load time, so it has to be part of this PR.

Tests. test/test_ecckd_radiation.jl (construction, defaults, conversion,
missing-gas error); test/test_speedyweather_clear_sky_ecckd.jl and SpeedyWeather's
test/parameterizations/numericalradiation.jl use it end to end.

5. ocean_emissivity and land_emissivity of AnalyticBandLongwave

File: src/longwave/williams_longwave.jl.

What forced it. #9: through SpeedyWeather there was no way to set the surface emissivities, which the solver reads from the SurfaceState a host builds per column (default 1). SpeedyWeather has no surface emissivity of its own (its one-band longwave keeps them as scheme parameters), and the scheme type is NumericalRadiation's, so the only place a user-chosen value can live is the scheme; the extension passes them into the surface state: AnalyticBandLongwave(spectral_grid; ocean_emissivity = 0.98, land_emissivity = 0.97).

Alternative considered. A SpeedyWeather-side wrapper type (dropped for both schemes), or per-grid-point emissivity fields declared by the extension, which have no initialization hook for radiation components in SpeedyWeather.

Why acceptable. Two fields with default 1, so nothing changes unless set; the same pattern as ClearSkyEcCKDRadiation.surface_emissivity. Hosts with their own surface emissivity keep passing it into SurfaceState and ignore the fields.

Tests. test/test_speedyweather_analytic_band_longwave.jl, "Ocean and land emissivity": the scheme's values reach the surface state, the ocean and land upward fluxes scale with them, the OLR drops.

Superseded by main during the merges

Written here first, then dropped when main provided the same:

  • surface_longwave_emission! (in place). main's streaming column API
    (PR Kernel-facing column API and line-by-line validation for the Breeze coupling #20, Breeze coupling) added TabulatedSurfaceEmission(model, T; emissivity),
    a lazy AbstractVector whose getindex(g) evaluates the source table for one
    g point; a host passes it as surface_longwave_up, or blends two of them per g
    point as the SpeedyWeather extension does for ocean and land, without any
    allocation.
  • EcCKDTabulatedGasOpticsModel{FT}(model). main added the same method,
    converting every array through an Adapt storage adaptor, sharing arrays already
    in FT, and working on device arrays. The behavioural tests written here were kept.
  • Two-stream direct-beam singularity guard. The Meador-Weaver direct-beam terms
    divide by 1 - (λμ₀)²; the old guard detected μ₀ within 1000 ulps of the pole but
    nudged it by 10 ulps, which in Float32 could land exactly on the pole and produced
    NaN fluxes in a coupled SpeedyWeather run (found with
    validation/speedyweather_nan_detector.jl). This work moved μ₀ to the nearer edge
    of the band; main fixed the same pole independently (54b6a75, found in a Breeze
    Float32 run) by stepping to the lower edge with a branch-free ifelse. Same band,
    same escape size, so main's version stands; the singularity scan in
    test/test_host_interface.jl stays as the regression test.

Considered and deliberately not changed

  • @boundscheck around check_ecckd_optics_shapes. The checks are O(1)
    size comparisons, negligible on CPU, and @boundscheck would only remove
    them if optical_properties! were inlined into the @inbounds caller, which
    it is not. Their throw paths need a kernel-safe variant for GPU anyway;
    revisit with the first GPU run.
  • Splitting optical_properties! per stream. Unnecessary once SpeedyWeather
    bundles both streams into one component (upstream change U1): one call fills
    both streams, which is exactly what the component needs.
  • A (ng, nlayers)-major work-array layout accessor. A PermutedDimsArray
    of the (nlayers, ng) column slice gives the package's [ig, k] indexing at
    no cost in the package; only if that shows up in profiles is a change
    warranted.
  • Gas-amount and interface-temperature helpers. Host glue; they live in
    the extension (gas_amounts!, interface_temperatures!).
  • Broadcasts inside the solvers (fluxes.longwave_up .= 0 and the like).
    Fine on CPU; GPU kernels need scalar loops. Deferred to the first GPU run
    together with the bounds checks.

🤖 Generated with Claude Code

@maximilian-gelbrecht
maximilian-gelbrecht added this pull request to stack #18 September 11, 2026 14:51
maximilian-gelbrecht and others added 21 commits September 11, 2026 16:59
Version of docs/plans/src_changes_for_speedyweather.md restricted to the
changes on this branch; mg/ecckd-speedy carries the extended one.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Conflicts resolved by keeping this branch's SpeedyWeather 0.23 port of the
extension and its test (with main's identifier names and main's constants
helper, which passes the host's molar masses), keeping the per-vector array
types of AtmosphereProfile on top of main's renamed column_views.jl, and
combining the compat entries (RRTMGP from main, SpeedyWeather 0.23 and
Julia 1.11 from this branch). README keeps both the SpeedyWeather and the
Breeze sections.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
main meanwhile gained the streaming column API (PR #20). Its
TabulatedSurfaceEmission and its Adapt-based EcCKDTabulatedGasOpticsModel{FT}
converter supersede this branch's surface_longwave_emission! and constructor
based converter, so main's ecckd_forward.jl and exports are taken as is.
ColumnAtmosphere keeps this branch's per-array type parameters on top of
main's new constants field. The host-interface tests follow main's
water_vapor_* names and lose the in-place emission testset; the plan and the
src-changes justification record the decisions.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
main's streaming column API supersedes this branch's own additions: the
extension now blends two TabulatedSurfaceEmission vectors for ocean and land
instead of the dropped surface_longwave_emission!, passes main's
ShortwaveColumnScratch built from seven column views instead of the dropped
CloudlessShortwaveWorkspace, and takes gravity and the molar masses from the
host's PhysicalConstants through ColumnAtmosphere.constants. The package
change of this branch shrinks to an optional caller-owned scratch argument of
radiative_fluxes!(…, CloudlessShortwave(), …); the two-stream singularity
guard is re-applied to main's shortwave_reflectance_transmittance. Tests,
README, API docs, validation README and the plan documents follow.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…DME.md

The Phase 4 budget comparison, per-column benchmark and per-step NaN
detector had been left untracked.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Pass constants as a keyword (a bare name in a call without ';' is positional),
and give all seven shortwave scratch work arrays the interface length so their
column views share one type, as main's ShortwaveColumnScratch{V} requires; the
test cross-check had the same keyword slip.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… 1.10

SpeedyWeather >= 0.23 (the Radiation bundle) is not registered yet and is
pulled from its development branch through a [sources] entry, which Julia 1.10
ignores; with it in test/Project.toml the 1.10 CI jobs could not resolve the
test environment. The extension tests now live in test/speedyweather/ with
their own environment and a dedicated CI job on Julia 1.11, test/Project.toml
is registry-only again, runtests.jl skips the extension tests when
SpeedyWeather is absent, and the package's Julia compat goes back to 1.10.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Brings in the move of the SpeedyWeather coupling into SpeedyWeather's own
extension (SpeedyWeatherNumericalRadiationExt) and the new host-neutral
ClearSkyEcCKDRadiation scheme type; src_changes_for_speedyweather.md gains
the entry for that type.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The SpeedyWeather coupling now lives in SpeedyWeather's extension
SpeedyWeatherNumericalRadiationExt: the extension directory is removed here,
the ecCKD coupling tests, the example and the validation scripts use
ClearSkyEcCKDRadiation and AnalyticBandLongwave through SpeedyWeather (branch
mg/numericalradiation-extension), the README and the plan documents follow.
src_changes_for_speedyweather.md lists the new scheme type as change 8.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Apache 2.0 relicensing, README restyle, TagBot, CI on Julia 1.10 and 1.13,
and main's own fix of the shortwave two-stream pole (54b6a75).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
main's fix of the shortwave two-stream pole (54b6a75) replaces this branch's
guard for the same NaN; src_changes_for_speedyweather.md now justifies exactly
the src diff against main (four changes) and records the superseded ones.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@maximilian-gelbrecht
maximilian-gelbrecht removed this pull request from stack #18 September 24, 2026 14:23
@maximilian-gelbrecht maximilian-gelbrecht changed the title Update SpeedyWeather extension Package changes for the SpeedyWeather coupling (extension lives in SpeedyWeather) Sep 24, 2026
Codecov coverage upload and badge, docs install instructions for the
registered package.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…nges document

test_with_speedyweather.jl becomes test_speedyweather_analytic_band_longwave.jl
and test_speedyweather_clear_sky_ecckd.jl (one module each), included by both
test runners. src_changes_for_speedyweather.md: ColumnAtmosphere's constants
parameter in the heading, test file names, GPU wording.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@maximilian-gelbrecht

Copy link
Copy Markdown
Collaborator Author

… core matrix entry

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…n scripts and their README

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@maximilian-gelbrecht
maximilian-gelbrecht marked this pull request as ready for review September 29, 2026 13:54
@maximilian-gelbrecht

maximilian-gelbrecht commented Sep 29, 2026 •

Copy link
Copy Markdown
Collaborator Author

@glwagner This removes the SpeedyWeather extension and is a companion to a new extension in SpeedyWeather SpeedyWeather/SpeedyWeather.jl#1271

It needs to adjust a few (but not many) things here for it to work, each is justified in the original (edited) post separately.

Currently I would prefer to merge this in a quick and dirty way of letting this PR depend on the feature branch and vice versa, merge both and then remove feature branch dependencies in a very quick follow up and release new versions of both SpeedyWeather and NumericalRadiation. Let me know, if you want to do this in a more proper way.

The registered 0.1.0 predates this PR; SpeedyWeather's extension needs
ClearSkyEcCKDRadiation and the per-array types and sets its compat bound to 0.1.1.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Comment thread src/ecckd_radiation.jl Outdated
@glwagner

Copy link
Copy Markdown
Member

Currently I would prefer to merge this in a quick and dirty way of letting this PR depend on the feature branch and vice versa, merge both and then remove feature branch dependencies in a very quick follow up and release new versions of both SpeedyWeather and NumericalRadiation. Let me know, if you want to do this in a more proper way.

I'm fine with that. This code isn't properly evaluated yet.

@maximilian-gelbrecht
maximilian-gelbrecht merged commit 8c2ca07 into main Oct 1, 2026
9 checks passed
@maximilian-gelbrecht
maximilian-gelbrecht deleted the mg/speedy-update branch October 1, 2026 08:45
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.

Missing option for setting ocean and land emissivity using SpeedyWeather

2 participants