Skip to content

v0.22.0

Choose a tag to compare

@tapios tapios released this 14 Jul 16:48
· 39 commits to main since this release
  • Internal compute buffers now use device-dependent physical layouts behind a
    uniform column-first (ncol, nlay/nlev) indexing convention: optical
    properties (OneScalar, TwoStream), source functions (SourceLWNoScat,
    SourceLW2Str, SourceSW2Str), and the broadband flux buffers (FluxLW,
    FluxSW). On the GPU the storage is column-first, giving coalesced access
    (on an A100 at DYAMOND scale this cuts longwave kernel times by 25-36% and
    shortwave all-sky by 14-20%); on the CPU the storage stays vertical-first
    under a lazy PermutedDimsArray wrapper, preserving the stride-1 vertical
    sweeps (and CPU performance) while running the same kernel code.
    The Layer-2 flux getters (lw_flux_up, net_flux, ...) still present
    plain (nlev, ncol) views: they read Fluxes.FluxPresentation arrays that
    update_fluxes! fills from the compute buffers with one fused transposing
    copy per call (so getters remain Array-materializable, broadcastable, and
    reducible on the GPU). The opt-in per-band buffers (FluxBand) keep the
    host-facing (nlev, ncol, n_bnd) layout. Breaking for Layer-1
    (functional-core) users
    : code reading the workspace fields directly
    (slv.flux.flux_up, op.τ, ...) must swap index order to [gcol, ilev]
    or wrap the buffer in Fluxes.lazy_transpose. State inputs
    (AtmosphericState arrays) and all boundary-condition arrays are
    unchanged.
  • New TransposedStateCache: column-first copies of the hot
    AtmosphericState arrays (layer pressure/temperature/dry-air column
    amount/relative humidity, level temperatures), refreshed by one
    permutedims! per spectral solve, so the gas-optics g-point loop reads
    coalesced memory on GPUs. On by default on the GPU (the RTE workspace
    constructors build one; the RRTMGPSolver shares a single cache between
    the longwave and shortwave workspaces) and off (nothing) on the CPU,
    where the state's vertical-first layout is already cache-friendly; pass
    state_cache = nothing to opt out, or pass the same cache to several
    workspaces to share the storage.
  • Documentation reorganized along tutorial / how-to / explanation / reference
    lines: two new executable (Literate.jl) tutorials — "A first radiation
    calculation" and "Radiative-convective equilibrium", which reproduces
    Manabe's classic fixed-relative-humidity climate-sensitivity experiment with
    RRTMGP's clear-sky optics — plus four how-to guides (driving RRTMGP from a
    host model, running on GPUs, caching the lookup tables, per-band fluxes).
    The README now gives a Thermodynamics.jl-style overview with quick starts.
  • Struct docstrings migrated off DocStringExtensions.FIELDS to explicit
    # Fields sections (per the CliMA documentation policy), so field
    descriptions and units render on the API pages without inline field
    docstrings.
  • New unit tests for previously indirect paths: every interpolation /
    bottom-extrapolation scheme against closed forms (including a dry-adiabat
    round-trip through interpolate_levels!), solver-constructor and getter
    guard errors, validate_inputs field-by-field, the incident-longwave-flux
    boundary condition, pointwise deep-atmosphere flux scaling, heating-rate =
    net-flux divergence, gray clip!, aerosol-only LookupBundle round-trips,
    cloud-radiative-effect sign checks in the all-sky tests, and the McICA
    reproducibility tests now also run in Float32.
  • New advisory GPU benchmark ratchet (perf/benchmark_ratchet.jl + a
    soft-fail Buildkite step): the DYAMOND-scale solve_lw!/solve_sw! medians
    are compared against per-GPU baselines committed under
    perf/benchmark_baselines/, flagging wall-time regressions beyond 20%
    without blocking merges. The three DYAMOND benchmark scripts are now
    includable (their sweeps run only when executed as scripts).
  • New docs page "Fortran and paper concordance": tables mapping RRTMGP.jl
    names (containers, RTE/gas-optics/cloud/aerosol kernels) to the Fortran
    rte-rrtmgp mo_*/ty_* names and to the papers whose equations they
    implement, so Fortran-literate readers can navigate instantly.
  • The standalone (Layer-3) path is complete: standard_atmosphere(FT; kind)
    builds an idealized clear-sky AtmosphereProfile (:tropical,
    :midlatitude_summer, :subarctic_winter — analytic two-segment
    temperature, exact hydrostatic pressure, idealized water vapor/ozone,
    present-day well-mixed gases), and solve(profile; method) solves it in one
    call — ClearSkyRadiation (default) or GrayRadiation. Both solve and
    solve_gray now return a documented RadiationOutput struct with stable
    field names (lw_upsw_direct_dn, net, heating_rate, solver);
    solve_gray previously returned a NamedTuple with the same names, so
    field access is unchanged.
  • New prepare_atmosphere!(solver): runs the atmospheric-state preparation
    cascade (level interpolation, isothermal boundary layer, clipping, dry-air
    column amounts) without solving, so hosts can inspect the prepared state —
    the prepare/solve split. update_fluxes! is now literally
    prepare_atmosphere! followed by the three solve/combine steps.
  • Breaking: lookup_tables now returns a typed [LookupBundle] instead of
    a nested (; lookups, lu_kwargs) NamedTuple: stable fields
    (lookup_lw, lookup_sw, cloud/aerosol tables, the name→index maps, and the
    band/gas counts), nothing where a table is absent. Code that indexed the
    NamedTuple (e.g. bundle.lookups.lookup_lw, bundle.lu_kwargs.nbnd_lw) drops
    one level (bundle.lookup_lw, bundle.nbnd_lw).
  • New save_lookup_tables(path, bundle) / load_lookup_tables(path, grid_params)
    cache the lookup tables on disk (Julia Serialization; a same-version cache,
    not an interchange format), so the spectral methods can run without NCDatasets
    once a cache has been generated — e.g. for standalone/classroom use.
  • The CPU and CUDA solver drivers now share device-agnostic per-(g-point,
    column) bodies (*_gpt_col! in src/rte/), removing the duplicated
    orchestration where backend asymmetries repeatedly crept in. As part of the
    unification, the broadband shortwave direct beam sw_direct_flux_dn is now
    accumulated at every level: it was previously meaningful only at the
    surface row (a documented limitation inherited from PR #550), with the rows
    above holding the first g-point's profile on CPU and uninitialized memory on
    GPU.
  • Breaking (with aliases): renamed two modules for clarity — Vmrs
    VolumeMixingRatios and GrayUtilsGrayAtmosphere. The old names remain
    as const aliases for one release and will be removed in 0.24.
  • Breaking: renamed aerosol_idx()aerosol_index_map(), resolving the
    one-character collision with aerosol_index(name) (which returns a single
    index rather than the whole map).
  • Float32 accuracy overhaul of the RTE kernels: series-switch threshold for the
    longwave no-scattering source at eps^(1/4) (was 100·eps), exact γ1 − γ2
    identities in the two-stream k, expm1-based thin-layer factors, a consistent
    off-resonance evaluation of the shortwave direct reflectance/transmittance,
    exact non-cancelling delta-scaling forms, an addition-built direct-beam
    profile, a continuous gas-optics η interpolation at η = 1, and a restructured
    longwave two-stream source (exact 1 ∓ Rdif − Tdif factorizations, so no term
    divides by τ and thin layers keep their real O(τ) emission instead of being
    zeroed below a threshold). Measured Float32↔Float64 broadband agreement
    improves 25–90×: every longwave path now sits at its ~2–5e-4 W/m²
    interpolation-noise floor (from 1.1e-2–3.4e-2). A new ratcheting f32↔f64
    consistency test (test/float32_consistency.jl) locks the gains, and the
    Float32 longwave no-scattering reference tolerances tighten from 0.05 to
    5e-3 W/m².
  • New RRTMGP.Numerics module: every numerical guard constant (k_min,
    τ_thresh, resonance_window, μ₀_min) in one place with its derivation.
  • New opt-in input validation: set RRTMGP.check_values[] = true to have
    update_fluxes! validate solver inputs (pressures/temperatures positive and
    finite, cos_zenith ∈ [-1, 1], emissivity/albedos ∈ [0, 1], VMRs ≥ 0) via
    validate_inputs; off by default and allocation-free when off.
  • Lookup loaders now assert the canonical gas slots the kernels hard-code
    (h2o → 1, o3 → 3) and that temperature_Planck is in Kelvin (the g128 file
    variants store an integer index, which would silently corrupt the Planck
    interpolation).
  • Breaking: the public API is now centered on RRTMGPSolver plus a complete set of named
    getters (layer_temperature, level_pressure, net_flux, ...), built on the functional
    solve_lw!/solve_sw! core. Hosts exchange every input and output through the getters — a
    documented, ClimaCore-free data-exchange contract — and drive the solve with a single
    allocation-free, type-stable update_fluxes! that returns nothing (results are read back
    through the getters).
  • Breaking: removed the deprecated positional-argument constructors that warned to use
    RRTMGPGridParams: the old signatures of OneScalar, TwoStream, FluxLW, FluxSW,
    SourceLWNoScat, SourceLW2Str, SourceSW2Str, NoScatLWRTE, TwoStreamLWRTE,
    NoScatSWRTE, TwoStreamSWRTE, and VmrGM, plus source_func_longwave,
    source_func_shortwave, and init_vmr. Construct these with RRTMGPGridParams (and
    VolumeMixingRatioGlobalMean for VmrGM).
  • Breaking: RRTMGPGridParams now takes domain_nlay (the physical layer count) instead
    of nlay, and adds the isothermal boundary layer internally; the stored nlay field is the
    total. Callers no longer compute domain_nlay + 1 themselves.
  • Breaking: removed the internal RRTMGPData{Order} data-layout types and helpers
    (NVCData/VCData/NCData/NData, set_cols!/set_domain!/domain_view, and the
    index-order tags); the getter-based API does not use them.
  • Lifted the grid-adaptation helpers out of ClimaAtmos into RRTMGP as separable, in-place
    functions over plain (nlay, ncol) array views: interpolate_levels!,
    add_isothermal_boundary_layer!, clip!, and update_concentrations!.
  • clip! now also clamps the spectral solvers' layer/level temperatures into the
    valid range of the optics lookup tables — the tables' first and last reference
    temperatures (lookup.t_ref_min/t_ref_max, 160–355 K for the standard tables),
    read straight from the lookup table — moving the clamp that ClimaAtmos applied
    before every radiation call into RRTMGP's own input preparation. The gray path is
    deliberately not clamped: it uses no lookup tables, and idealized gray atmospheres
    can legitimately reach temperatures outside the lookup range.
  • Added standalone entry points that need no NetCDF lookup tables — solve_gray,
    default_parameters, and heating_rate (in K/s) — for single-column/classroom use.
  • Added optional spectrally-resolved (per-band) fluxes: construct with spectral_fluxes = true,
    then read spectral_{lw,sw}_flux_{up,dn,net} and the band edges {lw,sw}_band_bounds. Off by
    default, and the broadband path is byte-for-byte unchanged.
  • Added a canonical aerosol species name map: the aerosol getters take canonical names
    (sulfate, sea_salt1, black_carbon_rh, ...), and hosts map their own short names
    (so4, ss1, ...) to these.
  • Added deep_atmosphere_inverse_scaling (an RRTMGPSolver kwarg/getter): a (nlev, ncol)
    factor applied to the fluxes for deep-atmosphere geometry, where the host passes the
    multiplicative inverse of its metric scaling (the low-level metric_scaling argument is
    unchanged).
  • RRTMGPSolver and its optics/source workspaces are now Adapt-able via custom overloads
    that preserve the packed-buffer view topology across device transfers, so checkpoint restores
    stay zero-allocation.
  • Fixed VolumeMixingRatioGlobalMean, which threw an UndefVarError on every call.
  • New documentation: "The functional core" and "The getter contract" pages.

Migration

RRTMGPGridParams now takes the physical domain_nlay and adds the isothermal boundary layer
itself, so host adapters (ClimaAtmos/ClimaCoupler) no longer compute the total layer count:

- nlay = domain_nlay + Int(add_isothermal_boundary_layer)
- gp = RRTMGPGridParams(FT; context, nlay, ncol, isothermal_boundary_layer)
+ gp = RRTMGPGridParams(FT; context, domain_nlay, ncol, isothermal_boundary_layer)
+ nlay = gp.nlay        # total, if you still need it (e.g. for state allocation)

Results are read through the getters after the solve: update_fluxes!(solver, seed) returns
nothing, so use net_flux(solver) (a domain-masked view) or the raw solver.net_flux_buffer.