Skip to content

Commit c508d1c

Browse files
authored
Add reverse option to column integral operators
Add a `reverse` keyword to `column_reduce!` and `column_accumulate!` so vertical reductions and accumulations can proceed from the top of a column downward. Extend the operator tests to cover forward and reverse traversal across center-to-center, face-to-face, center-to-face, and face-to-center accumulations, and document the new behavior. This description was generated by Codex.
1 parent 643ce6f commit c508d1c

3 files changed

Lines changed: 86 additions & 38 deletions

File tree

ext/cuda/operators_integral.jl

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ function column_reduce_device!(
1717
input,
1818
init,
1919
space,
20+
reverse,
2021
) where {F, T}
2122
Ni, Nj, _, _, Nh = size(Fields.field_values(output))
2223
us = UniversalSize(Fields.field_values(output))
@@ -36,6 +37,7 @@ function column_reduce_device!(
3637
us,
3738
mask,
3839
cart_inds,
40+
reverse,
3941
)
4042
nitems = Ni * Nj * Nh
4143
threads = threads_via_occupancy(bycolumn_kernel!, args)
@@ -49,7 +51,7 @@ function column_reduce_device!(
4951
)
5052
call_post_op_callback() && post_op_callback(
5153
output,
52-
(dev, f, transform, output, input, init, space),
54+
(dev, f, transform, output, input, init, space, reverse),
5355
(;),
5456
)
5557
end
@@ -62,6 +64,7 @@ function column_accumulate_device!(
6264
input,
6365
init,
6466
space,
67+
reverse,
6568
) where {F, T}
6669
out_fv = Fields.field_values(output)
6770
mask = Spaces.get_mask(space)
@@ -81,6 +84,7 @@ function column_accumulate_device!(
8184
us,
8285
mask,
8386
cart_inds,
87+
reverse,
8488
)
8589
(Ni, Nj, _, _, Nh) = DataLayouts.universal_size(us)
8690
nitems = Ni * Nj * Nh
@@ -106,9 +110,10 @@ function bycolumn_kernel!(
106110
us::DataLayouts.UniversalSize,
107111
mask,
108112
cart_inds,
113+
reverse,
109114
) where {S, F, T}
110115
if space isa Spaces.FiniteDifferenceSpace
111-
single_column_function!(f, transform, output, input, init, space)
116+
single_column_function!(f, transform, output, input, init, space, reverse)
112117
else
113118
tidx = linear_thread_idx()
114119
if linear_is_valid_index(tidx, us) && tidx length(unval(cart_inds))
@@ -123,6 +128,7 @@ function bycolumn_kernel!(
123128
column(input, i, j, h),
124129
init,
125130
column(space, i, j, h),
131+
reverse,
126132
)
127133
end
128134
end

src/Operators/integrals.jl

Lines changed: 49 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ Analogue of `Base._InitialValue` for `column_reduce!` and `column_accumulate!`.
107107
struct UnspecifiedInit end
108108

