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
Implement Scan
, generalizing Reduction
to accumulating scans like cumsum!
#3590
Conversation
Here we go: julia> using Oceananigans
julia> grid = RectilinearGrid(size=(1, 1, 3), extent=(1, 1, 1))
1×1×3 RectilinearGrid{Float64, Periodic, Periodic, Bounded} on CPU with 1×1×3 halo
├── Periodic x ∈ [0.0, 1.0) regularly spaced with Δx=1.0
├── Periodic y ∈ [0.0, 1.0) regularly spaced with Δy=1.0
└── Bounded z ∈ [-1.0, 0.0] regularly spaced with Δz=0.333333
julia> c = CenterField(grid)
1×1×3 Field{Center, Center, Center} on RectilinearGrid on CPU
├── grid: 1×1×3 RectilinearGrid{Float64, Periodic, Periodic, Bounded} on CPU with 1×1×3 halo
├── boundary conditions: FieldBoundaryConditions
│ └── west: Periodic, east: Periodic, south: Periodic, north: Periodic, bottom: ZeroFlux, top: ZeroFlux, immersed: ZeroFlux
└── data: 3×3×9 OffsetArray(::Array{Float64, 3}, 0:2, 0:2, -2:6) with eltype Float64 with indices 0:2×0:2×-2:6
└── max=0.0, min=0.0, mean=0.0
julia> set!(c, (x, y, z) -> rand())
1×1×3 Field{Center, Center, Center} on RectilinearGrid on CPU
├── grid: 1×1×3 RectilinearGrid{Float64, Periodic, Periodic, Bounded} on CPU with 1×1×3 halo
├── boundary conditions: FieldBoundaryConditions
│ └── west: Periodic, east: Periodic, south: Periodic, north: Periodic, bottom: ZeroFlux, top: ZeroFlux, immersed: ZeroFlux
└── data: 3×3×9 OffsetArray(::Array{Float64, 3}, 0:2, 0:2, -2:6) with eltype Float64 with indices 0:2×0:2×-2:6
└── max=0.925176, min=0.853073, mean=0.877648
julia> cumsum_op = Accumulation(cumsum!, c, dims=3)
Accumulating cumsum! over dims 3 of 1×1×3 Field{Center, Center, Center} on RectilinearGrid on CPU
└── operand: 1×1×3 Field{Center, Center, Center} on RectilinearGrid on CPU
└── grid: 1×1×3 RectilinearGrid{Float64, Periodic, Periodic, Bounded} on CPU with 1×1×3 halo
julia> cumsum_c = Field(cumsum_op)
1×1×3 Field{Center, Center, Center} on RectilinearGrid on CPU
├── data: OffsetArrays.OffsetArray{Float64, 3, Array{Float64, 3}}, size: (1, 1, 3)
├── grid: 1×1×3 RectilinearGrid{Float64, Periodic, Periodic, Bounded} on CPU with 1×1×3 halo
├── operand: Accumulating cumsum! over dims 3 of 1×1×3 Field{Center, Center, Center} on RectilinearGrid on CPU
├── status: time=0.0
└── data: 3×3×9 OffsetArray(::Array{Float64, 3}, 0:2, 0:2, -2:6) with eltype Float64 with indices 0:2×0:2×-2:6
└── max=0.0, min=0.0, mean=0.0
julia> compute!(cumsum_c)
1×1×3 Field{Center, Center, Center} on RectilinearGrid on CPU
├── data: OffsetArrays.OffsetArray{Float64, 3, Array{Float64, 3}}, size: (1, 1, 3)
├── grid: 1×1×3 RectilinearGrid{Float64, Periodic, Periodic, Bounded} on CPU with 1×1×3 halo
├── operand: Accumulating cumsum! over dims 3 of 1×1×3 Field{Center, Center, Center} on RectilinearGrid on CPU
├── status: time=0.0
└── data: 3×3×9 OffsetArray(::Array{Float64, 3}, 0:2, 0:2, -2:6) with eltype Float64 with indices 0:2×0:2×-2:6
└── max=2.63294, min=0.925176, mean=1.77933 |
Cool. Getting to the finish line will be a bit of work I guess. I think we usually want to integrate downwards so that's annoying. Not sure if that exists exactly, or we have to implement some lazy version of |
Isn't that a way to obtain the reverse cumulative integration by simply adding something? |
Ah that is a nice trick! I Think its something like cumsum!(b, a, dims=3)
A = view(b, :, :, Nz) # total sum of a over 3rd dimension
b .= A .- b .+ a # compute the reversed cumsum |
Dang, pretty cool: julia> grid = RectilinearGrid(size=(1, 1, 3), extent=(1, 1, 1))
1×1×3 RectilinearGrid{Float64, Periodic, Periodic, Bounded} on CPU with 1×1×3 halo
├── Periodic x ∈ [0.0, 1.0) regularly spaced with Δx=1.0
├── Periodic y ∈ [0.0, 1.0) regularly spaced with Δy=1.0
└── Bounded z ∈ [-1.0, 0.0] regularly spaced with Δz=0.333333
julia> c = CenterField(grid); set!(c, (x, y, z) -> rand())
1×1×3 Field{Center, Center, Center} on RectilinearGrid on CPU
├── grid: 1×1×3 RectilinearGrid{Float64, Periodic, Periodic, Bounded} on CPU with 1×1×3 halo
├── boundary conditions: FieldBoundaryConditions
│ └── west: Periodic, east: Periodic, south: Periodic, north: Periodic, bottom: ZeroFlux, top: ZeroFlux, immersed: ZeroFlux
└── data: 3×3×9 OffsetArray(::Array{Float64, 3}, 0:2, 0:2, -2:6) with eltype Float64 with indices 0:2×0:2×-2:6
└── max=0.53406, min=0.317935, mean=0.436216
julia> ci_op = CumulativeIntegral(c, dims=3)
CumulativeIntegral of BinaryOperation at (Center, Center, Center) over dims 3
└── operand: BinaryOperation at (Center, Center, Center)
└── grid: 1×1×3 RectilinearGrid{Float64, Periodic, Periodic, Bounded} on CPU with 1×1×3 halo
julia> ci = compute!(Field(ci_op))
1×1×3 Field{Center, Center, Center} on RectilinearGrid on CPU
├── data: OffsetArrays.OffsetArray{Float64, 3, Array{Float64, 3}}, size: (1, 1, 3)
├── grid: 1×1×3 RectilinearGrid{Float64, Periodic, Periodic, Bounded} on CPU with 1×1×3 halo
├── operand: CumulativeIntegral of BinaryOperation at (Center, Center, Center) over dims 3
├── status: time=0.0
└── data: 3×3×9 OffsetArray(::Array{Float64, 3}, 0:2, 0:2, -2:6) with eltype Float64 with indices 0:2×0:2×-2:6
└── max=0.436216, min=0.105978, mean=0.275398
julia> rci_op = CumulativeIntegral(c, dims=3, reverse=true)
CumulativeIntegral of BinaryOperation at (Center, Center, Center) over dims 3
└── operand: BinaryOperation at (Center, Center, Center)
└── grid: 1×1×3 RectilinearGrid{Float64, Periodic, Periodic, Bounded} on CPU with 1×1×3 halo
julia> rci = compute!(Field(rci_op))
1×1×3 Field{Center, Center, Center} on RectilinearGrid on CPU
├── data: OffsetArrays.OffsetArray{Float64, 3, Array{Float64, 3}}, size: (1, 1, 3)
├── grid: 1×1×3 RectilinearGrid{Float64, Periodic, Periodic, Bounded} on CPU with 1×1×3 halo
├── operand: CumulativeIntegral of BinaryOperation at (Center, Center, Center) over dims 3
├── status: time=0.0
└── data: 3×3×9 OffsetArray(::Array{Float64, 3}, 0:2, 0:2, -2:6) with eltype Float64 with indices 0:2×0:2×-2:6
└── max=0.436216, min=0.152218, mean=0.306224 |
@jagoosw thoughts? |
This looks great! I think we can re-state light integration as a field reduction with this. |
…into glw/scans
That's a great feature to add! I can remember it being requested more than a few times over the years. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great! My only question is: is "scan" a default / frequently-used name for what we're doing here? I ask because I had never heard of it and when I think of a "scan" of a Field
, I don't picture what is implemented here. That said I do agree the name makes sense after seeing what it does.
That's fair, I found it on wikipedia: https://en.wikipedia.org/wiki/Prefix_sum#:~:text=In%20computer%20science%2C%20the%20prefix,y0%20%3D%20x&text=y1%20%3D%20x0%20%2B%20x That said it is meant in a more specific context there... However, I found it to be descriptive, since the operations that That said I do think we can come up with a better name, maybe we can continue to discuss here even after this is merged. |
Would |
Ok here's an idea: replace We then have the mapping:
Honestly discussing this, the phrase "cumulative sum" is not the most clear, either. But, history. |
Couldn't this be confusing though, if the "cumulative scan of a sum" is in fact just a sum (and not a cumulative sum)? |
Pardon my being pedantic, but "cumulative accumulations" sounds pretty redundant. We might not have enough words in the English language to describe this functionality precisely 😆 |
I thought a cumulative scan of a sum would be a "double sum". I thought a cumulative scan of a field would be a sum. No? Maybe I'm misunderstanding what The way I understood it (based off of the wikipedia page primarily):
Is this not correct? |
I think "cumulative sum" is redundant too. If I say I have an "accumulation of beans", I'm talking about the whole pile of beans. It doesn't seem to me that terminology exists, to answer your first question about whether "scan" is commonly used. There isn't any term that is commonly used. |
I am not very familiar with this use of scan, and despite "cumulative sum" sounding redundant, it express to me better the idea that I am not simply summing all values of a series. I think that this is the reason why |
Could |
Well to be clear, the name we are searching for is something that can describe both reductions like sum, maximum, minimum (and average and integral, which are similar to sum but involve grid metrics) --- and operations like cumsum. The things these have in common (in contrast to local operations like derivative, plus, minus, etc) is that they involve a loop over one or more dimensions (which is why they have a For example, we could envision also supporting The key property of a reduction is that its output is also lower dimensionality than the input, the So we need a few things to build the abstraction. First we need a name that encompasses reductions, plus cumsum and sort. Second, we need another pair of names that express the distinction between a "reduction" and a non-dimensionality-changing-yet-still-scanning operation like cumsum and sort. I meant the term "scan" simply to mean literally that we are going to traverse one or more dimensions (eg scanning the dimension). No doubt it can be improved but the improvement needs to be sufficiently general. that's the whole challenge |
This PR generalizes
Reduction
to also support accumulating "scanning" operations likecumsum!
.This should not change the existing user interface but instead add the new feature, something like
and
Previously I don't think this was possible on GPU becauseTo support this feature, we've implemented kernels for forward and reverse accumulation.cumsum!
was not supported forCuArray
(?) But it is now it seems.I also have used the generalization of
Scan
to clean up the internals / user interface forAverage
andIntegral
(now they are proper type aliases).cc @hdrake @iuryt