Skip to content

Adds grid metrics to NetCDF output #2652

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 13 commits into from
Closed
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/Grids/grid_utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,34 @@ julia> minimum_zspacing(grid, Center(), Center(), Center())
minimum_zspacing(grid, ℓx, ℓy, ℓz) = minimum_spacing(:z, grid, ℓx, ℓy, ℓz)
minimum_zspacing(grid) = minimum_spacing(:z, grid, Center(), Center(), Center())

flip_loc(::Center) = Face()
flip_loc(::Face) = Center()

active_xspacing_at_boundary(i,j,k,grid, ℓx, ℓy, ℓz) = ifelse(i==1, xnode(i,j,k, grid, flip_loc(ℓx), ℓy, ℓz) - xnode(i,j,k, grid, ℓx, ℓy, ℓz),
xnode(i,j,k, grid, ℓx, ℓy, ℓz) - xnode(i-1,j,k, grid, flip_loc(ℓx), ℓy, ℓz))

active_yspacing_at_boundary(i,j,k,grid, ℓx, ℓy, ℓz) = ifelse(j==1, ynode(i,j,k, grid, ℓx, flip_loc(ℓy), ℓz) - ynode(i,j,k, grid, ℓx, ℓy, ℓz),
ynode(i,j,k, grid, ℓx, ℓy, ℓz) - ynode(i,j-1,k, grid, ℓx, flip_loc(ℓy), ℓz))

