From 8ddc9eb5e7563f56b690eebd15daa499476afabb Mon Sep 17 00:00:00 2001 From: "Gregory L. Wagner" Date: Thu, 22 Oct 2026 16:18:04 -0600 Subject: [PATCH 1/2] Require ClimaSeaIce 0.6, which renames LinearLiquidus's slope to salinity_slope Co-Authored-By: Claude Opus 5.5 --- Project.toml | 2 +- .../sea_ice_ocean_heat_flux_formulations.jl | 2 +- test/Project.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Project.toml b/Project.toml index 7c89bfd1d..bbe2172be 100644 --- a/Project.toml +++ b/Project.toml @@ -82,7 +82,7 @@ ArchGDAL = "0.10" Breeze = "0.12" CDSAPI = "2.2.1" CFTime = "0.1, 0.2" -ClimaSeaIce = "0.5.12, 0.6" +ClimaSeaIce = "0.6" CondaPkg = "0.2.33" ConservativeRegridding = "0.2" CopernicusClimateDataStore = "0.2.2" diff --git a/src/EarthSystemModels/InterfaceComputations/sea_ice_ocean_heat_flux_formulations.jl b/src/EarthSystemModels/InterfaceComputations/sea_ice_ocean_heat_flux_formulations.jl index ae0381557..e90b00bd6 100644 --- a/src/EarthSystemModels/InterfaceComputations/sea_ice_ocean_heat_flux_formulations.jl +++ b/src/EarthSystemModels/InterfaceComputations/sea_ice_ocean_heat_flux_formulations.jl @@ -284,7 +284,7 @@ Returns `(T★, S★, q)` where q is the melt rate (positive for melting). # Get conductive flux parameters - dispatches on flux type κ, Tˢⁱ = conductive_flux_parameters(flux, ice_state, ℰ) - λ₁ = -liquidus.slope + λ₁ = -liquidus.salinity_slope λ₂ = liquidus.freshwater_melting_temperature # Transfer coefficients diff --git a/test/Project.toml b/test/Project.toml index bcbb37323..d9fbf98cc 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -46,7 +46,7 @@ Breeze = "0.12" CDSAPI = "2.2.2" CFTime = "0.1, 0.2" CUDA = "5.9.5, 6" -ClimaSeaIce = "0.5, 0.6" +ClimaSeaIce = "0.6" CondaPkg = "0.2.33" ConservativeRegridding = "0.2" CopernicusClimateDataStore = "0.2" From 15d177ff61f55f0a670436546ea792e9d71838e6 Mon Sep 17 00:00:00 2001 From: "Gregory L. Wagner" Date: Fri, 23 Oct 2026 13:15:21 -0600 Subject: [PATCH 2/2] Update Oceananigans to 0.116 - ConformalConicGrid / geographic_to_conformal_conic replace LambertConformalConicGrid / lcc_forward (CliMA/Oceananigans.jl#6168) - Smoothing clamps at Sides{Closed} boundaries, replacing LeftConnected / RightConnected (CliMA/Oceananigans.jl#6275) - Compute the urban-roughness overlap area locally; fractional_horizontal_area was removed (CliMA/Oceananigans.jl#6142) - Vertical halos cover the default WENO(order=7) tracer advection (CliMA/Oceananigans.jl#6315) - Breeze 0.13; Oceananigans and Breeze resolved from main / the Breeze PR branch until registered Co-Authored-By: Claude Opus 5.5 --- Project.toml | 9 ++++++--- docs/Project.toml | 6 ++++-- examples/Project.toml | 6 ++++-- ext/NumericalEarthBreezeExt/breeze_nested_atmosphere.jl | 8 ++++---- src/Bathymetry/Bathymetry.jl | 2 +- src/Bathymetry/smooth_topography.jl | 8 +++----- src/EarthSystemModels/earth_system_model.jl | 4 ++-- src/Lands/Lands.jl | 2 +- src/Lands/roughness/urban_roughness_field.jl | 9 ++++++--- test/Project.toml | 6 ++++-- test/test_clock_consistency.jl | 2 +- test/test_nested_simulation.jl | 4 ++-- test/test_prescribed_atmosphere.jl | 4 ++-- test/test_river_routing.jl | 2 +- test/test_tracer_budget.jl | 4 ++-- 15 files changed, 43 insertions(+), 33 deletions(-) diff --git a/Project.toml b/Project.toml index bbe2172be..dc6855807 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "NumericalEarth" uuid = "904d977b-046a-4731-8b86-9235c0d1ef02" license = "MIT" -version = "0.8.2" +version = "0.9.0" authors = ["NumericalEarth contributors"] [workspace] @@ -79,7 +79,7 @@ NumericalEarthZarrExt = "Zarr" Accessors = "0.1.44" Adapt = "4" ArchGDAL = "0.10" -Breeze = "0.12" +Breeze = "0.13" CDSAPI = "2.2.1" CFTime = "0.1, 0.2" ClimaSeaIce = "0.6" @@ -107,7 +107,7 @@ NCDatasets = "0.12, 0.13, 0.14" NaturalEarth = "0.1" NetworkOptions = "1" OceanBioME = "0.19.1, 0.20" -Oceananigans = "0.111, 0.112, 0.113, 0.114" +Oceananigans = "0.115, 0.116" # TODO: "0.116" once registered; main still reports 0.115.0 OffsetArrays = "1.14" PrecompileTools = "1" PythonCall = "0.9.28" @@ -124,3 +124,6 @@ WorldOceanAtlasTools = "0.6" Zarr = "0.10" ZipFile = "0.10" julia = "1.10" + +[sources] +Oceananigans = {url = "https://github.com/CliMA/Oceananigans.jl", rev = "main"} # TODO: remove once Oceananigans 0.116 is registered diff --git a/docs/Project.toml b/docs/Project.toml index a3f86e94e..57de93f8f 100755 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -35,10 +35,12 @@ Terrarium = "80418d68-07fa-499d-ae2b-c12e531f5cd8" [sources] NumericalEarth = {path = ".."} +Oceananigans = {url = "https://github.com/CliMA/Oceananigans.jl", rev = "main"} # TODO: remove once Oceananigans 0.116 is registered +Breeze = {url = "https://github.com/NumericalEarth/Breeze.jl", rev = "glw/oceananigans-0.116"} # TODO: remove once Breeze 0.13 is registered [compat] ArchGDAL = "0.10" -Breeze = "0.12" +Breeze = "0.13" CUDA = "6" CairoMakie = "0.15" ConservativeRegridding = "0.2" @@ -46,7 +48,7 @@ Documenter = "1" DocumenterCitations = "1.3" Enzyme = "0.13.166" Literate = "2.2" -Oceananigans = "0.113, 0.114" +Oceananigans = "0.115, 0.116" # TODO: "0.116" once registered; main still reports 0.115.0 Reactant = "0.2.278" SpeedyWeather = "0.23" Terrarium = "0.1.8" diff --git a/examples/Project.toml b/examples/Project.toml index 771dd805a..d7babe61d 100644 --- a/examples/Project.toml +++ b/examples/Project.toml @@ -15,10 +15,12 @@ Terrarium = "80418d68-07fa-499d-ae2b-c12e531f5cd8" [sources] NumericalEarth = {path = ".."} +Oceananigans = {url = "https://github.com/CliMA/Oceananigans.jl", rev = "main"} # TODO: remove once Oceananigans 0.116 is registered +Breeze = {url = "https://github.com/NumericalEarth/Breeze.jl", rev = "glw/oceananigans-0.116"} # TODO: remove once Breeze 0.13 is registered [compat] -Breeze = "0.12" +Breeze = "0.13" CopernicusClimateDataStore = "0.2" CUDA = "6" CairoMakie = "0.15" -Oceananigans = "0.113, 0.114" +Oceananigans = "0.115, 0.116" # TODO: "0.116" once registered; main still reports 0.115.0 diff --git a/ext/NumericalEarthBreezeExt/breeze_nested_atmosphere.jl b/ext/NumericalEarthBreezeExt/breeze_nested_atmosphere.jl index bf298b72b..575175ab9 100644 --- a/ext/NumericalEarthBreezeExt/breeze_nested_atmosphere.jl +++ b/ext/NumericalEarthBreezeExt/breeze_nested_atmosphere.jl @@ -39,7 +39,7 @@ using Oceananigans.Fields: AbstractField, interior, interpolate! using Oceananigans.Forcings: Relaxation, FieldTimeSeriesTarget using Oceananigans.Grids: znode, minimum_xspacing, x_domain, y_domain using Oceananigans.Grids: OrthogonalSphericalShellGrid -using Oceananigans.OrthogonalSphericalShellGrids: LambertConformalConicGrid, lcc_forward +using Oceananigans.OrthogonalSphericalShellGrids: ConformalConicGrid, geographic_to_conformal_conic using Oceananigans.TurbulenceClosures: closure_required_tracers using Oceananigans.TimeSteppers: update_state! using Oceananigans.Units: Time @@ -170,7 +170,7 @@ end # On a Lambert-conformal child the walls are lines of constant projected x or y, so the distance to the # nearest wall is measured in the projection. -function boundary_relaxation_mask(grid::LambertConformalConicGrid, width; ramp = SmoothStepRamp()) +function boundary_relaxation_mask(grid::ConformalConicGrid, width; ramp = SmoothStepRamp()) map = grid.conformal_mapping Nx, Ny, _ = size(grid) x₁, y₁ = map.x₁, map.y₁ @@ -178,7 +178,7 @@ function boundary_relaxation_mask(grid::LambertConformalConicGrid, width; ramp = y₂ = y₁ + Ny * map.Δy w = width * max(map.Δx, map.Δy) return (λ, φ, z) -> begin - x, y = lcc_forward(map, λ, φ) + x, y = geographic_to_conformal_conic(map, λ, φ) d = min(x - x₁, x₂ - x, y - y₁, y₂ - y) s = clamp(d / w, zero(d), one(d)) return oftype(d, ramp(s)) @@ -187,7 +187,7 @@ end # Only the Lambert-conformal mapping carries a projected plane to measure the wall distance in. boundary_relaxation_mask(grid::OrthogonalSphericalShellGrid, width; ramp = SmoothStepRamp()) = - throw(ArgumentError("boundary_relaxation_mask requires a LambertConformalConicGrid or a LatitudeLongitudeGrid, got a $(nameof(typeof(grid.conformal_mapping))) mapping")) + throw(ArgumentError("boundary_relaxation_mask requires a ConformalConicGrid or a LatitudeLongitudeGrid, got a $(nameof(typeof(grid.conformal_mapping))) mapping")) # Cubic-ramp (smoothstep) Rayleigh mask over the top `depth` metres of the domain, for the ρw lid sponge. # `z_top` is read once host-side under `@allowscalar`: a `znode` on a terrain-following GPU grid diff --git a/src/Bathymetry/Bathymetry.jl b/src/Bathymetry/Bathymetry.jl index afe654c2c..a495c5c6b 100644 --- a/src/Bathymetry/Bathymetry.jl +++ b/src/Bathymetry/Bathymetry.jl @@ -15,7 +15,7 @@ using Oceananigans.DistributedComputations: DistributedComputations, Distributed reconstruct_global_grid, all_reduce using Oceananigans.Fields: Field, interior, interpolate! using Oceananigans.Grids: x_domain, y_domain, topology, Face, Center, - Flat, Periodic, Bounded, LeftConnected, RightConnected, + Flat, Periodic, Bounded, Sides, Closed, RectilinearGrid, LatitudeLongitudeGrid, OrthogonalSphericalShellGrid, λnodes, φnodes using Oceananigans.ImmersedBoundaries: ImmersedBoundaryGrid diff --git a/src/Bathymetry/smooth_topography.jl b/src/Bathymetry/smooth_topography.jl index c8cbdc8a3..b6b5b996e 100644 --- a/src/Bathymetry/smooth_topography.jl +++ b/src/Bathymetry/smooth_topography.jl @@ -1,13 +1,11 @@ using DocStringExtensions: TYPEDSIGNATURES -# RightConnected and LeftConnected have physical west and east boundaries, respectively. -@inline _previous_smoothing_index(i, ::Bounded) = max(1, i - 1) -@inline _previous_smoothing_index(i, ::RightConnected) = max(1, i - 1) +# Clamp to the interior across a physical (Closed) boundary. +@inline _previous_smoothing_index(i, ::Sides{Closed}) = max(1, i - 1) @inline _previous_smoothing_index(i, ::Flat) = i @inline _previous_smoothing_index(i, topology) = i - 1 -@inline _next_smoothing_index(i, N, ::Bounded) = min(N, i + 1) -@inline _next_smoothing_index(i, N, ::LeftConnected) = min(N, i + 1) +@inline _next_smoothing_index(i, N, ::Sides{<:Any, Closed}) = min(N, i + 1) @inline _next_smoothing_index(i, N, ::Flat) = i @inline _next_smoothing_index(i, N, topology) = i + 1 diff --git a/src/EarthSystemModels/earth_system_model.jl b/src/EarthSystemModels/earth_system_model.jl index e02587cdd..35148e923 100644 --- a/src/EarthSystemModels/earth_system_model.jl +++ b/src/EarthSystemModels/earth_system_model.jl @@ -378,7 +378,7 @@ grid = LatitudeLongitudeGrid(GPU(); z = (-100, 0), latitude = (-80, 80), longitude = (0, 360), - halo = (6, 6, 3)) + halo = (6, 6, 4)) ocean = ocean_simulation(grid, closure=nothing) set!(ocean.model, T=20, S=35, u=0.01, v=-0.005) @@ -420,7 +420,7 @@ grid = LatitudeLongitudeGrid(size = (20, 20, 4), z = (-100, 0), latitude = (-80, 80), longitude = (0, 360), - halo = (6, 6, 3)) + halo = (6, 6, 4)) ocean = ocean_simulation(grid, closure=nothing) set!(ocean.model, T=20, S=35, u=0.01, v=-0.005) diff --git a/src/Lands/Lands.jl b/src/Lands/Lands.jl index 0b3f906de..250f00c1d 100644 --- a/src/Lands/Lands.jl +++ b/src/Lands/Lands.jl @@ -52,7 +52,7 @@ using Oceananigans: Oceananigans, prognostic_state, restore_prognostic_state! using Oceananigans.Architectures: CPU, architecture, on_architecture using Oceananigans.BoundaryConditions: fill_halo_regions! using Oceananigans.Fields: AbstractField, CenterField, Field, Center, Face, ZeroField, interior, regrid!, - fractional_horizontal_area, dimension_faces + dimension_faces using Oceananigans.Grids: grid_name, Center, Face, LatitudeLongitudeGrid, Bounded, Flat, znodes, λnode, φnode using Oceananigans.Operators: Δxᶜᶜᶜ, Δyᶜᶜᶜ diff --git a/src/Lands/roughness/urban_roughness_field.jl b/src/Lands/roughness/urban_roughness_field.jl index 2ff05f575..c7dd99330 100644 --- a/src/Lands/roughness/urban_roughness_field.jl +++ b/src/Lands/roughness/urban_roughness_field.jl @@ -170,6 +170,9 @@ measured heights for neighborhood averaging. Dataset modules add methods for the """ function building_morphometry end +# Area of the region between longitudes λ₁, λ₂ and latitudes φ₁, φ₂ on a sphere of radius R +@inline spherical_rectangle_area(R, λ₁, λ₂, φ₁, φ₂) = R^2 * deg2rad(λ₂ - λ₁) * (sind(φ₂) - sind(φ₁)) + # Neighborhood statistics use spherical overlap areas and built-area height moments. @kernel function _neighborhood_morphometry!(pooled, grid, properties, source_grid, neighborhood) i, j = @index(Global, NTuple) @@ -200,9 +203,9 @@ function building_morphometry end overlap_north = min(north, latitude_faces[row + 1]) overlap_west = max(shifted_west, longitude_faces[column]) overlap_east = min(shifted_east, longitude_faces[column + 1]) - # TODO Oceananigans: use a stable sine difference in fractional_horizontal_area for thin Float32 cells. - area = convert(eltype(grid), fractional_horizontal_area(grid, Float64(overlap_west), Float64(overlap_east), - Float64(overlap_south), Float64(overlap_north))) + # Float64, because the sine difference of a thin Float32 cell is unstable. + area = convert(eltype(grid), spherical_rectangle_area(Float64(grid.radius), Float64(overlap_west), Float64(overlap_east), + Float64(overlap_south), Float64(overlap_north))) fraction = stateindex(properties.plan_area_index, column, row, 1) height = stateindex(properties.mean_building_height, column, row, 1) deviation = stateindex(properties.building_height_deviation, column, row, 1) diff --git a/test/Project.toml b/test/Project.toml index d9fbf98cc..bc27f2981 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -39,10 +39,12 @@ ZipFile = "a5390f91-8eb1-5f08-bee0-b1d1ffed6cea" [sources] NumericalEarth = {path = ".."} +Oceananigans = {url = "https://github.com/CliMA/Oceananigans.jl", rev = "main"} # TODO: remove once Oceananigans 0.116 is registered +Breeze = {url = "https://github.com/NumericalEarth/Breeze.jl", rev = "glw/oceananigans-0.116"} # TODO: remove once Breeze 0.13 is registered [compat] ArchGDAL = "0.10" -Breeze = "0.12" +Breeze = "0.13" CDSAPI = "2.2.2" CFTime = "0.1, 0.2" CUDA = "5.9.5, 6" @@ -60,7 +62,7 @@ JLD2 = "0.4, 0.5, 0.6" KernelAbstractions = "0.9" MPI = "0.20" NCDatasets = "0.12, 0.13, 0.14" -Oceananigans = "0.113, 0.114" +Oceananigans = "0.115, 0.116" # TODO: "0.116" once registered; main still reports 0.115.0 OceanBioME = "0.19.1, 0.20" Profile = "<0.0.1, 1" PythonCall = "0.9.28" diff --git a/test/test_clock_consistency.jl b/test/test_clock_consistency.jl index c4e653222..5a9572c4e 100644 --- a/test/test_clock_consistency.jl +++ b/test/test_clock_consistency.jl @@ -14,7 +14,7 @@ using NumericalEarth.Oceans: PrescribedOcean, SlabOcean z = (-100, 0), latitude = (-80, 80), longitude = (0, 360), - halo = (6, 6, 3)) + halo = (6, 6, 4)) model_clock = Clock{Float64}(time=0) diff --git a/test/test_nested_simulation.jl b/test/test_nested_simulation.jl index 5540b739e..280c48af4 100644 --- a/test/test_nested_simulation.jl +++ b/test/test_nested_simulation.jl @@ -633,8 +633,8 @@ end @testset "Boundary relaxation mask and parent region follow a Lambert-conformal child's walls on $(arch)" for arch in test_architectures ext = Base.get_extension(NumericalEarth, :NumericalEarthBreezeExt) Nx, Ny, width = 40, 30, 5 - grid = LambertConformalConicGrid(arch; size = (Nx, Ny, 2), center = (-97.5, 38.5), spacing = 13e3, - standard_parallel = 38.5, z = (0, 1)) + grid = ConformalConicGrid(arch; size = (Nx, Ny, 2), center = (-97.5, 38.5), spacing = 13e3, + standard_parallel = 38.5, z = (0, 1)) mask = CenterField(grid) set!(mask, ext.boundary_relaxation_mask(grid, width)) diff --git a/test/test_prescribed_atmosphere.jl b/test/test_prescribed_atmosphere.jl index 4f1a0e38f..89cb8ca09 100644 --- a/test/test_prescribed_atmosphere.jl +++ b/test/test_prescribed_atmosphere.jl @@ -117,7 +117,7 @@ end @testset "Prescribed atmosphere tracers at any horizontal location" begin for arch in test_architectures atmosphere_grid = LatitudeLongitudeGrid(arch; size = (8, 8, 1), longitude = (0, 90), latitude = (-40, 40), z = (0, 1)) - grid = LatitudeLongitudeGrid(arch; size = (10, 12, 1), longitude = (10, 80), latitude = (-30, 30), z = (-100, 0), halo = (6, 6, 3)) + grid = LatitudeLongitudeGrid(arch; size = (10, 12, 1), longitude = (10, 80), latitude = (-30, 30), z = (-100, 0), halo = (6, 6, 4)) # Tracers have their own time axis, twice as long as the atmosphere's function linear_tracer(LX, LY, f) @@ -159,7 +159,7 @@ end @testset "Prescribed atmosphere fields on their own time axes" begin for arch in test_architectures - grid = LatitudeLongitudeGrid(arch; size = (10, 10, 1), longitude = (10, 20), latitude = (30, 40), z = (-100, 0), halo = (6, 6, 3)) + grid = LatitudeLongitudeGrid(arch; size = (10, 10, 1), longitude = (10, 20), latitude = (30, 40), z = (-100, 0), halo = (6, 6, 4)) # Rain is sampled half an interval before the other fields, as ERA5 accumulations are rain_times = [-1800, 1800.0] diff --git a/test/test_river_routing.jl b/test/test_river_routing.jl index 599bc54a8..e7a24b120 100644 --- a/test/test_river_routing.jl +++ b/test/test_river_routing.jl @@ -169,7 +169,7 @@ end longitude = (-10, 10), latitude = (-10, 10), z = (-100, 0), - halo = (7, 7, 4)) + halo = (7, 7, 5)) bottom_height(λ, φ) = ifelse(λ < 0, -100, 10) target_grid = ImmersedBoundaryGrid(underlying, GridFittedBottom(bottom_height)) diff --git a/test/test_tracer_budget.jl b/test/test_tracer_budget.jl index 03a977659..16096b8aa 100644 --- a/test/test_tracer_budget.jl +++ b/test/test_tracer_budget.jl @@ -3,7 +3,7 @@ include("runtests_setup.jl") using CUDA: @allowscalar using Oceananigans.AbstractOperations: KernelFunctionOperation using Oceananigans.Diagnostics: NaNChecker, nan_detected -using Oceananigans.Grids: MutableVerticalDiscretization +using Oceananigans.Grids: MutableVerticalDiscretization, RightCenterFolded, RightFaceFolded using Oceananigans.Operators: volume using Oceananigans.Units using NumericalEarth.Oceans: get_radiative_forcing @@ -100,7 +100,7 @@ end underlying_grid = TripolarGrid(arch; size = (20, 20, 20), z, - halo = (7, 7, 4), + halo = (7, 7, 5), fold_topology) bottom_height = synthetic_bottom_height(underlying_grid;