109109
"""
110-
column_reduce!(f, output, input; [init], [transform])
110+
column_reduce!(f, output, input; [init], [transform], [reverse])
111111
112112
Applies `reduce` to `input` along the vertical direction, storing the result in
113113
`output`. The `input` can be either a `Field` or an `AbstractBroadcasted` that
@@ -116,10 +116,12 @@ computed by iteratively applying `f` to the values in `input`, starting from the
116116
bottom of each column and moving upward, and the result of the final iteration
117117
is passed to the `transform` function before being stored in `output`. If `init`
118118
is specified, it is used as the initial value of the iteration; otherwise, the
119-
value at the bottom of each column in `input` is used as the initial value.
119+
value at the starting boundary of each column in `input` is used as the initial
120+
value. By default, reduction starts at the bottom boundary and proceeds upward.
121+
When `reverse = true`, it starts at the top boundary and proceeds downward.
120122
121123
With `first_level` and `last_level` denoting the indices of the boundary levels
122-
of `input`, the reduction in each column can be summarized as follows:
124+
of `input`, the default reduction in each column can be summarized as follows:
123125
- If `init` is unspecified,
124126
```
125127
reduced_value = input[first_level]
@@ -143,10 +145,11 @@ function column_reduce!(
143145
input::Union{Fields.Field, PointwiseOrColumnwiseBroadcasted};
144146
init = UnspecifiedInit(),
145147
transform::T = identity,
148+
reverse::Bool = false,
146149
) where {F, T}
147150
device = ClimaComms.device(output)
148151
space = axes(input)
149-
column_reduce_device!(device, f, transform, output, input, init, space)
152+
column_reduce_device!(device, f, transform, output, input, init, space, reverse)
150153
end
151154

152155
function column_reduce_device!(
@@ -157,11 +160,12 @@ function column_reduce_device!(
157160
input,
158161
init,
159162
space,
163+
reverse,
160164
) where {F, T}
161165
mask = Spaces.get_mask(space)
162166
if space isa Spaces.FiniteDifferenceSpace
163167
@assert mask isa DataLayouts.NoMask
164-
single_column_reduce!(f, transform, output, input, init, space)
168+
single_column_reduce!(f, transform, output, input, init, space, reverse)
165169
else
166170
Fields.bycolumn(space) do colidx
167171
I = Fields.universal_index(colidx)
@@ -173,6 +177,7 @@ function column_reduce_device!(
173177
input[colidx],
174178
init,
175179
space[colidx],
180+
reverse,
176181
)
177182
end
178183
end
@@ -187,37 +192,45 @@ function single_column_reduce!(
187192
_input,
188193
init,
189194
space,
195+
reverse,
190196
) where {F, T}
191197
first_level = left_idx(space)
192198
last_level = right_idx(space)
199+
start_level, stop_level, direction =
200+
reverse ? (last_level, first_level, -1) : (first_level, last_level, 1)
193201
@inbounds if init == UnspecifiedInit()
194-
reduced_value = get_level_value(space, _input, first_level)
195-
next_level = first_level + 1
202+
reduced_value = get_level_value(space, _input, start_level)
203+
next_level = start_level + direction
196204
else
197205
reduced_value = init
198-
next_level = first_level
206+
next_level = start_level
199207
end
200-
@inbounds for level in next_level:last_level
208+
n_steps = direction * (stop_level - next_level) + 1
209+
@inbounds for i in 1:n_steps
210+
level = next_level + direction * (i - 1)
201211
reduced_value = f(reduced_value, get_level_value(space, _input, level))
202212
end
203213
Fields.field_values(_output)[] = transform(reduced_value)
204214
return nothing
205215
end
206216

207217
"""
208-
column_accumulate!(f, output, input; [init], [transform])
218+
column_accumulate!(f, output, input; [init], [transform], [reverse])
209219
210220
Applies `accumulate` to `input` along the vertical direction, storing the result
211221
in `output`. The `input` can be either a `Field` or an `AbstractBroadcasted`
212-
that performs pointwise or columnwise operations on `Field`s. Each accumulated
213-
value is computed by iteratively applying `f` to the values in `input`, starting
214-
from the bottom of each column and moving upward, and the result of each
215-
iteration is passed to the `transform` function before being stored in `output`.
222+
that performs pointwise or columnwise operations on `Field`s. By default, each
223+
accumulated value is computed by iteratively applying `f` to the values in
224+
`input`, starting from the bottom of each column and moving upward, and the
225+
result of each iteration is passed to the `transform` function before being
226+
stored in `output`.
216227
The `init` value is is optional for center-to-center, face-to-face, and
217228
face-to-center accumulation, but it is required for center-to-face accumulation.
229+
When `reverse = true`, accumulation starts at the top boundary and proceeds
230+
downward, with the corresponding staggered boundary offsets reversed.
218231
219232
With `first_level` and `last_level` denoting the indices of the boundary levels
220-
of `input`, the accumulation in each column can be summarized as follows:
233+
of `input`, the default accumulation in each column can be summarized as follows:
221234
- For center-to-center and face-to-face accumulation with `init` unspecified,
222235
```
223236
accumulated_value = input[first_level]
@@ -267,14 +280,15 @@ function column_accumulate!(
267280
input::Union{Fields.Field, PointwiseOrColumnwiseBroadcasted};
268281
init = UnspecifiedInit(),
269282
transform::T = identity,
283+
reverse::Bool = false,
270284
) where {F, T}
271285
device = ClimaComms.device(output)
272286
space = axes(input)
273287
init == UnspecifiedInit() &&
274288
Spaces.staggering(space) == Spaces.CellCenter() &&
275289
Spaces.staggering(axes(output)) == Spaces.CellFace() &&
276290
error("init must be specified for center-to-face accumulation")
277-
column_accumulate_device!(device, f, transform, output, input, init, space)
291+
column_accumulate_device!(device, f, transform, output, input, init, space, reverse)
278292
end
279293

280294
function column_accumulate_device!(
@@ -285,11 +299,12 @@ function column_accumulate_device!(
285299
input,
286300
init,
287301
space,
302+
reverse,
288303
) where {F, T}
289304
mask = Spaces.get_mask(space)
290305
if space isa Spaces.FiniteDifferenceSpace
291306
@assert mask isa DataLayouts.NoMask
292-
single_column_accumulate!(f, transform, output, input, init, space)
307+
single_column_accumulate!(f, transform, output, input, init, space, reverse)
293308
else
294309
Fields.bycolumn(space) do colidx
295310
I = Fields.universal_index(colidx)
@@ -301,6 +316,7 @@ function column_accumulate_device!(
301316
input[colidx],
302317
init,
303318
space[colidx],
319+
reverse,
304320
)
305321
end
306322
end
@@ -315,6 +331,7 @@ function single_column_accumulate!(
315331
_input,
316332
init,
317333
space,
334+
reverse,
318335
) where {F, T}
319336
device = ClimaComms.device(space)
320337
first_level = left_idx(space)
@@ -323,25 +340,32 @@ function single_column_accumulate!(
323340
is_c2c_or_f2f = Spaces.staggering(space) == Spaces.staggering(axes(output))
324341
is_c2f = !is_c2c_or_f2f && Spaces.staggering(space) == Spaces.CellCenter()
325342
is_f2c = !is_c2c_or_f2f && !is_c2f
343+
# With `reverse = true`, start at the top boundary, step downward, and
344+
# reverse the center/face half-level offset used by staggered accumulation.
345+
start_level, stop_level, direction =
346+
reverse ? (last_level, first_level, -1) : (first_level, last_level, 1)
347+
stagger = reverse ? -half : half
326348
@inbounds if init == UnspecifiedInit()
327349
@assert !is_c2f
328-
accumulated_value = get_level_value(space, _input, first_level)
329-
next_level = first_level + 1
330-
init_output_level = is_c2c_or_f2f ? first_level : nothing
350+
accumulated_value = get_level_value(space, _input, start_level)
351+
next_level = start_level + direction
352+
init_output_level = is_c2c_or_f2f ? start_level : nothing
331353
else
332354
accumulated_value =
333-
is_f2c ? f(init, get_level_value(space, _input, first_level)) : init
334-
next_level = is_f2c ? first_level + 1 : first_level
335-
init_output_level = is_c2f ? first_level - half : nothing
355+
is_f2c ? f(init, get_level_value(space, _input, start_level)) : init
356+
next_level = is_f2c ? start_level + direction : start_level
357+
init_output_level = is_c2f ? start_level - stagger : nothing
336358
end
337359
@inbounds if !isnothing(init_output_level)
338360
Fields.level(output, init_output_level)[] = transform(accumulated_value)
339361
end
340-
@inbounds for level in next_level:last_level
362+
n_steps = direction * (stop_level - next_level) + 1
363+
@inbounds for i in 1:n_steps
364+
level = next_level + direction * (i - 1)
341365
accumulated_value =
342366
f(accumulated_value, get_level_value(space, _input, level))
343367
output_level =
344-
is_c2c_or_f2f ? level : (is_c2f ? level + half : level - half)
368+
is_c2c_or_f2f ? level : (is_c2f ? level + stagger : level - stagger)
345369
Fields.level(output, output_level)[] = transform(accumulated_value)
346370
end
347371
end

test/Operators/integrals.jl

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,13 @@ end
106106
function test_column_reduce_and_accumulate!(center_space)
107107
face_space = center_to_face_space(center_space)
108108
ᶜwhole_number = ones(center_space)
109+
ᶜwhole_number_reverse = ones(center_space)
109110
column_accumulate!(+, ᶜwhole_number, ᶜwhole_number) # 1:Nv per column
111+
column_accumulate!(+, ᶜwhole_number_reverse, ᶜwhole_number_reverse; reverse = true)
110112
ᶠwhole_number = ones(face_space)
111113
column_accumulate!(+, ᶠwhole_number, ᶠwhole_number) # 1:(Nv + 1) per column
114+
ᶠwhole_number_reverse = ones(face_space)
115+
column_accumulate!(+, ᶠwhole_number_reverse, ᶠwhole_number_reverse; reverse = true) # 1:(Nv + 1) per column
112116

113117
safe_binomial(n, k) = binomial(Int32(n), Int32(k)) # GPU-compatible binomial
114118

@@ -125,12 +129,19 @@ function test_column_reduce_and_accumulate!(center_space)
125129
init = (1, 0) # m₀ = 1, m₋₁ = 0 (m₋₁ can be set to any finite value)
126130
transform = first # Get mₙ from each (mₙ, mₙ₋₁) pair before saving to output.
127131

128-
for input in (ᶜwhole_number, ᶠwhole_number)
129-
last_input_level = Fields.level(input, Operators.right_idx(axes(input)))
130-
output = similar(last_input_level)
131-
reference_output = motzkin_number.(last_input_level)
132+
for (input, reverse) in (
133+
(ᶜwhole_number, false),
134+
(ᶠwhole_number, false),
135+
(ᶜwhole_number_reverse, true),
136+
(ᶠwhole_number_reverse, true),
137+
)
138+
final_input_idx =
139+
reverse ? Operators.left_idx(axes(input)) : Operators.right_idx(axes(input))
140+
final_input_level = Fields.level(input, final_input_idx)
141+
output = similar(final_input_level)
142+
reference_output = motzkin_number.(final_input_level)
132143

133-
set_output! = () -> column_reduce!(f, output, input; init, transform)
144+
set_output! = () -> column_reduce!(f, output, input; init, transform, reverse)
134145
set_output!()
135146
@test output == reference_output
136147
@test_opt ignored_modules = CUDA_FRAMES set_output!()
@@ -139,19 +150,26 @@ function test_column_reduce_and_accumulate!(center_space)
139150

140151
ᶜoutput = similar(ᶜwhole_number)
141152
ᶠoutput = similar(ᶠwhole_number)
142-
for (input, output, reference_output) in (
143-
(ᶜwhole_number, ᶜoutput, motzkin_number.(ᶜwhole_number)),
144-
(ᶠwhole_number, ᶠoutput, motzkin_number.(ᶠwhole_number)),
145-
(ᶠwhole_number, ᶜoutput, motzkin_number.(ᶜwhole_number .+ 1)),
146-
(ᶜwhole_number, ᶠoutput, motzkin_number.(ᶠwhole_number .- 1)),
153+
for (input, output, reference_output, reverse) in (
154+
(ᶜwhole_number, ᶜoutput, motzkin_number.(ᶜwhole_number), false),
155+
(ᶠwhole_number, ᶠoutput, motzkin_number.(ᶠwhole_number), false),
156+
(ᶠwhole_number, ᶜoutput, motzkin_number.(ᶜwhole_number .+ 1), false),
157+
(ᶜwhole_number, ᶠoutput, motzkin_number.(ᶠwhole_number .- 1), false),
158+
(ᶜwhole_number_reverse, ᶜoutput, motzkin_number.(ᶜwhole_number_reverse), true),
159+
(ᶠwhole_number_reverse, ᶠoutput, motzkin_number.(ᶠwhole_number_reverse), true),
160+
(ᶠwhole_number_reverse, ᶜoutput, motzkin_number.(ᶜwhole_number_reverse .+ 1), true),
161+
(ᶜwhole_number_reverse, ᶠoutput, motzkin_number.(ᶠwhole_number_reverse .- 1), true),
147162
)
163+
148164
set_output! =
149-
() -> column_accumulate!(f, output, input; init, transform)
165+
() -> column_accumulate!(f, output, input; init, transform, reverse)
150166
set_output!()
151167
@test output == reference_output
152168
@test_opt ignored_modules = CUDA_FRAMES set_output!()
153169
test_allocs(@allocated set_output!())
154170
end
171+
172+
155173
end
156174

157175
function test_fubinis_theorem(space)

0 commit comments

Comments
 (0)