From 33ad2d47166c009340543395b6c1bf59fb716a74 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Wed, 1 Apr 2026 21:16:52 +0200 Subject: [PATCH 1/7] Move default_output_attributes and add_schedule_metadata! stubs to main module These functions were defined entirely in OceananigansNCDatasetsExt despite not depending on NCDatasets. This prevented downstream extensions (e.g. ClimaSeaIce) from adding methods at compile time, forcing them to use __init__ + @eval which breaks incremental compilation on Julia 1.12. By declaring function stubs in src/OutputWriters and importing them in the extension, any package extension can now extend these functions with a simple `import` + method definition at compile time. Co-Authored-By: Claude Opus 4.6 (1M context) --- Project.toml | 2 +- ext/OceananigansNCDatasetsExt/OceananigansNCDatasetsExt.jl | 2 ++ src/OutputWriters/netcdf_writer.jl | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 0ea2387708b..8fc3693cf33 100755 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "Oceananigans" uuid = "9e8cae18-63c1-5223-a75c-80ca9d6e9a09" -version = "0.106.3" +version = "0.106.4" authors = ["Climate Modeling Alliance and contributors"] [deps] diff --git a/ext/OceananigansNCDatasetsExt/OceananigansNCDatasetsExt.jl b/ext/OceananigansNCDatasetsExt/OceananigansNCDatasetsExt.jl index 8d3624f3eb8..53e2a1c47ba 100644 --- a/ext/OceananigansNCDatasetsExt/OceananigansNCDatasetsExt.jl +++ b/ext/OceananigansNCDatasetsExt/OceananigansNCDatasetsExt.jl @@ -70,6 +70,8 @@ import Oceananigans: write_output! import Oceananigans.OutputReaders: FieldTimeSeries, set_from_netcdf! import Oceananigans.OutputWriters: NetCDFWriter, + default_output_attributes, + add_schedule_metadata!, write_grid_reconstruction_data!, convert_for_netcdf, materialize_from_netcdf, diff --git a/src/OutputWriters/netcdf_writer.jl b/src/OutputWriters/netcdf_writer.jl index 09ebadb61ae..2362bd9fbf3 100644 --- a/src/OutputWriters/netcdf_writer.jl +++ b/src/OutputWriters/netcdf_writer.jl @@ -370,6 +370,8 @@ function NetCDFWriter(model, outputs; kw...) """) end +function default_output_attributes end +function add_schedule_metadata! end function write_grid_reconstruction_data! end function convert_for_netcdf end function materialize_from_netcdf end From 09517743ed1fa3ef6e9717ced72f7a59cfbd4583 Mon Sep 17 00:00:00 2001 From: Gregory Wagner Date: Wed, 1 Apr 2026 17:27:44 -0600 Subject: [PATCH 2/7] Move default_output_attributes and add_schedule_metadata! to OutputWriters These methods have no dependency on NCDatasets and belong in the main OutputWriters module, not the NCDatasets extension. Also removes the now-unused type aliases and imports from the extension. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../OceananigansNCDatasetsExt.jl | 8 +- .../netcdf_writer.jl | 80 ----------------- src/OutputWriters/OutputWriters.jl | 7 +- src/OutputWriters/netcdf_writer.jl | 85 ++++++++++++++++++- 4 files changed, 90 insertions(+), 90 deletions(-) diff --git a/ext/OceananigansNCDatasetsExt/OceananigansNCDatasetsExt.jl b/ext/OceananigansNCDatasetsExt/OceananigansNCDatasetsExt.jl index 53e2a1c47ba..b5cb49c9cf8 100644 --- a/ext/OceananigansNCDatasetsExt/OceananigansNCDatasetsExt.jl +++ b/ext/OceananigansNCDatasetsExt/OceananigansNCDatasetsExt.jl @@ -17,13 +17,11 @@ using NCDatasets: AbstractDataset using Dates: AbstractTime, UTC, now, DateTime using Printf: @sprintf using OrderedCollections: OrderedDict -using SeawaterPolynomials: BoussinesqEquationOfState using Statistics: mean using Oceananigans: initialize!, prettytime, pretty_filesize, AbstractModel using Oceananigans.AbstractOperations: KernelFunctionOperation, AbstractOperation using Oceananigans.Architectures: CPU, GPU, on_architecture -using Oceananigans.BuoyancyFormulations: BuoyancyForce, BuoyancyTracer, SeawaterBuoyancy, LinearEquationOfState using Oceananigans.Fields using Oceananigans.Fields: set!, Reduction, reduced_dimensions, reduced_location, location, indices using Oceananigans.Grids: @@ -35,7 +33,7 @@ using Oceananigans.Grids: using Oceananigans.ImmersedBoundaries: ImmersedBoundaryGrid, GridFittedBottom, GFBIBG, GridFittedBoundary, PartialCellBottom, PCBIBG, CenterImmersedCondition, InterfaceImmersedCondition -using Oceananigans.Models: ShallowWaterModel, LagrangianParticles +using Oceananigans.Models: LagrangianParticles using Oceananigans.OutputReaders: InMemoryFTS, time_indices, @@ -70,8 +68,6 @@ import Oceananigans: write_output! import Oceananigans.OutputReaders: FieldTimeSeries, set_from_netcdf! import Oceananigans.OutputWriters: NetCDFWriter, - default_output_attributes, - add_schedule_metadata!, write_grid_reconstruction_data!, convert_for_netcdf, materialize_from_netcdf, @@ -81,8 +77,6 @@ import Oceananigans.OutputWriters: const c = Center() const f = Face() -const BoussinesqSeawaterBuoyancy = SeawaterBuoyancy{FT, <:BoussinesqEquationOfState, T, S} where {FT, T, S} -const BuoyancyBoussinesqEOSModel = BuoyancyForce{<:BoussinesqSeawaterBuoyancy, g} where {g} ##### ##### Include scripts diff --git a/ext/OceananigansNCDatasetsExt/netcdf_writer.jl b/ext/OceananigansNCDatasetsExt/netcdf_writer.jl index b493d4e041a..2fbb70afa9e 100644 --- a/ext/OceananigansNCDatasetsExt/netcdf_writer.jl +++ b/ext/OceananigansNCDatasetsExt/netcdf_writer.jl @@ -59,86 +59,6 @@ function add_location_attribute!(attrib, fd::AbstractField) return merge(loc_attrib, attrib) end -##### -##### Variable attributes -##### - -default_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"), - "w" => Dict("long_name" => "Velocity in the +z-direction.", "units" => "m/s")) - -default_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"), - "w" => Dict("long_name" => "Velocity in the vertical direction (+ = up).", "units" => "m/s"), - "displacement" => Dict("long_name" => "Sea surface height displacement", "units" => "m")) - -default_velocity_attributes(ibg::ImmersedBoundaryGrid) = default_velocity_attributes(ibg.underlying_grid) - -default_tracer_attributes(::Nothing) = Dict() - -default_tracer_attributes(::BuoyancyForce{<:BuoyancyTracer}) = Dict("b" => Dict("long_name" => "Buoyancy", "units" => "m/s²")) - -default_tracer_attributes(::BuoyancyForce{<:SeawaterBuoyancy{FT, <:LinearEquationOfState}}) where FT = Dict( - "T" => Dict("long_name" => "Temperature", "units" => "°C"), - "S" => Dict("long_name" => "Salinity", "units" => "practical salinity unit (psu)")) - -default_tracer_attributes(::BuoyancyBoussinesqEOSModel) = Dict("T" => Dict("long_name" => "Conservative temperature", "units" => "°C"), - "S" => Dict("long_name" => "Absolute salinity", "units" => "g/kg")) - -function default_output_attributes(model) - velocity_attrs = default_velocity_attributes(model.grid) - buoyancy = model isa ShallowWaterModel ? nothing : model.buoyancy - tracer_attrs = default_tracer_attributes(buoyancy) - return merge(velocity_attrs, tracer_attrs) -end - -##### -##### Saving schedule metadata as global attributes -##### - -add_schedule_metadata!(attributes, schedule) = nothing - -function add_schedule_metadata!(global_attributes, schedule::IterationInterval) - global_attributes["schedule"] = "IterationInterval" - global_attributes["interval"] = schedule.interval - global_attributes["output iteration interval"] = "Output was saved every $(schedule.interval) iteration(s)." - - return nothing -end - -function add_schedule_metadata!(global_attributes, schedule::TimeInterval) - global_attributes["schedule"] = "TimeInterval" - global_attributes["interval"] = schedule.interval - global_attributes["output time interval"] = "Output was saved every $(prettytime(schedule.interval))." - - return nothing -end - -function add_schedule_metadata!(global_attributes, schedule::WallTimeInterval) - global_attributes["schedule"] = "WallTimeInterval" - global_attributes["interval"] = schedule.interval - global_attributes["output time interval"] = - "Output was saved every $(prettytime(schedule.interval))." - - return nothing -end - -function add_schedule_metadata!(global_attributes, schedule::AveragedTimeInterval) - global_attributes["schedule"] = "AveragedTimeInterval" - global_attributes["interval"] = schedule.interval - global_attributes["output time interval"] = "Output was time-averaged and saved every $(prettytime(schedule.interval))." - - global_attributes["time_averaging_window"] = schedule.window - global_attributes["time averaging window"] = "Output was time averaged with a window size of $(prettytime(schedule.window))" - - global_attributes["time_averaging_stride"] = schedule.stride - global_attributes["time averaging stride"] = "Output was time averaged with a stride of $(schedule.stride) iteration(s) within the time averaging window." - - return nothing -end - ##### ##### NetCDFWriter constructor ##### diff --git a/src/OutputWriters/OutputWriters.jl b/src/OutputWriters/OutputWriters.jl index 0fb06355c98..b6f964886b5 100644 --- a/src/OutputWriters/OutputWriters.jl +++ b/src/OutputWriters/OutputWriters.jl @@ -13,7 +13,12 @@ using Oceananigans: boundary_conditions using Oceananigans: AbstractOutputWriter using Oceananigans.Grids: interior_indices using Oceananigans.Utils: TimeInterval, IterationInterval, WallTimeInterval, instantiate -using Oceananigans.Utils: pretty_filesize +using Oceananigans.Utils: pretty_filesize, prettytime + +using Oceananigans.BuoyancyFormulations: BuoyancyForce, BuoyancyTracer, SeawaterBuoyancy, LinearEquationOfState +using Oceananigans.Models: ShallowWaterModel + +using SeawaterPolynomials: BoussinesqEquationOfState using OffsetArrays diff --git a/src/OutputWriters/netcdf_writer.jl b/src/OutputWriters/netcdf_writer.jl index 2362bd9fbf3..8dd0dde1f2f 100644 --- a/src/OutputWriters/netcdf_writer.jl +++ b/src/OutputWriters/netcdf_writer.jl @@ -370,8 +370,89 @@ function NetCDFWriter(model, outputs; kw...) """) end -function default_output_attributes end -function add_schedule_metadata! end +##### +##### Variable attributes +##### + +const BoussinesqSeawaterBuoyancy = SeawaterBuoyancy{FT, <:BoussinesqEquationOfState, T, S} where {FT, T, S} +const BuoyancyBoussinesqEOSModel = BuoyancyForce{<:BoussinesqSeawaterBuoyancy, g} where {g} + +default_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"), + "w" => Dict("long_name" => "Velocity in the +z-direction.", "units" => "m/s")) + +default_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"), + "w" => Dict("long_name" => "Velocity in the vertical direction (+ = up).", "units" => "m/s"), + "displacement" => Dict("long_name" => "Sea surface height displacement", "units" => "m")) + +default_velocity_attributes(ibg::ImmersedBoundaryGrid) = default_velocity_attributes(ibg.underlying_grid) + +default_tracer_attributes(::Nothing) = Dict() + +default_tracer_attributes(::BuoyancyForce{<:BuoyancyTracer}) = Dict("b" => Dict("long_name" => "Buoyancy", "units" => "m/s²")) + +default_tracer_attributes(::BuoyancyForce{<:SeawaterBuoyancy{FT, <:LinearEquationOfState}}) where FT = Dict( + "T" => Dict("long_name" => "Temperature", "units" => "°C"), + "S" => Dict("long_name" => "Salinity", "units" => "practical salinity unit (psu)")) + +default_tracer_attributes(::BuoyancyBoussinesqEOSModel) = Dict("T" => Dict("long_name" => "Conservative temperature", "units" => "°C"), + "S" => Dict("long_name" => "Absolute salinity", "units" => "g/kg")) + +function default_output_attributes(model) + velocity_attrs = default_velocity_attributes(model.grid) + buoyancy = model isa ShallowWaterModel ? nothing : model.buoyancy + tracer_attrs = default_tracer_attributes(buoyancy) + return merge(velocity_attrs, tracer_attrs) +end + +##### +##### Saving schedule metadata as global attributes +##### + +add_schedule_metadata!(attributes, schedule) = nothing + +function add_schedule_metadata!(global_attributes, schedule::IterationInterval) + global_attributes["schedule"] = "IterationInterval" + global_attributes["interval"] = schedule.interval + global_attributes["output iteration interval"] = "Output was saved every $(schedule.interval) iteration(s)." + + return nothing +end + +function add_schedule_metadata!(global_attributes, schedule::TimeInterval) + global_attributes["schedule"] = "TimeInterval" + global_attributes["interval"] = schedule.interval + global_attributes["output time interval"] = "Output was saved every $(prettytime(schedule.interval))." + + return nothing +end + +function add_schedule_metadata!(global_attributes, schedule::WallTimeInterval) + global_attributes["schedule"] = "WallTimeInterval" + global_attributes["interval"] = schedule.interval + global_attributes["output time interval"] = + "Output was saved every $(prettytime(schedule.interval))." + + return nothing +end + +function add_schedule_metadata!(global_attributes, schedule::AveragedTimeInterval) + global_attributes["schedule"] = "AveragedTimeInterval" + global_attributes["interval"] = schedule.interval + global_attributes["output time interval"] = "Output was time-averaged and saved every $(prettytime(schedule.interval))." + + global_attributes["time_averaging_window"] = schedule.window + global_attributes["time averaging window"] = "Output was time averaged with a window size of $(prettytime(schedule.window))" + + global_attributes["time_averaging_stride"] = schedule.stride + global_attributes["time averaging stride"] = "Output was time averaged with a stride of $(schedule.stride) iteration(s) within the time averaging window." + + return nothing +end + function write_grid_reconstruction_data! end function convert_for_netcdf end function materialize_from_netcdf end From 802dcc1a3290e4e15c1c67fd33458956ae82f91a Mon Sep 17 00:00:00 2001 From: Gregory Wagner Date: Wed, 1 Apr 2026 17:57:44 -0600 Subject: [PATCH 3/7] Fix module load order: define output attribute methods after Models OutputWriters is included before BuoyancyFormulations and Models, so the method definitions can't live directly in OutputWriters.jl. Instead, keep stubs in OutputWriters and define the actual methods in a separate file (output_attributes.jl) that is included from Oceananigans.jl after Models. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/Oceananigans.jl | 3 + src/OutputWriters/OutputWriters.jl | 7 +-- src/OutputWriters/netcdf_writer.jl | 87 ++------------------------ src/OutputWriters/output_attributes.jl | 87 ++++++++++++++++++++++++++ 4 files changed, 95 insertions(+), 89 deletions(-) create mode 100644 src/OutputWriters/output_attributes.jl diff --git a/src/Oceananigans.jl b/src/Oceananigans.jl index 008f61c9df8..91d119a9c32 100644 --- a/src/Oceananigans.jl +++ b/src/Oceananigans.jl @@ -264,6 +264,9 @@ include("Biogeochemistry.jl") # TODO: move above include("Models/Models.jl") +# Output attributes depend on BuoyancyFormulations and Models, so must be defined after them +include("OutputWriters/output_attributes.jl") + # Abstractions for distributed and multi-region models include("MultiRegion/MultiRegion.jl") diff --git a/src/OutputWriters/OutputWriters.jl b/src/OutputWriters/OutputWriters.jl index b6f964886b5..0fb06355c98 100644 --- a/src/OutputWriters/OutputWriters.jl +++ b/src/OutputWriters/OutputWriters.jl @@ -13,12 +13,7 @@ using Oceananigans: boundary_conditions using Oceananigans: AbstractOutputWriter using Oceananigans.Grids: interior_indices using Oceananigans.Utils: TimeInterval, IterationInterval, WallTimeInterval, instantiate -using Oceananigans.Utils: pretty_filesize, prettytime - -using Oceananigans.BuoyancyFormulations: BuoyancyForce, BuoyancyTracer, SeawaterBuoyancy, LinearEquationOfState -using Oceananigans.Models: ShallowWaterModel - -using SeawaterPolynomials: BoussinesqEquationOfState +using Oceananigans.Utils: pretty_filesize using OffsetArrays diff --git a/src/OutputWriters/netcdf_writer.jl b/src/OutputWriters/netcdf_writer.jl index 8dd0dde1f2f..ddcd997737e 100644 --- a/src/OutputWriters/netcdf_writer.jl +++ b/src/OutputWriters/netcdf_writer.jl @@ -370,89 +370,10 @@ function NetCDFWriter(model, outputs; kw...) """) end -##### -##### Variable attributes -##### - -const BoussinesqSeawaterBuoyancy = SeawaterBuoyancy{FT, <:BoussinesqEquationOfState, T, S} where {FT, T, S} -const BuoyancyBoussinesqEOSModel = BuoyancyForce{<:BoussinesqSeawaterBuoyancy, g} where {g} - -default_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"), - "w" => Dict("long_name" => "Velocity in the +z-direction.", "units" => "m/s")) - -default_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"), - "w" => Dict("long_name" => "Velocity in the vertical direction (+ = up).", "units" => "m/s"), - "displacement" => Dict("long_name" => "Sea surface height displacement", "units" => "m")) - -default_velocity_attributes(ibg::ImmersedBoundaryGrid) = default_velocity_attributes(ibg.underlying_grid) - -default_tracer_attributes(::Nothing) = Dict() - -default_tracer_attributes(::BuoyancyForce{<:BuoyancyTracer}) = Dict("b" => Dict("long_name" => "Buoyancy", "units" => "m/s²")) - -default_tracer_attributes(::BuoyancyForce{<:SeawaterBuoyancy{FT, <:LinearEquationOfState}}) where FT = Dict( - "T" => Dict("long_name" => "Temperature", "units" => "°C"), - "S" => Dict("long_name" => "Salinity", "units" => "practical salinity unit (psu)")) - -default_tracer_attributes(::BuoyancyBoussinesqEOSModel) = Dict("T" => Dict("long_name" => "Conservative temperature", "units" => "°C"), - "S" => Dict("long_name" => "Absolute salinity", "units" => "g/kg")) - -function default_output_attributes(model) - velocity_attrs = default_velocity_attributes(model.grid) - buoyancy = model isa ShallowWaterModel ? nothing : model.buoyancy - tracer_attrs = default_tracer_attributes(buoyancy) - return merge(velocity_attrs, tracer_attrs) -end - -##### -##### Saving schedule metadata as global attributes -##### - -add_schedule_metadata!(attributes, schedule) = nothing - -function add_schedule_metadata!(global_attributes, schedule::IterationInterval) - global_attributes["schedule"] = "IterationInterval" - global_attributes["interval"] = schedule.interval - global_attributes["output iteration interval"] = "Output was saved every $(schedule.interval) iteration(s)." - - return nothing -end - -function add_schedule_metadata!(global_attributes, schedule::TimeInterval) - global_attributes["schedule"] = "TimeInterval" - global_attributes["interval"] = schedule.interval - global_attributes["output time interval"] = "Output was saved every $(prettytime(schedule.interval))." - - return nothing -end - -function add_schedule_metadata!(global_attributes, schedule::WallTimeInterval) - global_attributes["schedule"] = "WallTimeInterval" - global_attributes["interval"] = schedule.interval - global_attributes["output time interval"] = - "Output was saved every $(prettytime(schedule.interval))." - - return nothing -end - -function add_schedule_metadata!(global_attributes, schedule::AveragedTimeInterval) - global_attributes["schedule"] = "AveragedTimeInterval" - global_attributes["interval"] = schedule.interval - global_attributes["output time interval"] = "Output was time-averaged and saved every $(prettytime(schedule.interval))." - - global_attributes["time_averaging_window"] = schedule.window - global_attributes["time averaging window"] = "Output was time averaged with a window size of $(prettytime(schedule.window))" - - global_attributes["time_averaging_stride"] = schedule.stride - global_attributes["time averaging stride"] = "Output was time averaged with a stride of $(schedule.stride) iteration(s) within the time averaging window." - - return nothing -end - +function default_output_attributes end +function default_velocity_attributes end +function default_tracer_attributes end +function add_schedule_metadata! end function write_grid_reconstruction_data! end function convert_for_netcdf end function materialize_from_netcdf end diff --git a/src/OutputWriters/output_attributes.jl b/src/OutputWriters/output_attributes.jl new file mode 100644 index 00000000000..0bcaf6b4604 --- /dev/null +++ b/src/OutputWriters/output_attributes.jl @@ -0,0 +1,87 @@ +##### +##### Variable attributes +##### + +using .BuoyancyFormulations: BuoyancyForce, BuoyancyTracer, SeawaterBuoyancy, LinearEquationOfState +using .Models: ShallowWaterModel + +using SeawaterPolynomials: BoussinesqEquationOfState + +const BoussinesqSeawaterBuoyancy = SeawaterBuoyancy{FT, <:BoussinesqEquationOfState, T, S} where {FT, T, S} +const BuoyancyBoussinesqEOSModel = BuoyancyForce{<:BoussinesqSeawaterBuoyancy, g} where {g} + +OutputWriters.default_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"), + "w" => Dict("long_name" => "Velocity in the +z-direction.", "units" => "m/s")) + +OutputWriters.default_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"), + "w" => Dict("long_name" => "Velocity in the vertical direction (+ = up).", "units" => "m/s"), + "displacement" => Dict("long_name" => "Sea surface height displacement", "units" => "m")) + +OutputWriters.default_velocity_attributes(ibg::ImmersedBoundaryGrid) = OutputWriters.default_velocity_attributes(ibg.underlying_grid) + +OutputWriters.default_tracer_attributes(::Nothing) = Dict() + +OutputWriters.default_tracer_attributes(::BuoyancyForce{<:BuoyancyTracer}) = Dict("b" => Dict("long_name" => "Buoyancy", "units" => "m/s²")) + +OutputWriters.default_tracer_attributes(::BuoyancyForce{<:SeawaterBuoyancy{FT, <:LinearEquationOfState}}) where FT = Dict( + "T" => Dict("long_name" => "Temperature", "units" => "°C"), + "S" => Dict("long_name" => "Salinity", "units" => "practical salinity unit (psu)")) + +OutputWriters.default_tracer_attributes(::BuoyancyBoussinesqEOSModel) = Dict("T" => Dict("long_name" => "Conservative temperature", "units" => "°C"), + "S" => Dict("long_name" => "Absolute salinity", "units" => "g/kg")) + +function OutputWriters.default_output_attributes(model) + velocity_attrs = OutputWriters.default_velocity_attributes(model.grid) + buoyancy = model isa ShallowWaterModel ? nothing : model.buoyancy + tracer_attrs = OutputWriters.default_tracer_attributes(buoyancy) + return merge(velocity_attrs, tracer_attrs) +end + +##### +##### Saving schedule metadata as global attributes +##### + +OutputWriters.add_schedule_metadata!(attributes, schedule) = nothing + +function OutputWriters.add_schedule_metadata!(global_attributes, schedule::IterationInterval) + global_attributes["schedule"] = "IterationInterval" + global_attributes["interval"] = schedule.interval + global_attributes["output iteration interval"] = "Output was saved every $(schedule.interval) iteration(s)." + + return nothing +end + +function OutputWriters.add_schedule_metadata!(global_attributes, schedule::TimeInterval) + global_attributes["schedule"] = "TimeInterval" + global_attributes["interval"] = schedule.interval + global_attributes["output time interval"] = "Output was saved every $(prettytime(schedule.interval))." + + return nothing +end + +function OutputWriters.add_schedule_metadata!(global_attributes, schedule::WallTimeInterval) + global_attributes["schedule"] = "WallTimeInterval" + global_attributes["interval"] = schedule.interval + global_attributes["output time interval"] = + "Output was saved every $(prettytime(schedule.interval))." + + return nothing +end + +function OutputWriters.add_schedule_metadata!(global_attributes, schedule::AveragedTimeInterval) + global_attributes["schedule"] = "AveragedTimeInterval" + global_attributes["interval"] = schedule.interval + global_attributes["output time interval"] = "Output was time-averaged and saved every $(prettytime(schedule.interval))." + + global_attributes["time_averaging_window"] = schedule.window + global_attributes["time averaging window"] = "Output was time averaged with a window size of $(prettytime(schedule.window))" + + global_attributes["time_averaging_stride"] = schedule.stride + global_attributes["time averaging stride"] = "Output was time averaged with a stride of $(schedule.stride) iteration(s) within the time averaging window." + + return nothing +end From d3e4f9d11c517f64fa9f20b547611c227ee7f3cf Mon Sep 17 00:00:00 2001 From: Gregory Wagner Date: Wed, 1 Apr 2026 18:40:45 -0600 Subject: [PATCH 4/7] Add missing using statements for output_attributes.jl The file is included at top-level Oceananigans scope before the using .Grids etc. statements, so it needs its own explicit imports. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/OutputWriters/output_attributes.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/OutputWriters/output_attributes.jl b/src/OutputWriters/output_attributes.jl index 0bcaf6b4604..518fa8ab706 100644 --- a/src/OutputWriters/output_attributes.jl +++ b/src/OutputWriters/output_attributes.jl @@ -2,8 +2,12 @@ ##### Variable attributes ##### +using .Grids: RectilinearGrid, LatitudeLongitudeGrid +using .ImmersedBoundaries: ImmersedBoundaryGrid using .BuoyancyFormulations: BuoyancyForce, BuoyancyTracer, SeawaterBuoyancy, LinearEquationOfState using .Models: ShallowWaterModel +using .Utils: prettytime, TimeInterval, IterationInterval, WallTimeInterval +using .OutputWriters: AveragedTimeInterval using SeawaterPolynomials: BoussinesqEquationOfState From 7c81f7144b3c09552a39abddf9d900a3767d8758 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Thu, 2 Apr 2026 08:50:03 +0200 Subject: [PATCH 5/7] some cleanup --- .../netcdf_writer.jl | 2 + src/OutputWriters/OutputWriters.jl | 1 + src/OutputWriters/netcdf_writer.jl | 4 -- src/OutputWriters/output_attributes.jl | 41 +++++++++---------- 4 files changed, 23 insertions(+), 25 deletions(-) diff --git a/ext/OceananigansNCDatasetsExt/netcdf_writer.jl b/ext/OceananigansNCDatasetsExt/netcdf_writer.jl index 2fbb70afa9e..2ba1f6872d0 100644 --- a/ext/OceananigansNCDatasetsExt/netcdf_writer.jl +++ b/ext/OceananigansNCDatasetsExt/netcdf_writer.jl @@ -8,6 +8,8 @@ # grid metrics. # +using Oceananigans.OutputWriters: add_schedule_metadata!, default_output_attributes + ##### ##### Extend defVar to be able to write fields to NetCDF directly ##### diff --git a/src/OutputWriters/OutputWriters.jl b/src/OutputWriters/OutputWriters.jl index 0fb06355c98..a180f03eb92 100644 --- a/src/OutputWriters/OutputWriters.jl +++ b/src/OutputWriters/OutputWriters.jl @@ -34,6 +34,7 @@ include("averaged_specified_times.jl") include("windowed_time_average.jl") include("output_construction.jl") include("jld2_writer.jl") +include("output_attributes.jl") include("netcdf_writer.jl") include("checkpointer.jl") diff --git a/src/OutputWriters/netcdf_writer.jl b/src/OutputWriters/netcdf_writer.jl index ddcd997737e..09ebadb61ae 100644 --- a/src/OutputWriters/netcdf_writer.jl +++ b/src/OutputWriters/netcdf_writer.jl @@ -370,10 +370,6 @@ function NetCDFWriter(model, outputs; kw...) """) end -function default_output_attributes end -function default_velocity_attributes end -function default_tracer_attributes end -function add_schedule_metadata! end function write_grid_reconstruction_data! end function convert_for_netcdf end function materialize_from_netcdf end diff --git a/src/OutputWriters/output_attributes.jl b/src/OutputWriters/output_attributes.jl index 518fa8ab706..b9a9fa0fd5d 100644 --- a/src/OutputWriters/output_attributes.jl +++ b/src/OutputWriters/output_attributes.jl @@ -2,46 +2,45 @@ ##### Variable attributes ##### -using .Grids: RectilinearGrid, LatitudeLongitudeGrid -using .ImmersedBoundaries: ImmersedBoundaryGrid -using .BuoyancyFormulations: BuoyancyForce, BuoyancyTracer, SeawaterBuoyancy, LinearEquationOfState -using .Models: ShallowWaterModel -using .Utils: prettytime, TimeInterval, IterationInterval, WallTimeInterval -using .OutputWriters: AveragedTimeInterval +using Oceananigans.Grids: RectilinearGrid, LatitudeLongitudeGrid +using Oceananigans.ImmersedBoundaries: ImmersedBoundaryGrid +using Oceananigans.BuoyancyFormulations: BuoyancyForce, BuoyancyTracer, SeawaterBuoyancy, LinearEquationOfState +using Oceananigans.Models: ShallowWaterModel +using Oceananigans.Utils: prettytime, TimeInterval, IterationInterval, WallTimeInterval using SeawaterPolynomials: BoussinesqEquationOfState const BoussinesqSeawaterBuoyancy = SeawaterBuoyancy{FT, <:BoussinesqEquationOfState, T, S} where {FT, T, S} const BuoyancyBoussinesqEOSModel = BuoyancyForce{<:BoussinesqSeawaterBuoyancy, g} where {g} -OutputWriters.default_velocity_attributes(::RectilinearGrid) = Dict( +default_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"), "w" => Dict("long_name" => "Velocity in the +z-direction.", "units" => "m/s")) -OutputWriters.default_velocity_attributes(::LatitudeLongitudeGrid) = Dict( +default_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"), "w" => Dict("long_name" => "Velocity in the vertical direction (+ = up).", "units" => "m/s"), "displacement" => Dict("long_name" => "Sea surface height displacement", "units" => "m")) -OutputWriters.default_velocity_attributes(ibg::ImmersedBoundaryGrid) = OutputWriters.default_velocity_attributes(ibg.underlying_grid) +default_velocity_attributes(ibg::ImmersedBoundaryGrid) = OutputWriters.default_velocity_attributes(ibg.underlying_grid) -OutputWriters.default_tracer_attributes(::Nothing) = Dict() +default_tracer_attributes(::Nothing) = Dict() -OutputWriters.default_tracer_attributes(::BuoyancyForce{<:BuoyancyTracer}) = Dict("b" => Dict("long_name" => "Buoyancy", "units" => "m/s²")) +default_tracer_attributes(::BuoyancyForce{<:BuoyancyTracer}) = Dict("b" => Dict("long_name" => "Buoyancy", "units" => "m/s²")) -OutputWriters.default_tracer_attributes(::BuoyancyForce{<:SeawaterBuoyancy{FT, <:LinearEquationOfState}}) where FT = Dict( +default_tracer_attributes(::BuoyancyForce{<:SeawaterBuoyancy{FT, <:LinearEquationOfState}}) where FT = Dict( "T" => Dict("long_name" => "Temperature", "units" => "°C"), "S" => Dict("long_name" => "Salinity", "units" => "practical salinity unit (psu)")) -OutputWriters.default_tracer_attributes(::BuoyancyBoussinesqEOSModel) = Dict("T" => Dict("long_name" => "Conservative temperature", "units" => "°C"), +default_tracer_attributes(::BuoyancyBoussinesqEOSModel) = Dict("T" => Dict("long_name" => "Conservative temperature", "units" => "°C"), "S" => Dict("long_name" => "Absolute salinity", "units" => "g/kg")) -function OutputWriters.default_output_attributes(model) - velocity_attrs = OutputWriters.default_velocity_attributes(model.grid) +function default_output_attributes(model) + velocity_attrs = default_velocity_attributes(model.grid) buoyancy = model isa ShallowWaterModel ? nothing : model.buoyancy - tracer_attrs = OutputWriters.default_tracer_attributes(buoyancy) + tracer_attrs = default_tracer_attributes(buoyancy) return merge(velocity_attrs, tracer_attrs) end @@ -49,9 +48,9 @@ end ##### Saving schedule metadata as global attributes ##### -OutputWriters.add_schedule_metadata!(attributes, schedule) = nothing +add_schedule_metadata!(attributes, schedule) = nothing -function OutputWriters.add_schedule_metadata!(global_attributes, schedule::IterationInterval) +function add_schedule_metadata!(global_attributes, schedule::IterationInterval) global_attributes["schedule"] = "IterationInterval" global_attributes["interval"] = schedule.interval global_attributes["output iteration interval"] = "Output was saved every $(schedule.interval) iteration(s)." @@ -59,7 +58,7 @@ function OutputWriters.add_schedule_metadata!(global_attributes, schedule::Itera return nothing end -function OutputWriters.add_schedule_metadata!(global_attributes, schedule::TimeInterval) +function add_schedule_metadata!(global_attributes, schedule::TimeInterval) global_attributes["schedule"] = "TimeInterval" global_attributes["interval"] = schedule.interval global_attributes["output time interval"] = "Output was saved every $(prettytime(schedule.interval))." @@ -67,7 +66,7 @@ function OutputWriters.add_schedule_metadata!(global_attributes, schedule::TimeI return nothing end -function OutputWriters.add_schedule_metadata!(global_attributes, schedule::WallTimeInterval) +function add_schedule_metadata!(global_attributes, schedule::WallTimeInterval) global_attributes["schedule"] = "WallTimeInterval" global_attributes["interval"] = schedule.interval global_attributes["output time interval"] = @@ -76,7 +75,7 @@ function OutputWriters.add_schedule_metadata!(global_attributes, schedule::WallT return nothing end -function OutputWriters.add_schedule_metadata!(global_attributes, schedule::AveragedTimeInterval) +function add_schedule_metadata!(global_attributes, schedule::AveragedTimeInterval) global_attributes["schedule"] = "AveragedTimeInterval" global_attributes["interval"] = schedule.interval global_attributes["output time interval"] = "Output was time-averaged and saved every $(prettytime(schedule.interval))." From 3f67eeb9da1282e9b87f3d4c79b9c6b7663e877a Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Thu, 2 Apr 2026 12:18:55 +0200 Subject: [PATCH 6/7] Move output attribute methods to Models, keep only generic fallback in OutputWriters Co-Authored-By: Claude Opus 4.6 (1M context) --- src/Models/Models.jl | 3 ++ src/Models/output_attributes.jl | 55 ++++++++++++++++++++++++++ src/Oceananigans.jl | 3 -- src/OutputWriters/output_attributes.jl | 46 ++------------------- 4 files changed, 62 insertions(+), 45 deletions(-) create mode 100644 src/Models/output_attributes.jl diff --git a/src/Models/Models.jl b/src/Models/Models.jl index 4abe1ccabeb..5453bf68819 100644 --- a/src/Models/Models.jl +++ b/src/Models/Models.jl @@ -214,6 +214,9 @@ checkpointer_address(::HydrostaticFreeSurfaceModel) = "HydrostaticFreeSurfaceMod default_included_properties(::OceananigansModels) = [:grid] +# Specialized output attributes for velocity and tracer fields +include("output_attributes.jl") + # Implementation of diagnostics applicable to both `NonhydrostaticModel` and `HydrostaticFreeSurfaceModel` include("seawater_density.jl") include("buoyancy_operation.jl") diff --git a/src/Models/output_attributes.jl b/src/Models/output_attributes.jl new file mode 100644 index 00000000000..5cfe20a24b8 --- /dev/null +++ b/src/Models/output_attributes.jl @@ -0,0 +1,55 @@ +using Oceananigans.Grids: RectilinearGrid, LatitudeLongitudeGrid +using Oceananigans.ImmersedBoundaries: ImmersedBoundaryGrid +using Oceananigans.BuoyancyFormulations: BuoyancyForce, BuoyancyTracer, SeawaterBuoyancy, LinearEquationOfState + +using SeawaterPolynomials: BoussinesqEquationOfState + +import Oceananigans.OutputWriters: default_output_attributes + +const BoussinesqSeawaterBuoyancy = SeawaterBuoyancy{FT, <:BoussinesqEquationOfState, T, S} where {FT, T, S} +const BuoyancyBoussinesqEOSModel = BuoyancyForce{<:BoussinesqSeawaterBuoyancy, g} where {g} + +##### +##### Specialized velocity attributes +##### + +default_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"), + "w" => Dict("long_name" => "Velocity in the +z-direction.", "units" => "m/s")) + +default_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"), + "w" => Dict("long_name" => "Velocity in the vertical direction (+ = up).", "units" => "m/s"), + "displacement" => Dict("long_name" => "Sea surface height displacement", "units" => "m")) + +default_velocity_attributes(ibg::ImmersedBoundaryGrid) = default_velocity_attributes(ibg.underlying_grid) + +##### +##### Specialized tracer attributes +##### + +default_tracer_attributes(::BuoyancyForce{<:BuoyancyTracer}) = Dict("b" => Dict("long_name" => "Buoyancy", "units" => "m/s²")) + +default_tracer_attributes(::BuoyancyForce{<:SeawaterBuoyancy{FT, <:LinearEquationOfState}}) where FT = Dict( + "T" => Dict("long_name" => "Temperature", "units" => "°C"), + "S" => Dict("long_name" => "Salinity", "units" => "practical salinity unit (psu)")) + +default_tracer_attributes(::BuoyancyBoussinesqEOSModel) = Dict( + "T" => Dict("long_name" => "Conservative temperature", "units" => "°C"), + "S" => Dict("long_name" => "Absolute salinity", "units" => "g/kg")) + +##### +##### Specialized default_output_attributes +##### + +function default_output_attributes(model::Union{NonhydrostaticModel, HydrostaticFreeSurfaceModel}) + velocity_attrs = default_velocity_attributes(model.grid) + tracer_attrs = default_tracer_attributes(model.buoyancy) + return merge(velocity_attrs, tracer_attrs) +end + +function default_output_attributes(model::ShallowWaterModel) + return default_velocity_attributes(model.grid) +end diff --git a/src/Oceananigans.jl b/src/Oceananigans.jl index 91d119a9c32..008f61c9df8 100644 --- a/src/Oceananigans.jl +++ b/src/Oceananigans.jl @@ -264,9 +264,6 @@ include("Biogeochemistry.jl") # TODO: move above include("Models/Models.jl") -# Output attributes depend on BuoyancyFormulations and Models, so must be defined after them -include("OutputWriters/output_attributes.jl") - # Abstractions for distributed and multi-region models include("MultiRegion/MultiRegion.jl") diff --git a/src/OutputWriters/output_attributes.jl b/src/OutputWriters/output_attributes.jl index b9a9fa0fd5d..487555bffee 100644 --- a/src/OutputWriters/output_attributes.jl +++ b/src/OutputWriters/output_attributes.jl @@ -1,48 +1,10 @@ -##### -##### Variable attributes -##### - -using Oceananigans.Grids: RectilinearGrid, LatitudeLongitudeGrid -using Oceananigans.ImmersedBoundaries: ImmersedBoundaryGrid -using Oceananigans.BuoyancyFormulations: BuoyancyForce, BuoyancyTracer, SeawaterBuoyancy, LinearEquationOfState -using Oceananigans.Models: ShallowWaterModel using Oceananigans.Utils: prettytime, TimeInterval, IterationInterval, WallTimeInterval -using SeawaterPolynomials: BoussinesqEquationOfState - -const BoussinesqSeawaterBuoyancy = SeawaterBuoyancy{FT, <:BoussinesqEquationOfState, T, S} where {FT, T, S} -const BuoyancyBoussinesqEOSModel = BuoyancyForce{<:BoussinesqSeawaterBuoyancy, g} where {g} - -default_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"), - "w" => Dict("long_name" => "Velocity in the +z-direction.", "units" => "m/s")) - -default_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"), - "w" => Dict("long_name" => "Velocity in the vertical direction (+ = up).", "units" => "m/s"), - "displacement" => Dict("long_name" => "Sea surface height displacement", "units" => "m")) - -default_velocity_attributes(ibg::ImmersedBoundaryGrid) = OutputWriters.default_velocity_attributes(ibg.underlying_grid) - -default_tracer_attributes(::Nothing) = Dict() - -default_tracer_attributes(::BuoyancyForce{<:BuoyancyTracer}) = Dict("b" => Dict("long_name" => "Buoyancy", "units" => "m/s²")) - -default_tracer_attributes(::BuoyancyForce{<:SeawaterBuoyancy{FT, <:LinearEquationOfState}}) where FT = Dict( - "T" => Dict("long_name" => "Temperature", "units" => "°C"), - "S" => Dict("long_name" => "Salinity", "units" => "practical salinity unit (psu)")) - -default_tracer_attributes(::BuoyancyBoussinesqEOSModel) = Dict("T" => Dict("long_name" => "Conservative temperature", "units" => "°C"), - "S" => Dict("long_name" => "Absolute salinity", "units" => "g/kg")) +##### +##### Generic fallbacks for variable attributes +##### -function default_output_attributes(model) - velocity_attrs = default_velocity_attributes(model.grid) - buoyancy = model isa ShallowWaterModel ? nothing : model.buoyancy - tracer_attrs = default_tracer_attributes(buoyancy) - return merge(velocity_attrs, tracer_attrs) -end +default_output_attributes(model) = Dict{String, Any}() ##### ##### Saving schedule metadata as global attributes From d8ef1dcc510b727aa574a0c4d11eeba666e30582 Mon Sep 17 00:00:00 2001 From: Simone Silvestri Date: Thu, 2 Apr 2026 13:48:48 +0200 Subject: [PATCH 7/7] Add default_tracer_attributes(::Nothing) fallback for models without buoyancy Co-Authored-By: Claude Opus 4.6 (1M context) --- src/Models/output_attributes.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Models/output_attributes.jl b/src/Models/output_attributes.jl index 5cfe20a24b8..ad2d326906c 100644 --- a/src/Models/output_attributes.jl +++ b/src/Models/output_attributes.jl @@ -30,6 +30,8 @@ default_velocity_attributes(ibg::ImmersedBoundaryGrid) = default_velocity_attrib ##### Specialized tracer attributes ##### +default_tracer_attributes(::Nothing) = Dict{String, Any}() + default_tracer_attributes(::BuoyancyForce{<:BuoyancyTracer}) = Dict("b" => Dict("long_name" => "Buoyancy", "units" => "m/s²")) default_tracer_attributes(::BuoyancyForce{<:SeawaterBuoyancy{FT, <:LinearEquationOfState}}) where FT = Dict(