From eaa560af166b00718d6da37d4848e5563c0273fa Mon Sep 17 00:00:00 2001 From: Eliot Quon Date: Thu, 17 Sep 2026 15:03:33 -0600 Subject: [PATCH 1/7] Anchor the coupled cold start on the reference, not the datum MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `atmosphere_model` passed its `surface_pressure` keyword — the scalar it also hands to `CompressibleDynamics` as `base_pressure`, the reference atmosphere's pressure at z = 0 — into `HydrostaticallyBalancedDensity`, whose keyword is a per-column ground-pressure override applied to every column. Since Breeze#893 those are different quantities: the reference carries the datum reduced hydrostatically to each column's bottom face, and `HydrostaticallyBalancedDensity()` anchors on that by default so the cold start agrees with the reference it is differenced against. On the flat z = 0 grids the coupled examples use, the two anchors are equal exactly (`columnwise_surface_pressure_field` returns the datum when the bottom face is at z = 0), so this is a no-op there. On a raised or terrain-following grid the override re-creates the O(ρgh) mismatch between state and reference that #893 fixed. It is also wrong whenever a caller passes its own `dynamics` with a different `base_pressure`: the density was then anchored at the keyword's value rather than the reference's. Co-Authored-By: Claude Opus 5 (1M context) --- ext/NumericalEarthBreezeExt/breeze_atmosphere_simulation.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/NumericalEarthBreezeExt/breeze_atmosphere_simulation.jl b/ext/NumericalEarthBreezeExt/breeze_atmosphere_simulation.jl index 397ddce08..923f71fb7 100644 --- a/ext/NumericalEarthBreezeExt/breeze_atmosphere_simulation.jl +++ b/ext/NumericalEarthBreezeExt/breeze_atmosphere_simulation.jl @@ -150,7 +150,7 @@ function NumericalEarth.Atmospheres.atmosphere_model(grid; # applying this compressible-only set! corrupts it in place (Breeze's set_to_mean.jl copies # between mismatched-shape Fields), NaN-ing every field that divides by density. initialize && dynamics isa CompressibleDynamics && - set!(model; θ = potential_temperature, ρ = HydrostaticallyBalancedDensity(; surface_pressure)) + set!(model; θ = potential_temperature, ρ = HydrostaticallyBalancedDensity()) return model end From be521abbd727d1e6709011b31ac23f29a5a7af7c Mon Sep 17 00:00:00 2001 From: Eliot Quon Date: Thu, 17 Sep 2026 15:04:06 -0600 Subject: [PATCH 2/7] Rename the reference-datum keyword `surface_pressure` to `base_pressure` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since Breeze#893, `surface_pressure` in Breeze is the pressure at the ground — a 2D field, per column on terrain — and the scalar pressure of the reference atmosphere at z = 0 is `base_pressure`. NumericalEarth's keyword forwards to the latter; #689 translated it at the `CompressibleDynamics` call and kept the old spelling to limit the blast radius. That left NumericalEarth with the name #893 rejected on the quantity it rejected it for: over terrain the two differ by O(ρgh), and the nested default is the domain-mean ERA5 mean sea level pressure, not the surface pressure. `base_pressure` rather than `sea_level_pressure` because the keyword is a pure pass-through, so the reader meets the same word in Breeze's docstrings and in its rejection message for the old spelling; and because `:sea_level_pressure` in NumericalEarth already names a time-varying 2D dataset field, not a scalar datum. Untouched, because they are the pressure at the ground and so correctly named: ERA5 `:surface_pressure` (`sp`), and `hydrostatic_pressure_from_surface(T, surface_pressure, orography)`. Co-Authored-By: Claude Opus 5 (1M context) --- .../breeze_atmosphere_interface.jl | 2 +- .../breeze_atmosphere_simulation.jl | 10 ++++----- .../breeze_nested_atmosphere.jl | 22 +++++++++---------- test/test_nested_simulation.jl | 2 +- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/ext/NumericalEarthBreezeExt/breeze_atmosphere_interface.jl b/ext/NumericalEarthBreezeExt/breeze_atmosphere_interface.jl index db692a81f..360748ed5 100644 --- a/ext/NumericalEarthBreezeExt/breeze_atmosphere_interface.jl +++ b/ext/NumericalEarthBreezeExt/breeze_atmosphere_interface.jl @@ -127,7 +127,7 @@ function NumericalEarth.EarthSystemModels.interpolate_state!(exchanger, exchange qᵛ = specific_humidity(atmosphere) # Breeze's diagnosed vapor mass fraction, not the scheme-dependent moisture prognostic; - # `dynamics_pressure` gives the per-column pressure (a single scalar `surface_pressure` + # `dynamics_pressure` gives the per-column pressure (a single scalar `base_pressure` # would bias fluxes over terrain). p = dynamics_pressure(atmosphere.dynamics) diff --git a/ext/NumericalEarthBreezeExt/breeze_atmosphere_simulation.jl b/ext/NumericalEarthBreezeExt/breeze_atmosphere_simulation.jl index 923f71fb7..7e0d12c10 100644 --- a/ext/NumericalEarthBreezeExt/breeze_atmosphere_simulation.jl +++ b/ext/NumericalEarthBreezeExt/breeze_atmosphere_simulation.jl @@ -40,10 +40,10 @@ end """ atmosphere_model(grid; - surface_pressure = 101325, + base_pressure = 101325, potential_temperature = 285, thermodynamic_constants = ThermodynamicConstants(eltype(grid)), - dynamics = CompressibleDynamics(; base_pressure = surface_pressure, + dynamics = CompressibleDynamics(; base_pressure, reference_potential_temperature = potential_temperature), microphysics = SaturationAdjustment(equilibrium = WarmPhaseEquilibrium()), momentum_advection = WENO(order=9), @@ -61,7 +61,7 @@ the role of [`ocean_simulation`](@ref)). When `initialize` (the default) and `dynamics isa CompressibleDynamics`, the returned model is set to a resting, hydrostatically balanced state at the reference `potential_temperature` and -`surface_pressure`, so its density is valid for anything that divides by it (e.g. the MOST +`base_pressure`, so its density is valid for anything that divides by it (e.g. the MOST surface-flux coupling). `AnelasticDynamics` needs no such step: its `reference_state` already prescribes a valid resting density, and zero prognostic perturbation is by construction the resting hydrostatically balanced state. Pass `initialize = false` when a caller derives the full @@ -86,10 +86,10 @@ extension), which derives the lateral BCs and Davies relaxation from the parent in a `NestedModel`. """ function NumericalEarth.Atmospheres.atmosphere_model(grid; - surface_pressure = 101325, + base_pressure = 101325, potential_temperature = 285, thermodynamic_constants = ThermodynamicConstants(eltype(grid)), - dynamics = CompressibleDynamics(; base_pressure = surface_pressure, + dynamics = CompressibleDynamics(; base_pressure, reference_potential_temperature = potential_temperature), microphysics = SaturationAdjustment(equilibrium = WarmPhaseEquilibrium()), momentum_advection = Oceananigans.WENO(order=9), diff --git a/ext/NumericalEarthBreezeExt/breeze_nested_atmosphere.jl b/ext/NumericalEarthBreezeExt/breeze_nested_atmosphere.jl index ad00f4765..6eba5f961 100644 --- a/ext/NumericalEarthBreezeExt/breeze_nested_atmosphere.jl +++ b/ext/NumericalEarthBreezeExt/breeze_nested_atmosphere.jl @@ -119,13 +119,13 @@ default_lid_depth(grid) = convert(eltype(grid), grid.Lz / 4) # Default child dynamics: compressible with split-explicit acoustic substepping, an `UpperSponge` # Rayleigh layer over the top `damping_depth` meters at `damping_rate`, and no divergence damping # (its (ρθ)′-proxy damper injects a spurious force on an unbalanced cold start). When given, -# `surface_pressure`/`reference_potential_temperature` anchor the hydrostatic reference and the +# `base_pressure`/`reference_potential_temperature` anchor the hydrostatic reference and the # perturbation-form pressure-gradient reference profile. -function default_nested_dynamics(grid; surface_pressure, reference_potential_temperature, damping_rate, damping_depth) +function default_nested_dynamics(grid; base_pressure, reference_potential_temperature, damping_rate, damping_depth) time_discretization = SplitExplicitTimeDiscretization(sponge = UpperSponge(; damping_rate, depth = damping_depth), damping = NoDivergenceDamping()) kw = (;) - isnothing(surface_pressure) || (kw = merge(kw, (; base_pressure = surface_pressure))) + isnothing(base_pressure) || (kw = merge(kw, (; base_pressure))) isnothing(reference_potential_temperature) || (kw = merge(kw, (; reference_potential_temperature))) return CompressibleDynamics(time_discretization; kw...) end @@ -192,7 +192,7 @@ Provides sensible, overridable physics defaults: `microphysics` (1-moment mixed- `CloudMicrophysics` is loaded), `momentum_advection = WENO(order=9)`, `coriolis = SphericalCoriolis()`, and a compressible split-explicit `dynamics` with an `UpperSponge` over the top `damping_depth` m at `damping_rate`; a matching ρw Rayleigh lid sponge (`Relaxation` toward zero) is added to `forcing`. Pass -`surface_pressure`/`reference_potential_temperature` to anchor the default dynamics. Any +`base_pressure`/`reference_potential_temperature` to anchor the default dynamics. Any `boundary_conditions`/`forcing` the caller passes are merged with the parent-derived ones (caller wins). When `bottom_drag_coefficient` is given — a constant drag coefficient or a `Breeze.PolynomialCoefficient` @@ -221,7 +221,7 @@ function NumericalEarth.NestedModels.nested_atmosphere_model(parent_atmosphere:: relaxation_mask = davies_relaxation_mask(child_grid, relaxation_width), sides = (:west, :east, :south, :north), thermodynamic_constants = ThermodynamicConstants(eltype(child_grid)), - surface_pressure = nothing, + base_pressure = nothing, reference_potential_temperature = nothing, terrain = nothing, terrain_blend_length = 60_000, # meters; physical blend width → resolution-invariant slope @@ -236,7 +236,7 @@ function NumericalEarth.NestedModels.nested_atmosphere_model(parent_atmosphere:: coriolis = SphericalCoriolis(), damping_rate = 1/5, damping_depth = default_lid_depth(child_grid), - dynamics = default_nested_dynamics(child_grid; surface_pressure, reference_potential_temperature, damping_rate, damping_depth), + dynamics = default_nested_dynamics(child_grid; base_pressure, reference_potential_temperature, damping_rate, damping_depth), boundary_conditions = NamedTuple(), forcing = NamedTuple(), kw...) @@ -368,7 +368,7 @@ Build the parent `PrescribedAtmosphere`, nest a Breeze child in it, and initiali `parent_dataset` at `first(dates)` — the returned model is ready to step. The parent spans `child_grid`'s bounding box padded by `parent_padding` (default `parent_dataset`'s `default_horizontal_padding`, margin for the lateral-BC interpolation stencils) at `dates`, on -`parent_dataset`'s native grid. Unless given, the default dynamics' `surface_pressure` anchor is the domain-mean dataset +`parent_dataset`'s native grid. Unless given, the default dynamics' `base_pressure` anchor is the domain-mean dataset mean-sea-level pressure over the child at `first(dates)`. When `bottom_drag_coefficient` is given, `drag_surface_temperature` defaults to the dataset's skin temperature at `first(dates)` regridded onto the child grid (a static snapshot, not the dataset's diurnal cycle). `balancer` controls the @@ -380,7 +380,7 @@ function NumericalEarth.NestedModels.nested_atmosphere_model(child_grid, parent_ dir = default_download_directory(parent_dataset), parent_padding = default_horizontal_padding(parent_dataset), parent_time_indices_in_memory = nothing, # nothing ⇒ every date resident; ≥3 streams a moving window - surface_pressure = nothing, + base_pressure = nothing, bottom_drag_coefficient = nothing, drag_surface_temperature = nothing, balancer = true, @@ -391,15 +391,15 @@ function NumericalEarth.NestedModels.nested_atmosphere_model(child_grid, parent_ architecture = architecture(child_grid), dir, time_indices_in_memory = parent_time_indices_in_memory) - if isnothing(surface_pressure) - surface_pressure = mean_sea_level_pressure(parent_dataset, child_grid, first(dates), dir) + if isnothing(base_pressure) + base_pressure = mean_sea_level_pressure(parent_dataset, child_grid, first(dates), dir) end if !isnothing(bottom_drag_coefficient) && isnothing(drag_surface_temperature) drag_surface_temperature = dataset_skin_temperature(parent_dataset, child_grid, first(dates), dir) end - nested_model = NumericalEarth.NestedModels.nested_atmosphere_model(parent_atmosphere, child_grid; surface_pressure, + nested_model = NumericalEarth.NestedModels.nested_atmosphere_model(parent_atmosphere, child_grid; base_pressure, bottom_drag_coefficient, drag_surface_temperature, kw...) initialize_nested_child!(nested_model, parent_dataset, first(dates), dir; balancer) return nested_model diff --git a/test/test_nested_simulation.jl b/test/test_nested_simulation.jl index 16f951d70..4cabdb1f5 100644 --- a/test/test_nested_simulation.jl +++ b/test/test_nested_simulation.jl @@ -663,7 +663,7 @@ end # Default microphysics: with CloudMicrophysics unloaded (as in the test env) this is the Breeze-native # `SaturationAdjustment(WarmPhaseEquilibrium())` — no extra dependency needed to exercise the seam. model = nested_atmosphere_model(parent, child_grid; - relaxation_rate = 1/300, relaxation_width = 3, surface_pressure = 1e5, + relaxation_rate = 1/300, relaxation_width = 3, base_pressure = 1e5, coriolis = nothing, terrain = nothing, parent_condensates = nothing) ext.initialize_nested_child!(model, nothing, first(times), ""; balancer = false) From 7ab91b6273a35eb35f80ac2f3117356e9967343d Mon Sep 17 00:00:00 2001 From: Eliot Quon Date: Tue, 22 Sep 2026 09:19:33 -0600 Subject: [PATCH 3/7] =?UTF-8?q?Name=20the=20energy=20coupling=20flux=20?= =?UTF-8?q?=E1=B4=B1,=20not=20=E1=B5=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The field the coupler writes sensible heat into is called `Jᵉ`, and `net_fluxes` returns it under the key `ρe`. Breeze reserves `e` for turbulent kinetic energy and uses `E` for total energy: notation.md:52 ("`e` is reserved for turbulent kinetic energy"), :54 (`E` is total energy and the formulation-agnostic energy key), :167 (`e` is subgrid TKE). So the spelling says TKE where the quantity is enthalpy. Nothing was misrouted — the boundary condition is installed under `energy_bc_key()`, which #689 already pointed at `total_energy_density_name` (`:ρE`). But `ρe` is now a live prognostic whenever the TKE closure is on, which a downstream nested configuration enables by default, so `net.ρe = Qc` sits twenty lines from a genuine `ρe` tracer meaning something else. That is the collision class of Breeze#956, where the `e`→`s` rename left stale keys silently accepted. Internal, so patch-releasable: `Jᵉ` is a local binding, and `ρe` is a NamedTuple key with one producer (`net_fluxes(::BreezeAtmosphere)`) and one consumer (`_assemble_net_atmosphere_fluxes!`) in the same file. `net_fluxes` is not exported from NumericalEarth, and there is no cross-component schema to honor — ocean, sea ice and Veros each return their own shape or `nothing`. No test asserts the key, and no known downstream caller references it. The four `Jᵉ` in `dry_layer_humidity.jl` are a different quantity — a vapor flux where the superscript marks the evaporating surface — and are deliberately untouched. Do not do this rename with a global substitution. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01D2fuRfqFoMFjvYyx3idQtQ --- ext/NumericalEarthBreezeExt/breeze_atmosphere_interface.jl | 6 +++--- ext/NumericalEarthBreezeExt/breeze_atmosphere_simulation.jl | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ext/NumericalEarthBreezeExt/breeze_atmosphere_interface.jl b/ext/NumericalEarthBreezeExt/breeze_atmosphere_interface.jl index 360748ed5..21492bbc3 100644 --- a/ext/NumericalEarthBreezeExt/breeze_atmosphere_interface.jl +++ b/ext/NumericalEarthBreezeExt/breeze_atmosphere_interface.jl @@ -160,12 +160,12 @@ function NumericalEarth.EarthSystemModels.InterfaceComputations.net_fluxes(atmos "flux field, so its surface stress cannot come from the coupler. Build the atmosphere " * "without `bottom_drag_coefficient` (Breeze `BulkDrag`) when coupling to land or ocean.")) - ρe = thermodynamic_density(atmosphere.formulation).boundary_conditions.bottom.condition.condition + ρE = thermodynamic_density(atmosphere.formulation).boundary_conditions.bottom.condition.condition # Moisture flux field ρqᵛᵉ = atmosphere.moisture_density.boundary_conditions.bottom.condition - return (; ρu, ρv, ρe, ρqᵛᵉ) + return (; ρu, ρv, ρE, ρqᵛᵉ) end NumericalEarth.EarthSystemModels.InterfaceComputations.net_fluxes(atmos::BreezeAtmosphereSim) = @@ -184,7 +184,7 @@ NumericalEarth.EarthSystemModels.InterfaceComputations.net_fluxes(atmos::BreezeA # interpolate stresses on variable's location net.ρu[i, j, 1] = ℑxᶠᵃᵃ(i, j, 1, grid, ao_fluxes.x_momentum) net.ρv[i, j, 1] = ℑyᵃᶠᵃ(i, j, 1, grid, ao_fluxes.y_momentum) - net.ρe[i, j, 1] = Qc # sensible heat only; latent heat handled by moisture flux + net.ρE[i, j, 1] = Qc # sensible heat only; latent heat handled by moisture flux net.ρqᵛᵉ[i, j, 1] = Fv end end diff --git a/ext/NumericalEarthBreezeExt/breeze_atmosphere_simulation.jl b/ext/NumericalEarthBreezeExt/breeze_atmosphere_simulation.jl index 7e0d12c10..1ea88eb38 100644 --- a/ext/NumericalEarthBreezeExt/breeze_atmosphere_simulation.jl +++ b/ext/NumericalEarthBreezeExt/breeze_atmosphere_simulation.jl @@ -111,12 +111,12 @@ function NumericalEarth.Atmospheres.atmosphere_model(grid; # Create 2D coupling-flux fields populated by the ESM coupler each step. ρτˣ = Field{Center, Center, Nothing}(grid) ρτʸ = Field{Center, Center, Nothing}(grid) - Jᵉ = Field{Center, Center, Nothing}(grid) + Jᴱ = Field{Center, Center, Nothing}(grid) Jᵛ = Field{Center, Center, Nothing}(grid) moisture_key = moisture_prognostic_name(microphysics) moisture_bc = NamedTuple{tuple(moisture_key)}(tuple(FieldBoundaryConditions(bottom = FluxBoundaryCondition(Jᵛ)))) - energy_bc = NamedTuple{(energy_bc_key(),)}((FieldBoundaryConditions(bottom = FluxBoundaryCondition(Jᵉ)),)) + energy_bc = NamedTuple{(energy_bc_key(),)}((FieldBoundaryConditions(bottom = FluxBoundaryCondition(Jᴱ)),)) momentum_bcs = ( ρu = FieldBoundaryConditions(bottom = FluxBoundaryCondition(ρτˣ)), From e760f105ad4964d8069b1fd5e093757ad2123ea1 Mon Sep 17 00:00:00 2001 From: Eliot Quon Date: Tue, 22 Sep 2026 10:59:26 -0600 Subject: [PATCH 4/7] Test that the cold start agrees with its reference on a raised domain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pins the invariant restored by "Anchor the coupled cold start on the reference, not the datum": `atmosphere_model`'s resting initialization must anchor the density on the reference state's per-column ground pressure, not on the z = 0 datum, which is the same scalar and equal to it only when the domain bottom sits at z = 0. Nothing in-tree exercised that path — both coupled examples are bottomed at z = 0, where the two anchors coincide, and the nested constructor passes `initialize = false` — so without this the fix has no guard and a reinstated override would keep CI green. The second assertion earns its place because both quantities anchored at the datum would also agree with each other: it pins that the reference is genuinely reduced to the domain bottom. On this branch: 2/2. Reverted to the pre-fix anchor, both fail — `max|p - pᵣ|` is 22020.6 Pa against a 1e-6 tolerance, and the bottom-cell pressure is 100119.5 Pa where the reduced value is under 91192.5. It lands after the keyword rename because it calls `base_pressure`, which does not exist before it. Co-Authored-By: Claude Opus 5 (1M context) --- test/test_breeze_coupling.jl | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test/test_breeze_coupling.jl b/test/test_breeze_coupling.jl index 39fcb4bde..40c3aa9be 100644 --- a/test/test_breeze_coupling.jl +++ b/test/test_breeze_coupling.jl @@ -426,3 +426,31 @@ end end end end + +# `atmosphere_model`'s resting initialization anchors the density on the reference state's +# per-column ground pressure. Anchoring it on the z = 0 datum instead — which is the same +# scalar, and equal to it only when the domain bottom is at z = 0 — leaves the cold start +# O(ρgh) away from the reference it is differenced against on a raised domain. +@testset "Cold start agrees with its own reference on a raised domain" begin + for arch in test_architectures + A = typeof(arch) + + @testset "on $A" begin + p₀ = 101325 + grid = RectilinearGrid(arch; size = (8, 20), halo = (5, 5), + x = (0, 10kilometers), z = (2kilometers, 6kilometers), + topology = (Periodic, Flat, Bounded)) + + model = atmosphere_model(grid; base_pressure = p₀) + + p = Array(interior(model.dynamics.pressure)) + pᵣ = Array(interior(model.dynamics.reference_state.pressure)) + + @test maximum(abs, p .- pᵣ) < 1e-6 + + # Both anchored at the datum would also agree, so pin that the reference really is + # reduced to the domain bottom: 2 km of hydrostatic descent is ~22 kPa. + @test maximum(p[:, 1, 1]) < 0.9 * p₀ + end + end +end From a8feb26f865cec11ee82509231f2ed4f6b515445 Mon Sep 17 00:00:00 2001 From: Eliot Quon Date: Thu, 1 Oct 2026 17:04:04 -0700 Subject: [PATCH 5/7] Follow the datum rename in the terrain initialization test `nested_atmosphere_model` no longer takes `surface_pressure`; the keyword falls through `kw...` into `atmosphere_model`, whose keyword list is closed, so the call raises a `MethodError` rather than being ignored. The testset arrived from main after the rename was written and merged without a textual conflict, so neither side could flag it. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01AWWn9XNLXVywosTVY8uS9z --- test/test_nested_simulation.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_nested_simulation.jl b/test/test_nested_simulation.jl index 743d6265a..550f98705 100644 --- a/test/test_nested_simulation.jl +++ b/test/test_nested_simulation.jl @@ -852,7 +852,7 @@ end set!(terrain, (λ, φ) -> 1000 * exp(-(λ^2 + (φ - 36.6)^2) / 0.25)) model = nested_atmosphere_model(parent, child_grid; terrain, terrain_smoothing_passes = 0, - surface_pressure = 1e5, coriolis = nothing, parent_condensates = nothing) + base_pressure = 1e5, coriolis = nothing, parent_condensates = nothing) ext.initialize_nested_child!(model, nothing, 0.0, ""; balancer = false) expected_u = XFaceField(child_grid); set!(expected_u, (λ, φ, z) -> parent_u(z)) From 23a1624362664e883d14d085d7e5d0d430710e08 Mon Sep 17 00:00:00 2001 From: Eliot Quon Date: Thu, 1 Oct 2026 17:04:04 -0700 Subject: [PATCH 6/7] =?UTF-8?q?Compare=20the=20cold=20start=20to=20its=20r?= =?UTF-8?q?eference=20with=20`=E2=89=88`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `maximum(abs, p .- pᵣ) < 1e-6` is an absolute bound on a 10^5 Pa quantity, so it carries no margin under a narrower float type. `≈` scales with the element type and still separates the two anchors by three orders of magnitude in Float32 and seven in Float64, against a difference of 22 kPa. `p[:, :, 1]` selects the bottom level whatever extent the Flat dimension has. The comment now states what the two names on screen mean; the rest of what it said is in this PR's description. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01AWWn9XNLXVywosTVY8uS9z --- test/test_breeze_coupling.jl | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/test/test_breeze_coupling.jl b/test/test_breeze_coupling.jl index 40c3aa9be..43246a8a8 100644 --- a/test/test_breeze_coupling.jl +++ b/test/test_breeze_coupling.jl @@ -427,10 +427,7 @@ end end end -# `atmosphere_model`'s resting initialization anchors the density on the reference state's -# per-column ground pressure. Anchoring it on the z = 0 datum instead — which is the same -# scalar, and equal to it only when the domain bottom is at z = 0 — leaves the cold start -# O(ρgh) away from the reference it is differenced against on a raised domain. +# `base_pressure` is the datum at z = 0, which `reference_state` reduces to each column's surface. @testset "Cold start agrees with its own reference on a raised domain" begin for arch in test_architectures A = typeof(arch) @@ -446,11 +443,11 @@ end p = Array(interior(model.dynamics.pressure)) pᵣ = Array(interior(model.dynamics.reference_state.pressure)) - @test maximum(abs, p .- pᵣ) < 1e-6 + @test p ≈ pᵣ # Both anchored at the datum would also agree, so pin that the reference really is # reduced to the domain bottom: 2 km of hydrostatic descent is ~22 kPa. - @test maximum(p[:, 1, 1]) < 0.9 * p₀ + @test maximum(p[:, :, 1]) < 0.9 * p₀ end end end From ef8c2d428cee6429e61e73dd406f4037be78eb53 Mon Sep 17 00:00:00 2001 From: Eliot Quon Date: Thu, 1 Oct 2026 17:20:37 -0700 Subject: [PATCH 7/7] Broadcast the cold-start pressure comparison MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `@test p ≈ pᵣ` cannot run: the state pressure is (8, 1, 20) while the reference is a single broadcast column, (1, 1, 20), so `isapprox` reaches non-broadcasting `-` and throws DimensionMismatch. The reference being one column is the point — it is horizontally uniform on a height- coordinate grid, and the assertion is that every column of the cold start matches it. `all(p .≈ pᵣ)` keeps the relative tolerance the absolute 1e-6 bound was replaced for — the concern was a fixed bound on a 1e5 Pa quantity under a narrower float type — and compares elementwise, which broadcasts. Verified both directions: 2/2 here, and against the pre-fix anchor both assertions fail, the second evaluating 100119.5 < 91192.5. Co-Authored-By: Claude Opus 5 (1M context) --- test/test_breeze_coupling.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_breeze_coupling.jl b/test/test_breeze_coupling.jl index 43246a8a8..6f4baf87b 100644 --- a/test/test_breeze_coupling.jl +++ b/test/test_breeze_coupling.jl @@ -443,7 +443,7 @@ end p = Array(interior(model.dynamics.pressure)) pᵣ = Array(interior(model.dynamics.reference_state.pressure)) - @test p ≈ pᵣ + @test all(p .≈ pᵣ) # Both anchored at the datum would also agree, so pin that the reference really is # reduced to the domain bottom: 2 km of hydrostatic descent is ~22 kPa.