Skip to content

Commit

Permalink
(0.90.8) Rules for Time interpolation / extrapolation for FieldTime…
Browse files Browse the repository at this point in the history
…Series and `InterpolatedField` (#3450)

* Bump to 0.90.7

* Fix docstring for field time series

* this should work?

* fixing the show method

* Allow OnDisk FieldTimeSeries without times

* this should work for the update

* time_extrapolation as property

* positional argument for `InMemory`

* better show

* bugfix

* adjust margin

* bugfix

* Change InMemory user interface to InMemory(chunk_size)

* Add back lost architecture kwarg plus fix docstring

* Not lost... hidden

* Clean up time extrapolation

* time_indexing -> time_extrapolation

* Generalize _interpolate

* Add a node definition for AbstractField

* small vugfix

* correction

* now it should go

* add a test

* couple of more tests

* better definitions of indics

* more tests for clamp

* should be ready

* Clearer architecture determination in FieldTimeSeries

* Fix FieldTimeSeries docstring

* this works provided a Δt

* some show method

* tests should pass now

* now it works

* last bugfix

* ok

* some more explanation

* Call data_summary from interior

* Cosmetic change

* Start cleaning up time_extrapolation

* Fix Cycled time extrapolation

* Working on Cyclical extrapolation for FieldTimeSeries

* More progress

* Its a masterpiece that may not wokr

* some getindices

* delete file

* should work

* remove time extrapolation

* Less than 2 not ge 2

* canoot index with a tuple

* typos

* Get in memory to wrk on GPU

* Iron out bugs that came up during ClimaOcean tests

* Cosmetic update

* Updates

* Mind bending refactor

* Remove unused filesd

* Fix a few more bugs

* Pretty things up

* Fix a bug plus cosmetic updates

* Hotfix for LatLonGrid y-periodic

* Add time_index and clean up time_indices

* Fix bug in GPUAdaptedFTS

* Temporary fix for mean

* Try to fix showing for GPU fieldtime series

* Implement version of interpolate that accepts offsetarray

* Fix bugs with interpolating_time_indices

* Add method to show tim eindexing

* Reduced dimensions for AbstractField and Field

* Update RiBasedVerticalDiffusivity

* Add comment to RiBasedVerticalDiffusivity

* Make halo filling code easier to read

* Add a helpful comment

* Generalize PartlyInMemory to accept custom backends

* Fix a sign error in implicit dissipative buoyancy flux

* Better comments

* Make interior work for FieldTimeSeries with custom backend

* bump patch release

* Get rid of unnecessary intermediate constructor for FieldTimeSeries

* Add written_names utility

* Add extra criterion on dissipative buoyancy flux calculation

* Fix faulty show for FieldTimeSeries

* Add flattened_unique_values for empty tuple

* Fix up possible_field_time_series

* Try to get mean of FieldTimeSeries working

* mean(FieldTimeSeries) works now?

* Bump Project to 0.90.8

* Export FieldDataset

* Add conditional_length for FieldTimeSeries

* Fix tests

* fix test

* fixed all tests?

* bugfix

* another small bugfix

* Put back the CATKE bug

* Update src/OutputReaders/set_field_time_series.jl

Co-authored-by: Simone Silvestri <[email protected]>

---------

Co-authored-by: Simone Silvestri <[email protected]>
Co-authored-by: Navid C. Constantinou <[email protected]>
Co-authored-by: Simone Silvestri <[email protected]>
  • Loading branch information
4 people authored Feb 27, 2024
1 parent 0391b3a commit f251196
Show file tree
Hide file tree
Showing 26 changed files with 1,216 additions and 768 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "Oceananigans"
uuid = "9e8cae18-63c1-5223-a75c-80ca9d6e9a09"
authors = ["Climate Modeling Alliance and contributors"]
version = "0.90.7"
version = "0.90.8"

[deps]
Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
Expand Down
151 changes: 111 additions & 40 deletions src/BoundaryConditions/fill_halo_regions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -81,16 +81,16 @@ end
# position [1] and the associated boundary conditions in position [2]
function permute_boundary_conditions(boundary_conditions)

split_x_boundaries = split_boundary(extract_west_bc(boundary_conditions), extract_east_bc(boundary_conditions))
split_y_boundaries = split_boundary(extract_south_bc(boundary_conditions), extract_north_bc(boundary_conditions))
split_x_halo_filling = split_halo_filling(extract_west_bc(boundary_conditions), extract_east_bc(boundary_conditions))
split_y_halo_filling = split_halo_filling(extract_south_bc(boundary_conditions), extract_north_bc(boundary_conditions))

west_bc = extract_west_bc(boundary_conditions)
east_bc = extract_east_bc(boundary_conditions)
south_bc = extract_south_bc(boundary_conditions)
north_bc = extract_north_bc(boundary_conditions)

if split_x_boundaries
if split_y_boundaries
if split_x_halo_filling
if split_y_halo_filling
fill_halos! = [fill_west_halo!, fill_east_halo!, fill_south_halo!, fill_north_halo!, fill_bottom_and_top_halo!]
sides = [:west, :east, :south, :north, :bottom_and_top]
bcs_array = [west_bc, east_bc, south_bc, north_bc, extract_bottom_bc(boundary_conditions)]
Expand All @@ -100,7 +100,7 @@ function permute_boundary_conditions(boundary_conditions)
bcs_array = [west_bc, east_bc, south_bc, extract_bottom_bc(boundary_conditions)]
end
else
if split_y_boundaries
if split_y_halo_filling
fill_halos! = [fill_west_and_east_halo!, fill_south_halo!, fill_north_halo!, fill_bottom_and_top_halo!]
sides = [:west_and_east, :south, :north, :bottom_and_top]
bcs_array = [west_bc, south_bc, north_bc, extract_bottom_bc(boundary_conditions)]
Expand All @@ -122,17 +122,17 @@ end

# Split direction in two distinct fill_halo! events in case of a communication boundary condition
# (distributed DCBC), paired with a Flux, Value or Gradient boundary condition
split_boundary(bcs1, bcs2) = false
split_boundary(::DCBC, ::DCBC) = false
split_boundary(bcs1, ::DCBC) = true
split_boundary(::DCBC, bcs2) = true
split_halo_filling(bcs1, bcs2) = false
split_halo_filling(::DCBC, ::DCBC) = false
split_halo_filling(bcs1, ::DCBC) = true
split_halo_filling(::DCBC, bcs2) = true

# TODO: support heterogeneous distributed-shared communication
# split_boundary(::MCBC, ::DCBC) = false
# split_boundary(::DCBC, ::MCBC) = false
# split_boundary(::MCBC, ::MCBC) = false
# split_boundary(bcs1, ::MCBC) = true
# split_boundary(::MCBC, bcs2) = true
# split_halo_filling(::MCBC, ::DCBC) = false
# split_halo_filling(::DCBC, ::MCBC) = false
# split_halo_filling(::MCBC, ::MCBC) = false
# split_halo_filling(bcs1, ::MCBC) = true
# split_halo_filling(::MCBC, bcs2) = true

#####
##### Halo filling order
Expand Down Expand Up @@ -293,30 +293,47 @@ end
#####

fill_west_halo!(c, bc, size, offset, loc, arch, grid, args...; kwargs...) =
launch!(arch, grid, KernelParameters(size, offset), _fill_only_west_halo!, c, bc, loc, grid, Tuple(args); kwargs...)
launch!(arch, grid, KernelParameters(size, offset),
_fill_only_west_halo!, c, bc, loc, grid, Tuple(args); kwargs...)

fill_east_halo!(c, bc, size, offset, loc, arch, grid, args...; kwargs...) =
launch!(arch, grid, KernelParameters(size, offset), _fill_only_east_halo!, c, bc, loc, grid, Tuple(args); kwargs...)
launch!(arch, grid, KernelParameters(size, offset),
_fill_only_east_halo!, c, bc, loc, grid, Tuple(args); kwargs...)

fill_south_halo!(c, bc, size, offset, loc, arch, grid, args...; kwargs...) =
launch!(arch, grid, KernelParameters(size, offset), _fill_only_south_halo!, c, bc, loc, grid, Tuple(args); kwargs...)
launch!(arch, grid, KernelParameters(size, offset),
_fill_only_south_halo!, c, bc, loc, grid, Tuple(args); kwargs...)

fill_north_halo!(c, bc, size, offset, loc, arch, grid, args...; kwargs...) =
launch!(arch, grid, KernelParameters(size, offset), _fill_only_north_halo!, c, bc, loc, grid, Tuple(args); kwargs...)
launch!(arch, grid, KernelParameters(size, offset),
_fill_only_north_halo!, c, bc, loc, grid, Tuple(args); kwargs...)

fill_bottom_halo!(c, bc, size, offset, loc, arch, grid, args...; kwargs...) =
launch!(arch, grid, KernelParameters(size, offset), _fill_only_bottom_halo!, c, bc, loc, grid, Tuple(args); kwargs...)
launch!(arch, grid, KernelParameters(size, offset),
_fill_only_bottom_halo!, c, bc, loc, grid, Tuple(args); kwargs...)

fill_top_halo!(c, bc, size, offset, loc, arch, grid, args...; kwargs...) =
launch!(arch, grid, KernelParameters(size, offset), _fill_only_top_halo!, c, bc, loc, grid, Tuple(args); kwargs...)
launch!(arch, grid, KernelParameters(size, offset),
_fill_only_top_halo!, c, bc, loc, grid, Tuple(args); kwargs...)

#####
##### Kernel launchers for double-sided fill_halos
#####

fill_west_and_east_halo!(c, west_bc, east_bc, size, offset, loc, arch, grid, args...; kwargs...) =
launch!(arch, grid, KernelParameters(size, offset), _fill_west_and_east_halo!, c, west_bc, east_bc, loc, grid, Tuple(args); kwargs...)
function fill_west_and_east_halo!(c, west_bc, east_bc, size, offset, loc, arch, grid, args...; kwargs...)
return launch!(arch, grid, KernelParameters(size, offset),
_fill_west_and_east_halo!, c, west_bc, east_bc, loc, grid, Tuple(args); kwargs...)
end

fill_south_and_north_halo!(c, south_bc, north_bc, size, offset, loc, arch, grid, args...; kwargs...) =
launch!(arch, grid, KernelParameters(size, offset), _fill_south_and_north_halo!, c, south_bc, north_bc, loc, grid, Tuple(args); kwargs...)
function fill_south_and_north_halo!(c, south_bc, north_bc, size, offset, loc, arch, grid, args...; kwargs...)
return launch!(arch, grid, KernelParameters(size, offset),
_fill_south_and_north_halo!, c, south_bc, north_bc, loc, grid, Tuple(args); kwargs...)
end

fill_bottom_and_top_halo!(c, bottom_bc, top_bc, size, offset, loc, arch, grid, args...; kwargs...) =
launch!(arch, grid, KernelParameters(size, offset), _fill_bottom_and_top_halo!, c, bottom_bc, top_bc, loc, grid, Tuple(args); kwargs...)
function fill_bottom_and_top_halo!(c, bottom_bc, top_bc, size, offset, loc, arch, grid, args...; kwargs...)
return launch!(arch, grid, KernelParameters(size, offset),
_fill_bottom_and_top_halo!, c, bottom_bc, top_bc, loc, grid, Tuple(args); kwargs...)
end

#####
##### Calculate kernel size and offset for Windowed and Sliced Fields
Expand All @@ -331,7 +348,9 @@ const TBB = Union{typeof(fill_bottom_and_top_halo!), typeof(fill_bottom_halo!),
@inline fill_halo_size(::Tuple, ::SNB, args...) = :xz
@inline fill_halo_size(::Tuple, ::TBB, args...) = :xy

# If indices are colon, fill the whole boundary plane!
# If indices are colon, and locations are _not_ Nothing, fill the whole boundary plane!
# If locations are _Nothing_, then the kwarg `reduced_dimensions` will allow the size `:xz`
# to be correctly interpreted inside `launch!`.
@inline fill_halo_size(::OffsetArray, ::WEB, ::Tuple{<:Any, <:Colon, <:Colon}, args...) = :yz
@inline fill_halo_size(::OffsetArray, ::SNB, ::Tuple{<:Colon, <:Any, <:Colon}, args...) = :xz
@inline fill_halo_size(::OffsetArray, ::TBB, ::Tuple{<:Colon, <:Colon, <:Any}, args...) = :xy
Expand All @@ -343,21 +362,73 @@ const TBB = Union{typeof(fill_bottom_and_top_halo!), typeof(fill_bottom_halo!),
@inline whole_halo(::Colon, ::Nothing) = false
@inline whole_halo(::Colon, loc) = true

# Calculate kernel size
@inline fill_halo_size(c::OffsetArray, ::WEB, idx, bc, loc, grid) =
@inbounds (ifelse(whole_halo(idx[2], loc[2]), size(grid, 2), size(c, 2)), ifelse(whole_halo(idx[3], loc[3]), size(grid, 3), size(c, 3)))
@inline fill_halo_size(c::OffsetArray, ::SNB, idx, bc, loc, grid) =
@inbounds (ifelse(whole_halo(idx[1], loc[1]), size(grid, 1), size(c, 1)), ifelse(whole_halo(idx[3], loc[3]), size(grid, 3), size(c, 3)))
@inline fill_halo_size(c::OffsetArray, ::TBB, idx, bc, loc, grid) =
@inbounds (ifelse(whole_halo(idx[1], loc[1]), size(grid, 1), size(c, 1)), ifelse(whole_halo(idx[2], loc[2]), size(grid, 2), size(c, 2)))
# Calculate kernel size for windowed fields. This code is only called when
# one or more of the elements of `idx` is not Colon in the two direction perpendicular
# to the halo region and `bc` is not `PeriodicBoundaryCondition`.
@inline function fill_halo_size(c::OffsetArray, ::WEB, idx, bc, loc, grid)
@inbounds begin
whole_y_halo = whole_halo(idx[2], loc[2])
whole_z_halo = whole_halo(idx[3], loc[3])
end

_, Ny, Nz = size(grid)
_, Cy, Cz = size(c)

Sy = ifelse(whole_y_halo, Ny, Cy)
Sz = ifelse(whole_z_halo, Nz, Cz)

return (Sy, Sz)
end

@inline function fill_halo_size(c::OffsetArray, ::SNB, idx, bc, loc, grid)
@inbounds begin
whole_x_halo = whole_halo(idx[1], loc[1])
whole_z_halo = whole_halo(idx[3], loc[3])
end

Nx, _, Nz = size(grid)
Cx, _, Cz = size(c)

Sx = ifelse(whole_x_halo, Nx, Cx)
Sz = ifelse(whole_z_halo, Nz, Cz)

return (Sx, Sz)
end

@inline function fill_halo_size(c::OffsetArray, ::TBB, idx, bc, loc, grid)
@inbounds begin
whole_x_halo = whole_halo(idx[1], loc[1])
whole_y_halo = whole_halo(idx[2], loc[2])
end

Nx, Ny, _ = size(grid)
Cx, Cy, _ = size(c)

Sx = ifelse(whole_x_halo, Nx, Cx)
Sy = ifelse(whole_y_halo, Ny, Cy)

return (Sx, Sy)
end

# Remember that Periodic BCs also fill halo points!
@inline fill_halo_size(c::OffsetArray, ::WEB, idx, ::PBC, args...) = @inbounds size(c)[[2, 3]]
@inline fill_halo_size(c::OffsetArray, ::SNB, idx, ::PBC, args...) = @inbounds size(c)[[1, 3]]
@inline fill_halo_size(c::OffsetArray, ::TBB, idx, ::PBC, args...) = @inbounds size(c)[[1, 2]]
@inline fill_halo_size(c::OffsetArray, ::WEB, ::Tuple{<:Any, <:Colon, <:Colon}, ::PBC, args...) = @inbounds size(c)[[2, 3]]
@inline fill_halo_size(c::OffsetArray, ::SNB, ::Tuple{<:Colon, <:Any, <:Colon}, ::PBC, args...) = @inbounds size(c)[[1, 3]]
@inline fill_halo_size(c::OffsetArray, ::TBB, ::Tuple{<:Colon, <:Colon, <:Any}, ::PBC, args...) = @inbounds size(c)[[1, 2]]
@inline fill_halo_size(c::OffsetArray, ::WEB, idx, ::PBC, args...) = tuple(size(c, 2), size(c, 3))
@inline fill_halo_size(c::OffsetArray, ::SNB, idx, ::PBC, args...) = tuple(size(c, 1), size(c, 3))
@inline fill_halo_size(c::OffsetArray, ::TBB, idx, ::PBC, args...) = tuple(size(c, 1), size(c, 2))

@inline function fill_halo_size(c::OffsetArray, ::WEB, ::Tuple{<:Any, <:Colon, <:Colon}, ::PBC, args...)
_, Cy, Cz = size(c)
return (Cy, Cz)
end

@inline function fill_halo_size(c::OffsetArray, ::SNB, ::Tuple{<:Colon, <:Any, <:Colon}, ::PBC, args...)
Cx, _, Cz = size(c)
return (Cx, Cz)
end

@inline function fill_halo_size(c::OffsetArray, ::TBB, ::Tuple{<:Colon, <:Colon, <:Any}, ::PBC, args...)
Cx, Cy, _ = size(c)
return (Cx, Cy)
end

# The offsets are non-zero only if the indices are not Colon
@inline fill_halo_offset(::Symbol, args...) = (0, 0)
Expand Down
3 changes: 2 additions & 1 deletion src/Fields/abstract_field.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import Base: minimum, maximum, extrema
import Oceananigans: location, instantiated_location
import Oceananigans.Architectures: architecture
import Oceananigans.Grids: interior_x_indices, interior_y_indices, interior_z_indices
import Oceananigans.Grids: total_size, topology, nodes, xnodes, ynodes, znodes, xnode, ynode, znode
import Oceananigans.Grids: total_size, topology, nodes, xnodes, ynodes, znodes, node, xnode, ynode, znode
import Oceananigans.Utils: datatuple

const ArchOrNothing = Union{AbstractArchitecture, Nothing}
Expand Down Expand Up @@ -71,6 +71,7 @@ interior(f::AbstractField) = f
##### Coordinates of fields
#####

@propagate_inbounds node(i, j, k, ψ::AbstractField) = node(i, j, k, ψ.grid, instantiated_location(ψ)...)
@propagate_inbounds xnode(i, j, k, ψ::AbstractField) = xnode(i, j, k, ψ.grid, instantiated_location(ψ)...)
@propagate_inbounds ynode(i, j, k, ψ::AbstractField) = ynode(i, j, k, ψ.grid, instantiated_location(ψ)...)
@propagate_inbounds znode(i, j, k, ψ::AbstractField) = znode(i, j, k, ψ.grid, instantiated_location(ψ)...)
Expand Down
48 changes: 35 additions & 13 deletions src/Fields/field.jl
Original file line number Diff line number Diff line change
Expand Up @@ -488,11 +488,15 @@ const XYReducedField = Field{Nothing, Nothing, <:Any}

const XYZReducedField = Field{Nothing, Nothing, Nothing}

const ReducedField = Union{XReducedField, YReducedField, ZReducedField,
YZReducedField, XZReducedField, XYReducedField,
const ReducedField = Union{XReducedField,
YReducedField,
ZReducedField,
YZReducedField,
XZReducedField,
XYReducedField,
XYZReducedField}

reduced_dimensions(field::Field) = ()
reduced_dimensions(field::Field) = ()
reduced_dimensions(field::XReducedField) = tuple(1)
reduced_dimensions(field::YReducedField) = tuple(2)
reduced_dimensions(field::ZReducedField) = tuple(3)
Expand Down Expand Up @@ -551,6 +555,24 @@ end
##### Field reductions
#####

const XReducedAbstractField = AbstractField{Nothing}
const YReducedAbstractField = AbstractField{<:Any, Nothing}
const ZReducedAbstractField = AbstractField{<:Any, <:Any, Nothing}

const YZReducedAbstractField = AbstractField{<:Any, Nothing, Nothing}
const XZReducedAbstractField = AbstractField{Nothing, <:Any, Nothing}
const XYReducedAbstractField = AbstractField{Nothing, Nothing, <:Any}

const XYZReducedAbstractField = AbstractField{Nothing, Nothing, Nothing}

const ReducedAbstractField = Union{XReducedAbstractField,
YReducedAbstractField,
ZReducedAbstractField,
YZReducedAbstractField,
XZReducedAbstractField,
XYReducedAbstractField,
XYZReducedAbstractField}

# TODO: needs test
Statistics.dot(a::Field, b::Field) = mapreduce((x, y) -> x * y, +, interior(a), interior(b))

Expand All @@ -563,12 +585,12 @@ const MinimumReduction = typeof(Base.minimum!)
const AllReduction = typeof(Base.all!)
const AnyReduction = typeof(Base.any!)

initialize_reduced_field!(::SumReduction, f, r::ReducedField, c) = Base.initarray!(interior(r), f, Base.add_sum, true, interior(c))
initialize_reduced_field!(::ProdReduction, f, r::ReducedField, c) = Base.initarray!(interior(r), f, Base.mul_prod, true, interior(c))
initialize_reduced_field!(::AllReduction, f, r::ReducedField, c) = Base.initarray!(interior(r), f, &, true, interior(c))
initialize_reduced_field!(::AnyReduction, f, r::ReducedField, c) = Base.initarray!(interior(r), f, |, true, interior(c))
initialize_reduced_field!(::MaximumReduction, f, r::ReducedField, c) = Base.mapfirst!(f, interior(r), interior(c))
initialize_reduced_field!(::MinimumReduction, f, r::ReducedField, c) = Base.mapfirst!(f, interior(r), interior(c))
initialize_reduced_field!(::SumReduction, f, r::ReducedAbstractField, c) = Base.initarray!(interior(r), f, Base.add_sum, true, interior(c))
initialize_reduced_field!(::ProdReduction, f, r::ReducedAbstractField, c) = Base.initarray!(interior(r), f, Base.mul_prod, true, interior(c))
initialize_reduced_field!(::AllReduction, f, r::ReducedAbstractField, c) = Base.initarray!(interior(r), f, &, true, interior(c))
initialize_reduced_field!(::AnyReduction, f, r::ReducedAbstractField, c) = Base.initarray!(interior(r), f, |, true, interior(c))
initialize_reduced_field!(::MaximumReduction, f, r::ReducedAbstractField, c) = Base.mapfirst!(f, interior(r), interior(c))
initialize_reduced_field!(::MinimumReduction, f, r::ReducedAbstractField, c) = Base.mapfirst!(f, interior(r), interior(c))

filltype(f, c) = eltype(c)
filltype(::Union{AllReduction, AnyReduction}, grid) = Bool
Expand Down Expand Up @@ -624,7 +646,7 @@ for reduction in (:sum, :maximum, :minimum, :all, :any, :prod)

# In-place
function Base.$(reduction!)(f::Function,
r::ReducedField,
r::ReducedAbstractField,
a::AbstractField;
condition = nothing,
mask = get_neutral_mask(Base.$(reduction!)),
Expand All @@ -636,7 +658,7 @@ for reduction in (:sum, :maximum, :minimum, :all, :any, :prod)
kwargs...)
end

function Base.$(reduction!)(r::ReducedField,
function Base.$(reduction!)(r::ReducedAbstractField,
a::AbstractField;
condition = nothing,
mask = get_neutral_mask(Base.$(reduction!)),
Expand Down Expand Up @@ -689,15 +711,15 @@ end
Statistics.mean(f::Function, c::AbstractField; condition = nothing, dims=:) = Statistics._mean(f, c, dims; condition)
Statistics.mean(c::AbstractField; condition = nothing, dims=:) = Statistics._mean(identity, c, dims; condition)

function Statistics.mean!(f::Function, r::ReducedField, a::AbstractField; condition = nothing, mask = 0)
function Statistics.mean!(f::Function, r::ReducedAbstractField, a::AbstractField; condition = nothing, mask = 0)
sum!(f, r, a; condition, mask, init=true)
dims = reduced_dimension(location(r))
n = conditional_length(condition_operand(f, a, condition, mask), dims)
r ./= n
return r
end

Statistics.mean!(r::ReducedField, a::AbstractArray; kwargs...) = Statistics.mean!(identity, r, a; kwargs...)
Statistics.mean!(r::ReducedAbstractField, a::AbstractArray; kwargs...) = Statistics.mean!(identity, r, a; kwargs...)

function Statistics.norm(a::AbstractField; condition = nothing)
r = zeros(a.grid, 1)
Expand Down
2 changes: 2 additions & 0 deletions src/Fields/field_tuples.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ using Oceananigans.BoundaryConditions: FieldBoundaryConditions, regularize_field
##### `fill_halo_regions!` for tuples of `Field`
#####

@inline flattened_unique_values(::Tuple{}) = tuple()

"""
flattened_unique_values(a::NamedTuple)
Expand Down
Loading

2 comments on commit f251196

@navidcy
Copy link
Collaborator

Choose a reason for hiding this comment

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

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

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

Registration pull request created: JuliaRegistries/General/101776

Tip: Release Notes

Did you know you can add release notes too? Just add markdown formatted text underneath the comment after the text
"Release notes:" and it will be added to the registry PR, and if TagBot is installed it will also be added to the
release that TagBot creates. i.e.

@JuliaRegistrator register

Release notes:

## Breaking changes

- blah

To add them here just re-invoke and the PR will be updated.

Tagging

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.90.8 -m "<description of version>" f2511962ca15f3aaf87d2571e3551e59dc05c694
git push origin v0.90.8

Please sign in to comment.