-
Notifications
You must be signed in to change notification settings - Fork 3
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
Fixing boundary checks on masked grids #114
Conversation
Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>
Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>
src/kernel/outwards.jl
Outdated
sum += propagules | ||
target = I .+ offset | ||
# Check if the target cell is within bounds and not masked | ||
(target_mod, inbounds) = _inbounds(Reflect(), size(data[1]), target...) |
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.
I think inbounds(data, i)
does exactly this for you
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.
True! And it's better cause I don't need to specify the boundary condition
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.
Yes we try to keep anything related to boundary conditions, aux data, masks etc out of rules and in the data
object. Then we can swap them around without changing all our code.
src/kernel/outwards.jl
Outdated
target = I .+ offset | ||
# Check if the target cell is within bounds and not masked | ||
(target_mod, inbounds) = _inbounds(Reflect(), size(data[1]), target...) | ||
if inbounds && (rule.mask === nothing || rule.mask[target_mod...]) |
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.
if inbounds && (rule.mask === nothing || rule.mask[target_mod...]) | |
if inbounds && (isnothing(rule.mask) || mask(data)[target_mod...]) |
Ok instead of attaching the mask to the rule, we can just get it from We can have the rule field as This is better than the field being |
Thats available to Dispersal.jl, so it just works. (we need And maybe you wonder how the constructor for There are default constructors, see here: |
But how will mask(data) work when it's not missingval's what's in the masked area? And I don't think using the |
Still doesn't work, but I think we're closer
Well, I'll leave it for today. I don't even know what I'm doing at this point jajaja. I created what I think it's a singleton Then I added the mask(data) approach within
|
src/kernel/outwards.jl
Outdated
@@ -32,21 +32,90 @@ is occupied. | |||
|
|||
Pass grid name `Symbol`s to `R` and `W` type parameters to use specific grids. | |||
""" | |||
struct OutwardsDispersal{R,W,S<:Stencils.AbstractKernelStencil} <: SetNeighborhoodRule{R,W} | |||
struct _Mask end | |||
const NoMask = nothing |
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.
I was confused by what NoMask was below. I would just use nothing
, or make NoMask
a real singleton like _Mask
.
You can even make Mask
and NoMask
singletons, then users could even use them directly. IDK
src/kernel/outwards.jl
Outdated
outdisp_with_mask = OutwardsDispersal( | ||
formulation=ExponentialKernel(λ=0.0125), | ||
distancemethod=AreaToArea(30), | ||
mask_flag=_Mask() |
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.
If you want to use it directly like this I would just make it Mask
, underscore is if its internal
I mean Its unfortunate the name is the same, its from before Rasters existed. It just gets the mask object that you pass in to the output. |
That makes much more sense!
…On Thu, 13 Jun 2024, 19:11 Rafael Schouten, ***@***.***> wrote:
I mean DynamicGrids.mask! That's what you will use inside
DynamicGrids.jl, as theres no Rasters.jl dep.
Its unfortunate the name is the same, its from before Rasters existed. It
just gets the mask object that you pass in to the output.
—
Reply to this email directly, view it on GitHub
<#114 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ALXYX3YAAYR6JS5EDNVTXI3ZHHHE3AVCNFSM6AAAAABJIEWBZWVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCNRWGM2DGMZRHA>
.
You are receiving this because you authored the thread.Message ID:
***@***.***>
|
Co-authored-by: Rafael Schouten <rafaelschouten@gmail.com>
Ok, for perspective here's a full benchmarking for the original package (O), my script (M) and your suggestions(S): |
src/kernel/outwards.jl
Outdated
@inbounds add!(data[W], propagules, I .+ offset...) | ||
sum += propagules | ||
target = I .+ offset | ||
(target_mod, inbounds) = inbounds(data, target) |
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.
(target_mod, inbounds) = inbounds(data, target) | |
(target_mod, inbounds) = inbounds(data, target) |
This line is still running in all cases... this is the problem ;)
It doesn't need to, it can be in the conditional below.
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.
But you're reviewing the old code again because I went back to it after checking your idea. If you check the previous commit 607885d you'd see it only runs (target_mod, inbounds) = inbounds(data, target)
when mask.flag === Mask()
I believe.
But I think I know what you mean, I'll try to implement it.
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 is the code that's running now... The rest is commented out
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.
The commented out part is actually your conditional idea that I benchmarked and didn't get better
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.
Ok, hard to assess it clearly without being able to see the diff
I don't think we need a different function, as long as we let the compiler see the differences it will just delete them completely. That line I've highlighted needs to go inside a conditional that depends on the type of This should work and have the original performance when sum = zero(N)
for (offset, k) in zip(offsets(rule), kernel(rule))
target = I .+ offset
inbounds = if isnothing(mask_data)
true
else
(target_mod, inbounds) = inbounds(data, target)
mask_data[target_mod...])
end
if inbounds
propagules = N * k
@inbounds add!(data[W], propagules, target_mod...)
sum += propagules
end
end |
Ok, kind of fixed? However, now I'm getting losses on real boundaries, which didn't use to happen. For example:
|
Nice timings!
The original algorithm should be identical to what it was. It looks identical to me? I assumed there was always some losses at the edges for non-masked grids with (And bounds checks are pretty expensive, as you can see in your algorithm timings. We could add another singleton like Mask to opt into them?) |
I don't know, I'm quite confused now, because the tests I wrote actually checked that when there was no mask and NoMask() OutwardsDispersal there should only be floating error loss (which is below 1.0) and I was passing them (when using Wrap or Reflect of course). But now I don't pass them, neither I pass them using the original Dispersal#dev. InwardsDispersal only has floating error loss when not using a mask. I don't think there should be that large loss when not using a mask (and using Wrap or Reflect), I find it quite inconvenient because if you really need to shut down losses you'll have to create a buffer mask. It doesn't personally affect because my masks barely touch the grid edges, but seems suboptimal to me :) We could add another singleton like Mask to opt into them? |
Yes having to use a fake mask sounds annoying. Personally I always have a mask if I use
Ahh I think I get you, yes we can just use the same (We will also need to add a docstring to describe what these things do and why you would choose to use them or not) |
shouldn't Wrap be loss-free? we can just use the same Mask singleton when there is no mask array just to mean no out of bounds losses
Because it's basically the same problem, if a mask doesn't cover all grid borders, it'll lose individuals. We will also need to add a docstring to describe what these things do and why you would choose to use them or not. |
Yes I meant we would have to adjust the code so that the For Or, we just always do the Maybe that's just simpler and better? (you can probably see why this was simple but a bit buggy... its pretty hard to get all of these things right and have good performance and simple code all at the same time) |
I mean, that's your call. If you're ok with a decrease in performance I'm fine with that. And InwardsDispersal is always there in need of efficiency. And yes, I see the tradeoff :) |
Ok lets just make it correct for now :) I can go back in an retrieve some |
Ok, so I brought the code back to always bound-check. We're back to the not-so-good timings we discussed the other day but the tests all pass. I also added a more detailed description in Outwards.jl so the user clearly knows what |
test/integration.jl
Outdated
output_without_mask = ArrayOutput(init; tspan=1:1000) | ||
b = sim!(output_without_mask, rule_without_mask) | ||
|
||
@test sum(b[1]) ≈ sum(b[1000]) # Floating error should be smaller than 1.0 |
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.
@test sum(b[1]) ≈ sum(b[1000]) # Floating error should be smaller than 1.0 | |
@test sum(b[1]) ≈ sum(b[1000]) # Floating error should be close to zero |
test/integration.jl
Outdated
output_with_mask = ArrayOutput(init; tspan=1:1000, mask=mask_data) | ||
a = sim!(output_with_mask, rule_with_mask) | ||
|
||
@test sum(a[1]) ≈ sum(a[1000]) # Floating error should be smaller than 1.0 |
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.
@test sum(a[1]) ≈ sum(a[1000]) # Floating error should be smaller than 1.0 | |
@test sum(a[1]) ≈ sum(a[1000]) # Floating error should be close to zero |
src/kernel/outwards.jl
Outdated
struct Mask end | ||
struct NoMask end |
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.
struct Mask end | |
struct NoMask end | |
struct AvoidMasked end | |
struct LossToMasked end |
Ideas for more descriptive names, now that everything works
src/kernel/outwards.jl
Outdated
stencil::S | ||
mask_flag::M |
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.
mask_flag::M | |
maskbehavior::M |
Cool, all changes done. |
Yes it's hard to not get a double negative there! But those names sound good to me. |
Hey Raf, Hope you're doing good :) I'll figure something out if you're busy, no worries :) |
Sorry, holidays I've merged will fix the tests later (You will still have to use the main branch it's not registered yet) |
Hey there again :)
I opened the PR to discuss a "small?" implementation of masked boundary checks in OutwardsDispersal. I haven't had much success yet but we're on it :) I've tried to modify OutwardsDispersal such that it can hold an optional mask:
And then modified its applyrule! so it discards cells outside of the mask.
Perhaps I instead should create a brand new OutwardsDispersalWithMask to keep them separate but since I don't know what other parts of the "family" packages are related with these rules, this feels more tedious to build.
Just for the sake of understanding the package behaviour:
I know what
kernelproduct()
is used for, but where did you define its method for an InwardsDispersal rule?Edit: Btw, just to know. Are tests passing for you? Because I could not pass them even before making any modification.