From e4344edde1c9bfceeecd069650777f4fbebe0ace Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Wed, 25 Mar 2026 09:48:19 +0100 Subject: [PATCH 1/4] Add a test --- .github/workflows/ci.yml | 25 +++++++++++++++ Project.toml | 10 ++++-- ext/ClimaSeaIceNCDatasetsExt.jl | 38 ++++++++++++++++++++++ test/runtests.jl | 4 +++ test/test_netcdf_writer.jl | 57 +++++++++++++++++++++++++++++++++ 5 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 ext/ClimaSeaIceNCDatasetsExt.jl create mode 100644 test/test_netcdf_writer.jl diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f46ccd21..9129dfdd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -124,6 +124,31 @@ jobs: env: TEST_GROUP: "timestepping" + netcdf: + name: NetCDF Writer - Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} + runs-on: ${{ matrix.os }} + timeout-minutes: 120 + strategy: + fail-fast: false + matrix: + version: + - '1.12' + os: + - ubuntu-latest + arch: + - x64 + steps: + - uses: actions/checkout@v5 + - uses: julia-actions/setup-julia@v2 + with: + version: ${{ matrix.version }} + arch: ${{ matrix.arch }} + - uses: julia-actions/cache@v2 + - uses: julia-actions/julia-buildpkg@v1 + - uses: julia-actions/julia-runtest@v1 + env: + TEST_GROUP: "netcdf" + enthalpy: name: Enthalpy Model - Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} runs-on: ${{ matrix.os }} diff --git a/Project.toml b/Project.toml index 517023b9..4e6fd611 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "ClimaSeaIce" uuid = "6ba0ff68-24e6-4315-936c-2e99227c95a4" authors = ["Climate Modeling Alliance and contributors"] -version = "0.4.5" +version = "0.4.6" [deps] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" @@ -12,6 +12,12 @@ RootSolvers = "7181ea78-2dcb-4de3-ab41-2b8ab5a31e74" Roots = "f2b01f46-fcfa-551c-844a-d8ac1e96c665" SeawaterPolynomials = "d496a93d-167e-4197-9f49-d3af4ff8fe40" +[weakdeps] +NCDatasets = "85f8d34a-cbdd-5861-8df4-14fed0d494ab" + +[extensions] +ClimaSeaIceNCDatasetsExt = "NCDatasets" + [compat] Adapt = "3, 4" JLD2 = "0.6.2" @@ -27,4 +33,4 @@ MPI = "da04e1cc-30fd-572f-bb4f-1f8673147195" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["MPI", "Test"] +test = ["MPI", "Test", "NCDatasets"] diff --git a/ext/ClimaSeaIceNCDatasetsExt.jl b/ext/ClimaSeaIceNCDatasetsExt.jl new file mode 100644 index 00000000..2372aafd --- /dev/null +++ b/ext/ClimaSeaIceNCDatasetsExt.jl @@ -0,0 +1,38 @@ +module ClimaSeaIceNCDatasetsExt + +using NCDatasets +using Oceananigans +using ClimaSeaIce + +const OCNE = Base.get_extension(Oceananigans, :OceananigansNCDatasetsExt) + +##### +##### Variable attributes +##### + +default_horizontal_velocity_attributes(::RectilinearGrid) = Dict( + "u" => Dict("long_name" => "Velocity in the +x-direction.", "units" => "m/s"), + "v" => Dict("long_name" => "Velocity in the +y-direction.", "units" => "m/s")) + +default_horizontal_velocity_attributes(::LatitudeLongitudeGrid) = Dict( + "u" => Dict("long_name" => "Velocity in the zonal direction (+ = east).", "units" => "m/s"), + "v" => Dict("long_name" => "Velocity in the meridional direction (+ = north).", "units" => "m/s")) + +default_horizontal_velocity_attributes(::OrthogonalSphericalShellGrid) = Dict( + "u" => Dict("long_name" => "Velocity in the i-direction (+ = increasing i).", "units" => "m/s"), + "v" => Dict("long_name" => "Velocity in the j-direction (+ = increasing j).", "units" => "m/s")) + +default_horizontal_velocity_attributes(ibg::ImmersedBoundaryGrid) = default_velocity_attributes(ibg.underlying_grid) + +default_sea_ice_attributes() = Dict( + "h" => Dict("long_name" => "Sea ice thickness.", "units" => "m"), + "ℵ" => Dict("long_name" => "Sea ice concentration.", "units" => "-") +) + +function OCNE.default_output_attributes(model::SeaIceModel) + velocity_attrs = default_horizontal_velocity_attributes(model.grid) + tracer_attrs = default_sea_ice_attributes() + return merge(velocity_attrs, tracer_attrs) +end + +end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index efe922d1..8c9577a5 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -39,6 +39,10 @@ if TEST_GROUP == "all" || TEST_GROUP == "checkpointing" include("test_checkpointing.jl") end +if TEST_GROUP == "all" || TEST_GROUP == "netcdf" + include("test_netcdf_writer.jl") +end + if TEST_GROUP == "all" || TEST_GROUP == "distributed" include("test_distributed_sea_ice.jl") end diff --git a/test/test_netcdf_writer.jl b/test/test_netcdf_writer.jl new file mode 100644 index 00000000..b84f55f3 --- /dev/null +++ b/test/test_netcdf_writer.jl @@ -0,0 +1,57 @@ +using ClimaSeaIce +using ClimaSeaIce.SeaIceDynamics +using ClimaSeaIce.SeaIceThermodynamics +using Test + +using NCDatasets +using Oceananigans +using Oceananigans.OutputWriters: NetCDFWriter + +function test_netcdf_writer(arch) + Nx, Ny = 16, 16 + Lx, Ly = 100, 100 + Δt = 1 + + grid = RectilinearGrid(arch, size=(Nx, Ny), x=(0, Lx), y=(0, Ly), topology=(Bounded, Bounded, Flat)) + + for dynamics in (nothing, SeaIceMomentumEquation(grid)) + ice_thermodynamics = SlabSeaIceThermodynamics(grid) + model = SeaIceModel(grid; dynamics, ice_thermodynamics) + + set!(model, h=0.5, ℵ=1) + + simulation = Simulation(model, Δt=Δt, stop_iteration=5) + + filepath = "test_sea_ice_netcdf.nc" + + simulation.output_writers[:nc] = NetCDFWriter(model, Oceananigans.prognostic_fields(model); + filename = filepath, + schedule = IterationInterval(1), + overwrite_existing = true) + + run!(simulation) + + ds = NCDataset(filepath) + + # Check that the time dimension exists + @test haskey(ds.dim, "time") + @test length(ds["time"]) == 6 # iteration 0 through 5 + + # Check that sea ice fields are present + @test haskey(ds, "h") + @test haskey(ds, "ℵ") + + # Check that velocity fields are present when dynamics are enabled + if !isnothing(dynamics) + @test haskey(ds, "u") + @test haskey(ds, "v") + end + + close(ds) + rm(filepath, force=true) + end +end + +@testset "NetCDFWriter Tests" begin + test_netcdf_writer(CPU()) +end From a31955fda75c438e42a3308b8b73daa4469067a9 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Wed, 25 Mar 2026 09:49:21 +0100 Subject: [PATCH 2/4] Fix missing newline at end of ClimaSeaIceNCDatasetsExt.jl --- ext/ClimaSeaIceNCDatasetsExt.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/ClimaSeaIceNCDatasetsExt.jl b/ext/ClimaSeaIceNCDatasetsExt.jl index 2372aafd..3778ce25 100644 --- a/ext/ClimaSeaIceNCDatasetsExt.jl +++ b/ext/ClimaSeaIceNCDatasetsExt.jl @@ -35,4 +35,4 @@ function OCNE.default_output_attributes(model::SeaIceModel) return merge(velocity_attrs, tracer_attrs) end -end \ No newline at end of file +end From 2b3136d7f818b906119332667c957f965dfea3c6 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Wed, 25 Mar 2026 14:39:46 +0100 Subject: [PATCH 3/4] Update ext/ClimaSeaIceNCDatasetsExt.jl Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- ext/ClimaSeaIceNCDatasetsExt.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/ClimaSeaIceNCDatasetsExt.jl b/ext/ClimaSeaIceNCDatasetsExt.jl index 3778ce25..9a3a9295 100644 --- a/ext/ClimaSeaIceNCDatasetsExt.jl +++ b/ext/ClimaSeaIceNCDatasetsExt.jl @@ -22,7 +22,7 @@ default_horizontal_velocity_attributes(::OrthogonalSphericalShellGrid) = Dict( "u" => Dict("long_name" => "Velocity in the i-direction (+ = increasing i).", "units" => "m/s"), "v" => Dict("long_name" => "Velocity in the j-direction (+ = increasing j).", "units" => "m/s")) -default_horizontal_velocity_attributes(ibg::ImmersedBoundaryGrid) = default_velocity_attributes(ibg.underlying_grid) +default_horizontal_velocity_attributes(ibg::ImmersedBoundaryGrid) = default_horizontal_velocity_attributes(ibg.underlying_grid) default_sea_ice_attributes() = Dict( "h" => Dict("long_name" => "Sea ice thickness.", "units" => "m"), From eccd8ca3c54a85a77da853cbd3af0073102ec353 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Wed, 25 Mar 2026 20:35:05 +0100 Subject: [PATCH 4/4] change OCNE after precompilation (sigh) --- ext/ClimaSeaIceNCDatasetsExt.jl | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/ext/ClimaSeaIceNCDatasetsExt.jl b/ext/ClimaSeaIceNCDatasetsExt.jl index 2372aafd..9de8123f 100644 --- a/ext/ClimaSeaIceNCDatasetsExt.jl +++ b/ext/ClimaSeaIceNCDatasetsExt.jl @@ -4,8 +4,6 @@ using NCDatasets using Oceananigans using ClimaSeaIce -const OCNE = Base.get_extension(Oceananigans, :OceananigansNCDatasetsExt) - ##### ##### Variable attributes ##### @@ -21,7 +19,7 @@ default_horizontal_velocity_attributes(::LatitudeLongitudeGrid) = Dict( default_horizontal_velocity_attributes(::OrthogonalSphericalShellGrid) = Dict( "u" => Dict("long_name" => "Velocity in the i-direction (+ = increasing i).", "units" => "m/s"), "v" => Dict("long_name" => "Velocity in the j-direction (+ = increasing j).", "units" => "m/s")) - + default_horizontal_velocity_attributes(ibg::ImmersedBoundaryGrid) = default_velocity_attributes(ibg.underlying_grid) default_sea_ice_attributes() = Dict( @@ -29,10 +27,17 @@ default_sea_ice_attributes() = Dict( "ℵ" => Dict("long_name" => "Sea ice concentration.", "units" => "-") ) -function OCNE.default_output_attributes(model::SeaIceModel) - velocity_attrs = default_horizontal_velocity_attributes(model.grid) - tracer_attrs = default_sea_ice_attributes() - return merge(velocity_attrs, tracer_attrs) +function __init__() + OCNE = Base.get_extension(Oceananigans, :OceananigansNCDatasetsExt) + if !isnothing(OCNE) + @eval begin + function $OCNE.default_output_attributes(model::SeaIceModel) + velocity_attrs = default_horizontal_velocity_attributes(model.grid) + tracer_attrs = default_sea_ice_attributes() + return merge(velocity_attrs, tracer_attrs) + end + end + end end end \ No newline at end of file