Skip to content

Commit dbb67af

Browse files
navidcyglwagnertaimoorsohail
authored
Add section in the docs for reduced operations (#4235)
* add section for reduced operations * Apply suggestions from code review Co-authored-by: Gregory L. Wagner <[email protected]> * apply review suggestions * Apply suggestions from code review Co-authored-by: Gregory L. Wagner <[email protected]> * Update docs/src/operations.md Co-authored-by: Gregory L. Wagner <[email protected]> * Update docs/src/operations.md Co-authored-by: Gregory L. Wagner <[email protected]> * Apply suggestions from code review * few tweaks * no need for CPU() arg in grid * some space * few tweaks * add a sentence about reductions * fix latex formatting in docstrings we might as well, since we are at it.. * fix Reduction ref * Some typo fixes and added note about face fields * Apply suggestions from code review * showcase that condition can get an array of bools * slight rephrase * move the comment up * remove duplicate broadcasting --------- Co-authored-by: Gregory L. Wagner <[email protected]> Co-authored-by: Taimoor Sohail <[email protected]>
1 parent aba99f7 commit dbb67af

File tree

3 files changed

+152
-38
lines changed

3 files changed

+152
-38
lines changed

docs/src/operations.md

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ BinaryOperation at (Center, Center, Center)
6767
   └── 1
6868
```
6969

70-
Like `Field`s, `AbstractOperations` have a location and a grid.
70+
Like `Field`s, `AbstractOperations` have a location and a grid.
7171
In addition to `BinaryOperation`s like the kind above, `UnaryOperation`s and `MultiaryOperation`s are also supported,
7272

7373
```jldoctest operations
@@ -211,3 +211,117 @@ BinaryOperation at (Center, Center, Center)
211211
      │   └── 4×1×4 Field{Center, Center, Center} on RectilinearGrid on CPU
212212
      └── 2
213213
```
214+
215+
## Averages and integrals
216+
217+
An operation that reduces one or more dimensions of a field is called a [`Reduction`](@ref); some reductions include [`Average`](@ref), [`Integral`](@ref), and [`CumulativeIntegral`](@ref).
218+
219+
Let's demonstrate now how we can compute the average or the integral of a field over the grid or over some part of the grid.
220+
We start by creating a latitude-longitude grid that only goes up to 30 degrees latitude.
221+
Conveniently, with this latitude extent that grid covers half the total area of the sphere, i.e., `2π * grid.radius^2`.
222+
223+
Let's try to estimate this area using `Integral` operation.
224+
We create a Field, we fill it with ones and we integrate it over the whole grid.
225+
We use a `CenterField` for the example below, but all location combinations work similarly.
226+
227+
228+
```jldoctest operations_avg_int
229+
using Oceananigans
230+
231+
grid = LatitudeLongitudeGrid(size=(60, 10, 5),
232+
latitude = (-30, 30),
233+
longitude = (0, 360),
234+
z = (-1000, 0))
235+
236+
c = CenterField(grid)
237+
set!(c, 1)
238+
239+
∫c = Field(Integral(c, dims=(1, 2)))
240+
241+
# output
242+
1×1×5 Field{Nothing, Nothing, Center} reduced over dims = (1, 2) on LatitudeLongitudeGrid on CPU
243+
├── data: OffsetArrays.OffsetArray{Float64, 3, Array{Float64, 3}}, size: (1, 1, 5)
244+
├── grid: 60×10×5 LatitudeLongitudeGrid{Float64, Periodic, Bounded, Bounded} on CPU with 3×3×3 halo and with precomputed metrics
245+
├── operand: Integral of BinaryOperation at (Center, Center, Center) over dims (1, 2)
246+
├── status: time=0.0
247+
└── data: 1×1×11 OffsetArray(::Array{Float64, 3}, 1:1, 1:1, -2:8) with eltype Float64 with indices 1:1×1:1×-2:8
248+
└── max=0.0, min=0.0, mean=0.0
249+
```
250+
251+
A few remarks: note that the `∫c` has locations `Nothing, Nothing, Center`; this is because we have integrated in the first two dimensions and thus it's `reduced over dims = (1, 2)`.
252+
Further note that `∫c` is full of zeros; its max, min, and mean values are all 0.
253+
No computation has been done yet.
254+
To compute `∫c`, we call [`compute!`](@ref),
255+
256+
```jldoctest operations_avg_int
257+
compute!(∫c)
258+
259+
# output
260+
1×1×5 Field{Nothing, Nothing, Center} reduced over dims = (1, 2) on LatitudeLongitudeGrid on CPU
261+
├── data: OffsetArrays.OffsetArray{Float64, 3, Array{Float64, 3}}, size: (1, 1, 5)
262+
├── grid: 60×10×5 LatitudeLongitudeGrid{Float64, Periodic, Bounded, Bounded} on CPU with 3×3×3 halo and with precomputed metrics
263+
├── operand: Integral of BinaryOperation at (Center, Center, Center) over dims (1, 2)
264+
├── status: time=0.0
265+
└── data: 1×1×11 OffsetArray(::Array{Float64, 3}, 1:1, 1:1, -2:8) with eltype Float64 with indices 1:1×1:1×-2:8
266+
└── max=2.55032e14, min=2.55032e14, mean=2.55032e14
267+
```
268+
269+
Above we see that the max, min and mean of the field are all the same.
270+
Let's check that these values are what we expect:
271+
272+
```jldoctest operations_avg_int
273+
∫c[1, 1, 1] ≈ 2π * grid.radius^2 # area of spherical zone with |φ| ≤ 30ᵒ
274+
275+
# output
276+
277+
true
278+
```
279+
280+
We can further have conditional reduced operations. Let's compute the above integral but only for North hemisphere.
281+
282+
First we need to define a condition which is a function with arguments `(i, j, k, grid, field)` that returns true or false.
283+
In this example we use `Oceananigans.Grids.φnode` to check whether the latitude is within the range we'd like.
284+
285+
```jldoctest operations_avg_int
286+
using Oceananigans.Grids: φnode
287+
288+
cond(i, j, k, grid, c) = φnode(j, grid, Center()) ≥ 0
289+
290+
conditional_∫c = Field(Integral(c, dims=(1, 2), condition=cond)) # integrate only when condition is true
291+
292+
# output
293+
1×1×5 Field{Nothing, Nothing, Center} reduced over dims = (1, 2) on LatitudeLongitudeGrid on CPU
294+
├── data: OffsetArrays.OffsetArray{Float64, 3, Array{Float64, 3}}, size: (1, 1, 5)
295+
├── grid: 60×10×5 LatitudeLongitudeGrid{Float64, Periodic, Bounded, Bounded} on CPU with 3×3×3 halo and with precomputed metrics
296+
├── operand: Integral of ConditionalOperation of BinaryOperation at (Center, Center, Center) with condition cond (generic function with 1 method) over dims (1, 2)
297+
├── status: time=0.0
298+
└── data: 1×1×11 OffsetArray(::Array{Float64, 3}, 1:1, 1:1, -2:8) with eltype Float64 with indices 1:1×1:1×-2:8
299+
└── max=0.0, min=0.0, mean=0.0
300+
```
301+
302+
Above we have attached a condition to the operand.
303+
Now the operand is applied only when the condition is true.
304+
Let's compute and see if we get 1/4 of the area of the sphere
305+
306+
```jldoctest operations_avg_int
307+
compute!(conditional_∫c)
308+
conditional_∫c[1, 1, 1] ≈ π * grid.radius^2 # area of spherical zone with 0ᵒ ≤ φ ≤ 30ᵒ
309+
310+
# output
311+
true
312+
```
313+
314+
Another way to do the above is to provide the `condition` keyword argument with an array of booleans.
315+
316+
```jldoctest operations_avg_int
317+
cond_array = trues(size(grid))
318+
cond_array[:, 1:5, :] .= false # set the first half of the latitude range to false
319+
320+
conditional_∫c = Field(Integral(c, dims=(1, 2), condition=cond_array))
321+
322+
compute!(conditional_∫c)
323+
conditional_∫c[1, 1, 1] ≈ π * grid.radius^2 # area of spherical zone with 0ᵒ ≤ φ ≤ 30ᵒ
324+
325+
# output
326+
true
327+
```

src/Models/HydrostaticFreeSurfaceModels/SplitExplicitFreeSurfaces/split_explicit_free_surface.jl

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Oceananigans.Grids: on_architecture
44

55
struct SplitExplicitFreeSurface{H, U, M, FT, K , S, T} <: AbstractFreeSurface{H, FT}
66
η :: H
7-
barotropic_velocities :: U # A namedtuple with U, V
7+
barotropic_velocities :: U # A namedtuple with U, V
88
filtered_state :: M # A namedtuple with η, U, V averaged throughout the substepping
99
gravitational_acceleration :: FT
1010
kernel_parameters :: K
@@ -21,20 +21,20 @@ end
2121
averaging_kernel = averaging_shape_function,
2222
timestepper = ForwardBackwardScheme())
2323
24-
Return a `SplitExplicitFreeSurface` representing an explicit time discretization of
24+
Return a `SplitExplicitFreeSurface` representing an explicit time discretization of
2525
a free surface dynamics with `gravitational_acceleration`. The free surface dynamics are solved by discretizing:
2626
```math
27-
\begin{gather}
28-
∂_t η = - \nabla ⋅ U, \\
29-
∂_t U = - g H \nabla η + G^U,
30-
\end{gather}
27+
\\begin{gather}
28+
∂_t η = - \\nabla ⋅ U, \\\\
29+
∂_t U = - g H \\nabla η + G^U,
30+
\\end{gather}
3131
```
32-
where ``η`` is the free surface displacement, ``U`` is the barotropic velocity vector, calculated as the vertical integral
33-
of the velocity field ``u`` and ``v``, ``H`` is the column depth, ``G^U`` is the slow forcing calculated as the integral of the
34-
tendency of ``u`` and ``v``, and ``g`` is the gravitational acceleration.
32+
where ``η`` is the free surface displacement, ``U`` is the barotropic velocity vector, calculated as the vertical integral
33+
of the velocity field ``u`` and ``v``, ``H`` is the column depth, ``G^U`` is the slow forcing calculated as the integral of the
34+
tendency of ``u`` and ``v``, and ``g`` is the gravitational acceleration.
3535
36-
The discretized equations are solved within a baroclinic timestep (``Δt``) by substepping with a ``Δτ < Δt``.
37-
The barotropic velocities are filtered throughout the substepping and, finally,
36+
The discretized equations are solved within a baroclinic timestep (``Δt``) by substepping with a ``Δτ < Δt``.
37+
The barotropic velocities are filtered throughout the substepping and, finally,
3838
the barotropic mode of the velocities at the new time step is corrected with the filtered velocities.
3939
4040
Keyword Arguments
@@ -55,7 +55,7 @@ Keyword Arguments
5555
5656
!!! info "Needed keyword arguments"
5757
Either `substeps` _or_ `cfl` need to be prescribed.
58-
58+
5959
When `cfl` is prescribed then `grid` is also required as a positional argument.
6060
6161
- `fixed_Δt`: The maximum baroclinic timestep allowed. If `fixed_Δt` is a `nothing` and a cfl is provided,
@@ -124,21 +124,21 @@ function split_explicit_substepping(::Nothing, substeps, fixed_Δt, grid, averag
124124
end
125125

126126
# The substeps are calculated dynamically when a cfl without a fixed_Δt is provided
127-
function split_explicit_substepping(cfl, ::Nothing, ::Nothing, grid, averaging_kernel, gravitational_acceleration)
127+
function split_explicit_substepping(cfl, ::Nothing, ::Nothing, grid, averaging_kernel, gravitational_acceleration)
128128
cfl = convert(eltype(grid), cfl)
129129
return FixedTimeStepSize(grid; cfl, averaging_kernel)
130130
end
131131

132132
# The number of substeps are calculated based on the cfl and the fixed_Δt
133133
function split_explicit_substepping(cfl, ::Nothing, fixed_Δt, grid, averaging_kernel, gravitational_acceleration)
134-
substepping = split_explicit_substepping(cfl, nothing, nothing, grid, averaging_kernel, gravitational_acceleration)
134+
substepping = split_explicit_substepping(cfl, nothing, nothing, grid, averaging_kernel, gravitational_acceleration)
135135
substeps = ceil(Int, 2 * fixed_Δt / substepping.Δt_barotropic)
136-
substepping = split_explicit_substepping(nothing, substeps, nothing, grid, averaging_kernel, gravitational_acceleration)
136+
substepping = split_explicit_substepping(nothing, substeps, nothing, grid, averaging_kernel, gravitational_acceleration)
137137
return substepping
138138
end
139139

140140
# TODO: When open boundary conditions are online
141-
# We need to calculate the barotropic boundary conditions
141+
# We need to calculate the barotropic boundary conditions
142142
# from the baroclinic boundary conditions by integrating the BC upwards
143143
@inline west_barotropic_bc(baroclinic_velocity) = baroclinic_velocity.boundary_conditions.west
144144
@inline east_barotropic_bc(baroclinic_velocity) = baroclinic_velocity.boundary_conditions.east
@@ -202,7 +202,7 @@ function materialize_free_surface(free_surface::SplitExplicitFreeSurface, veloci
202202
timestepper)
203203
end
204204

205-
# (p = 2, q = 4, r = 0.18927) minimize dispersion error from Shchepetkin and McWilliams (2005): https://doi.org/10.1016/j.ocemod.2004.08.002
205+
# (p = 2, q = 4, r = 0.18927) minimize dispersion error from Shchepetkin and McWilliams (2005): https://doi.org/10.1016/j.ocemod.2004.08.002
206206
@inline function averaging_shape_function::FT; p = 2, q = 4, r = FT(0.18927)) where FT
207207
τ₀ = (p + 2) * (p + q + 2) / (p + 1) / (p + q + 1)
208208

@@ -274,7 +274,7 @@ Base.show(io::IO, sefs::SplitExplicitFreeSurface) = print(io, "$(summary(sefs))\
274274
maybe_extend_halos(TX, TY, grid, ::FixedTimeStepSize) = grid
275275

276276
function maybe_extend_halos(TX, TY, grid, substepping::FixedSubstepNumber)
277-
277+
278278
old_halos = halo_size(grid)
279279
Nsubsteps = length(substepping.averaging_weights)
280280
step_halo = Nsubsteps + 1

src/Models/HydrostaticFreeSurfaceModels/SplitExplicitFreeSurfaces/split_explicit_timesteppers.jl

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22
struct ForwardBackwardScheme
33
44
A timestepping scheme used for substepping in the split-explicit free surface solver.
5-
5+
66
The equations are evolved as follows:
77
```math
8-
\begin{gather}
9-
η^{m+1} = η^m - Δτ (∂_x U^m + ∂_y V^m), \\
10-
U^{m+1} = U^m - Δτ (∂_x η^{m+1} - G^U), \\
8+
\\begin{gather}
9+
η^{m+1} = η^m - Δτ (∂_x U^m + ∂_y V^m), \\\\
10+
U^{m+1} = U^m - Δτ (∂_x η^{m+1} - G^U), \\\\
1111
V^{m+1} = V^m - Δτ (∂_y η^{m+1} - G^V).
12-
\end{gather}
12+
\\end{gather}
1313
```
1414
"""
1515
struct ForwardBackwardScheme end
@@ -50,31 +50,31 @@ free surface at time-step `m + 1/2`:
5050
The equations are evolved as follows:
5151
5252
```math
53-
\begin{gather}
54-
η^{m+1} = η^m - Δτ g H (∂_x Ũ + ∂y Ṽ), \\
55-
U^{m+1} = U^m - Δτ (∂_x η̃ - G^U), \\
53+
\\begin{gather}
54+
η^{m+1} = η^m - Δτ g H (∂_x Ũ + ∂y Ṽ), \\\\
55+
U^{m+1} = U^m - Δτ (∂_x η̃ - G^U), \\\\
5656
V^{m+1} = V^m - Δτ (∂_y η̃ - G^V),
57-
\end{gather}
58-
```
57+
\\end{gather}
58+
```
5959
60-
where `η̃`, `Ũ` and `Ṽ` are the AB3 time-extrapolated values of free surface,
60+
where `η̃`, `Ũ` and `Ṽ` are the AB3 time-extrapolated values of free surface,
6161
barotropic zonal and meridional velocities, respectively:
6262
6363
```math
64-
\begin{gather}
65-
Ũ = α U^m + θ U^{m-1} + β U^{m-2}, \\
66-
Ṽ = α V^m + θ V^{m-1} + β V^{m-2}, \\
64+
\\begin{gather}
65+
Ũ = α U^m + θ U^{m-1} + β U^{m-2}, \\\\
66+
Ṽ = α V^m + θ V^{m-1} + β V^{m-2}, \\\\
6767
η̃ = δ η^{m+1} + μ η^m + γ η^{m-1} + ϵ η^{m-2}.
68-
\end{gather}
68+
\\end{gather}
6969
```
7070
71-
The default values for the time-extrapolation coefficients, described by [Shchepetkin2005](@citet),
71+
The default values for the time-extrapolation coefficients, described by [Shchepetkin2005](@citet),
7272
correspond to the best stability range for the AB3 algorithm.
7373
"""
74-
AdamsBashforth3Scheme(; β = 0.281105, α = 1.5 + β, θ = - 0.5 - 2β, γ = 0.088, δ = 0.614, ϵ = 0.013, μ = 1 - δ - γ - ϵ) =
74+
AdamsBashforth3Scheme(; β = 0.281105, α = 1.5 + β, θ = - 0.5 - 2β, γ = 0.088, δ = 0.614, ϵ = 0.013, μ = 1 - δ - γ - ϵ) =
7575
AdamsBashforth3Scheme(nothing, nothing, nothing, nothing, nothing, nothing, nothing, β, α, θ, γ, δ, ϵ, μ)
7676

77-
Adapt.adapt_structure(to, t::AdamsBashforth3Scheme) =
77+
Adapt.adapt_structure(to, t::AdamsBashforth3Scheme) =
7878
AdamsBashforth3Scheme(
7979
Adapt.adapt(to, t.ηᵐ ),
8080
Adapt.adapt(to, t.ηᵐ⁻¹),
@@ -104,7 +104,7 @@ function materialize_timestepper(t::AdamsBashforth3Scheme, grid, free_surface, v
104104
δ = convert(FT, t.δ)
105105
ϵ = convert(FT, t.ϵ)
106106
μ = convert(FT, t.μ)
107-
107+
108108
return AdamsBashforth3Scheme(ηᵐ, ηᵐ⁻¹, ηᵐ⁻², Uᵐ⁻¹, Uᵐ⁻², Vᵐ⁻¹, Vᵐ⁻², β, α, θ, γ, δ, ϵ, μ)
109109
end
110110

0 commit comments

Comments
 (0)