active_zspacing_at_boundary(i,j,k,grid, ℓx, ℓy, ℓz) = ifelse(k==1, znode(i,j,k, grid, ℓx, ℓy, flip_loc(ℓz)) - znode(i,j,k, grid, ℓx, ℓy, ℓz),
znode(i,j,k, grid, ℓx, ℓy, ℓz) - znode(i,j,k-1, grid, ℓx, ℓy, flip_loc(ℓz)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do these do?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These implement the behavior defined here. I'm not sure if that's a good name for that function, but whatever we call it, I'm going to make sure it has a helpful docstring before we merge (if in fact that function survives). This I believe (I'm confirming it here) is the proper way to define spacings in a staggered grid following SGRID conventions.

for dir in (:x, :y, :z)
active_spacing = Symbol(:active_, dir, :spacing)
spacing = Symbol(dir, :spacing)
boundary_spacing = Symbol(active_spacing, :_at_boundary)
@eval begin
function $active_spacing(i, j, k, grid, ℓx, ℓy, ℓz)
Δξ = ifelse(inactive_node(i, j, k, grid, ℓx, ℓy, ℓz),
0,
ifelse(boundary_node(i, j, k, grid, ℓx, ℓy, ℓz),
$boundary_spacing(i, j, k, grid, ℓx, ℓy, ℓz),
$spacing(i, j, k, grid, ℓx, ℓy, ℓz)))
return Δξ
end
end
end


#####
##### Convenience functions
#####
Expand Down
77 changes: 77 additions & 0 deletions src/OutputWriters/grid_metric_output_netcdf.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using Oceananigans.Grids: active_xspacing, active_yspacing, active_zspacing


import Oceananigans.OutputWriters: save_output!, define_output_variable!
using Oceananigans.OutputWriters: fetch_and_convert_output, drop_output_dims,
netcdf_spatial_dimensions, output_indices
using Oceananigans.Fields: AbstractField
using NCDatasets: defVar

define_timeconstant_variable!(dataset, output::AbstractField, name, array_type, compression, output_attributes, dimensions) =
defVar(dataset, name, eltype(array_type), netcdf_spatial_dimensions(output),
compression=compression, attrib=output_attributes)

function save_output!(ds, output, model, ow, name)

data = fetch_and_convert_output(output, model, ow)
data = drop_output_dims(output, data)
colons = Tuple(Colon() for _ in 1:ndims(data))
ds[name][colons...] = data
return nothing
end

function grid_metric_locations(outputs)
location_list = []
for output in values(outputs)
loc = location(output)
if (loc ∉ location_list) && (Nothing ∉ loc)
push!(location_list, location(output))
end
end
return location_list
end

loc_superscript(::Type{Center}) = "ᶜ"
loc_superscript(::Type{Face}) = "ᶠ"
loc_superscript(::Type{Nothing}) = "ⁿ"
function default_grid_spacings(outputs, grid::AbstractRectilinearGrid)
loc_list = unique(map(location, values(outputs)))
spacing_operations = Dict()

for loc in loc_list
LX, LY, LZ = loc

# Let's replace Nothing for Center for now since `active_xyzspacing` doesn't accept Nothing as location
LX = LX == Nothing ? Center : LX
LY = LY == Nothing ? Center : LY
LZ = LZ == Nothing ? Center : LZ

Δx_name = "Δx" * loc_superscript(LX) * "ᵃᵃ"
Δy_name = "Δyᵃ" * loc_superscript(LY) * "ᵃ"
Δz_name = "Δzᵃᵃ" * loc_superscript(LZ)

push!(spacing_operations,
Δx_name => Average(KernelFunctionOperation{LX, LY, LZ}(active_xspacing, grid, LX(), LY(), LZ()), dims=(2,3)),
Δy_name => Average(KernelFunctionOperation{LX, LY, LZ}(active_yspacing, grid, LX(), LY(), LZ()), dims=(1,3)),
Δz_name => Average(KernelFunctionOperation{LX, LY, LZ}(active_zspacing, grid, LX(), LY(), LZ()), dims=(1,2)))
end
return Dict(name => Field(op) for (name, op) in spacing_operations)
end

function write_grid_metrics!(ow, metrics; user_indices = (:, :, :), with_halos=false)
ds = open(ow)
@show keys(ds)

for (metric_name, metric_operation) in metrics
indices = output_indices(metric_operation, metric_operation.grid, user_indices, with_halos)
sliced_metric = Field(metric_operation, indices=indices)

@show metric_name
define_timeconstant_variable!(ds, sliced_metric, metric_name, ow.array_type, 0, Dict(), ("xC", "yC", "zC"))
save_output!(ds, sliced_metric, model, ow, metric_name)
end
close(ds)
end



91 changes: 55 additions & 36 deletions src/OutputWriters/netcdf_output_writer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -42,31 +42,31 @@ netcdf_spatial_dimensions(::AbstractField{LX, LY, LZ}) where {LX, LY, LZ} =
tuple(xdim(instantiate(LX))..., ydim(instantiate(LY))..., zdim(instantiate(LZ))...)

native_dimensions_for_netcdf_output(grid, indices, TX, TY, TZ, Hx, Hy, Hz) =
Dict("xC" => parent(xnodes(grid, Center(); with_halos=true))[parent_index_range(indices["xC"][1], Center(), TX(), Hx)],
"xF" => parent(xnodes(grid, Face(); with_halos=true))[parent_index_range(indices["xF"][1], Face(), TX(), Hx)],
"yC" => parent(ynodes(grid, Center(); with_halos=true))[parent_index_range(indices["yC"][2], Center(), TY(), Hy)],
"yF" => parent(ynodes(grid, Face(); with_halos=true))[parent_index_range(indices["yF"][2], Face(), TY(), Hy)],
"zC" => parent(znodes(grid, Center(); with_halos=true))[parent_index_range(indices["zC"][3], Center(), TZ(), Hz)],
"zF" => parent(znodes(grid, Face(); with_halos=true))[parent_index_range(indices["zF"][3], Face(), TZ(), Hz)])
Dict(first(xdim(Center())) => parent(xnodes(grid, Center(); with_halos=true))[parent_index_range(indices["xC"][1], Center(), TX(), Hx)],
first(xdim(Face())) => parent(xnodes(grid, Face(); with_halos=true))[parent_index_range(indices["xF"][1], Face(), TX(), Hx)],
first(ydim(Center())) => parent(ynodes(grid, Center(); with_halos=true))[parent_index_range(indices["yC"][2], Center(), TY(), Hy)],
first(ydim(Face())) => parent(ynodes(grid, Face(); with_halos=true))[parent_index_range(indices["yF"][2], Face(), TY(), Hy)],
first(zdim(Center())) => parent(znodes(grid, Center(); with_halos=true))[parent_index_range(indices["zC"][3], Center(), TZ(), Hz)],
first(zdim(Face())) => parent(znodes(grid, Face(); with_halos=true))[parent_index_range(indices["zF"][3], Face(), TZ(), Hz)])

native_dimensions_for_netcdf_output(grid::AbstractCurvilinearGrid, indices, TX, TY, TZ, Hx, Hy, Hz) =
Dict("xC" => parent(λnodes(grid, Center(); with_halos=true))[parent_index_range(indices["xC"][1], Center(), TX(), Hx)],
"xF" => parent(λnodes(grid, Face(); with_halos=true))[parent_index_range(indices["xF"][1], Face(), TX(), Hx)],
"yC" => parent(φnodes(grid, Center(); with_halos=true))[parent_index_range(indices["yC"][2], Center(), TY(), Hy)],
"yF" => parent(φnodes(grid, Face(); with_halos=true))[parent_index_range(indices["yF"][2], Face(), TY(), Hy)],
"zC" => parent(znodes(grid, Center(); with_halos=true))[parent_index_range(indices["zC"][3], Center(), TZ(), Hz)],
"zF" => parent(znodes(grid, Face(); with_halos=true))[parent_index_range(indices["zF"][3], Face(), TZ(), Hz)])
Dict(first(xdim(Center())) => parent(λnodes(grid, Center(); with_halos=true))[parent_index_range(indices["xC"][1], Center(), TX(), Hx)],
first(xdim(Face())) => parent(λnodes(grid, Face(); with_halos=true))[parent_index_range(indices["xF"][1], Face(), TX(), Hx)],
first(ydim(Center())) => parent(φnodes(grid, Center(); with_halos=true))[parent_index_range(indices["yC"][2], Center(), TY(), Hy)],
first(ydim(Face())) => parent(φnodes(grid, Face(); with_halos=true))[parent_index_range(indices["yF"][2], Face(), TY(), Hy)],
first(zdim(Center())) => parent(znodes(grid, Center(); with_halos=true))[parent_index_range(indices["zC"][3], Center(), TZ(), Hz)],
first(zdim(Face())) => parent(znodes(grid, Face(); with_halos=true))[parent_index_range(indices["zF"][3], Face(), TZ(), Hz)])

function default_dimensions(output, grid, indices, with_halos)
Hx, Hy, Hz = halo_size(grid)
TX, TY, TZ = topo = topology(grid)

locs = Dict("xC" => (Center(), Center(), Center()),
"xF" => (Face(), Center(), Center()),
"yC" => (Center(), Center(), Center()),
"yF" => (Center(), Face(), Center()),
"zC" => (Center(), Center(), Center()),
"zF" => (Center(), Center(), Face() ))
locs = Dict(first(xdim(Center())) => (Center(), Center(), Center()),
first(xdim(Face())) => (Face(), Center(), Center()),
first(ydim(Center())) => (Center(), Center(), Center()),
first(ydim(Face())) => (Center(), Face(), Center()),
first(zdim(Center())) => (Center(), Center(), Center()),
first(zdim(Face())) => (Center(), Center(), Face() ))

topo = map(instantiate, topology(grid))

Expand All @@ -80,24 +80,36 @@ function default_dimensions(output, grid, indices, with_halos)
return native_dimensions_for_netcdf_output(grid, indices, TX, TY, TZ, Hx, Hy, Hz)
end

drop_output_dims(output, data) = data # fallback
drop_output_dims(output::Field, data) = dropdims(data, dims=reduced_dimensions(output))
drop_output_dims(output::WindowedTimeAverage{<:Field}, data) = dropdims(data, dims=reduced_dimensions(output.operand))

include("grid_metric_output_netcdf.jl")

const default_dimension_attributes_rectilinear = Dict(
"xC" => Dict("longname" => "Locations of the cell centers in the x-direction.", "units" => "m"),
"xF" => Dict("longname" => "Locations of the cell faces in the x-direction.", "units" => "m"),
"yC" => Dict("longname" => "Locations of the cell centers in the y-direction.", "units" => "m"),
"yF" => Dict("longname" => "Locations of the cell faces in the y-direction.", "units" => "m"),
"zC" => Dict("longname" => "Locations of the cell centers in the z-direction.", "units" => "m"),
"zF" => Dict("longname" => "Locations of the cell faces in the z-direction.", "units" => "m"),
"time" => Dict("longname" => "Time", "units" => "s"),
"particle_id" => Dict("longname" => "Particle ID")
first(xdim(Center())) => Dict("longname" => "Locations of the cell centers in the x-direction.", "units" => "m"),
first(xdim(Face())) => Dict("longname" => "Locations of the cell faces in the x-direction.", "units" => "m"),
first(ydim(Center())) => Dict("longname" => "Locations of the cell centers in the y-direction.", "units" => "m"),
first(ydim(Face())) => Dict("longname" => "Locations of the cell faces in the y-direction.", "units" => "m"),
first(zdim(Center())) => Dict("longname" => "Locations of the cell centers in the z-direction.", "units" => "m"),
first(zdim(Face())) => Dict("longname" => "Locations of the cell faces in the z-direction.", "units" => "m"),
"Δ" * first(xdim(Center())) => Dict("longname" => "Spacings around cell centers in the x-direction.", "units" => "m"),
"Δ" * first(xdim(Face())) => Dict("longname" => "Spacings around cell faces in the x-direction.", "units" => "m"),
"Δ" * first(ydim(Center())) => Dict("longname" => "Spacings around cell centers in the y-direction.", "units" => "m"),
"Δ" * first(ydim(Face())) => Dict("longname" => "Spacings around cell faces in the y-direction.", "units" => "m"),
"Δ" * first(zdim(Center())) => Dict("longname" => "Spacings around cell centers in the z-direction.", "units" => "m"),
"Δ" * first(zdim(Face())) => Dict("longname" => "Spacings around cell faces in the z-direction.", "units" => "m"),
"time" => Dict("longname" => "Time", "units" => "s"),
"particle_id" => Dict("longname" => "Particle ID")
)

const default_dimension_attributes_curvilinear = Dict(
"xC" => Dict("longname" => "Locations of the cell centers in the λ-direction.", "units" => "degrees"),
"xF" => Dict("longname" => "Locations of the cell faces in the λ-direction.", "units" => "degrees"),
"yC" => Dict("longname" => "Locations of the cell centers in the φ-direction.", "units" => "degrees"),
"yF" => Dict("longname" => "Locations of the cell faces in the φ-direction.", "units" => "degrees"),
"zC" => Dict("longname" => "Locations of the cell centers in the z-direction.", "units" => "m"),
"zF" => Dict("longname" => "Locations of the cell faces in the z-direction.", "units" => "m"),
first(xdim(Center())) => Dict("longname" => "Locations of the cell centers in the λ-direction.", "units" => "degrees"),
first(xdim(Face())) => Dict("longname" => "Locations of the cell faces in the λ-direction.", "units" => "degrees"),
first(ydim(Center())) => Dict("longname" => "Locations of the cell centers in the φ-direction.", "units" => "degrees"),
first(ydim(Face())) => Dict("longname" => "Locations of the cell faces in the φ-direction.", "units" => "degrees"),
first(zdim(Center())) => Dict("longname" => "Locations of the cell centers in the z-direction.", "units" => "m"),
first(zdim(Face())) => Dict("longname" => "Locations of the cell faces in the z-direction.", "units" => "m"),
"time" => Dict("longname" => "Time", "units" => "s"),
"particle_id" => Dict("longname" => "Particle ID")
)
Expand Down Expand Up @@ -330,6 +342,7 @@ function NetCDFOutputWriter(model, outputs; filename, schedule,
array_type = Array{Float64},
indices = (:, :, :),
with_halos = false,
with_grid_spacings = true,
global_attributes = Dict(),
output_attributes = Dict(),
dimensions = Dict(),
Expand Down Expand Up @@ -385,6 +398,7 @@ function NetCDFOutputWriter(model, outputs; filename, schedule,
schedule, outputs = time_average_outputs(schedule, outputs, model)

dims = default_dimensions(outputs, model.grid, indices, with_halos)
grid_spacings = default_grid_spacings(outputs, model.grid)

# Open the NetCDF dataset file
dataset = NCDataset(filepath, mode, attrib=global_attributes)
Expand All @@ -396,6 +410,15 @@ function NetCDFOutputWriter(model, outputs; filename, schedule,
for (dim_name, dim_array) in dims
defVar(dataset, dim_name, array_type(dim_array), (dim_name,),
compression=compression, attrib=default_dimension_attributes[dim_name])

end
if with_grid_spacings
for (spacing_name, spacing_array) in grid_spacings
dim_name = chop(spacing_name, head=1, tail=0)
@show spacing_name dim_name spacing_array
defVar(dataset, spacing_name, array_type(spacing_array), (dim_name,),
compression=compression, attrib=default_dimension_attributes[spacing_name])
end
end

# DateTime and TimeDate are both <: AbstractTime
Expand Down Expand Up @@ -537,10 +560,6 @@ function write_output!(ow::NetCDFOutputWriter, model)
return nothing
end

drop_output_dims(output, data) = data # fallback
drop_output_dims(output::Field, data) = dropdims(data, dims=reduced_dimensions(output))
drop_output_dims(output::WindowedTimeAverage{<:Field}, data) = dropdims(data, dims=reduced_dimensions(output.operand))

#####
##### Show
#####
Expand Down