Skip to content

Commit 774b61e

Browse files
bas
1 parent de11dd2 commit 774b61e

4 files changed

Lines changed: 78 additions & 78 deletions

File tree

ext/SDTExtension/SDTExtension.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ module SDTExtension
33
using BiodiversityObservationNetworks
44
using TestItems
55

6+
@info "Hi from SDTExtension"
7+
68

79
_apply_sdm_mask!(::BitMatrix, ::SDMLayer, ::Missing) = nothing
810
function _apply_sdm_mask!(valid::BitMatrix, layer::SDMLayer, mask::AbstractMatrix)

src/BiodiversityObservationNetworks.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ module BiodiversityObservationNetworks
55
using Distributions
66
using LinearAlgebra
77
using NearestNeighbors
8-
8+
using HaltonSequences
99

1010
include("sampler.jl")
1111
include("bon.jl")
@@ -17,13 +17,14 @@ module BiodiversityObservationNetworks
1717
include(joinpath("samplers", "simplerandom.jl"))
1818
include(joinpath("samplers", "spatiallycorrelatedpoisson.jl"))
1919
include(joinpath("samplers", "pivotal.jl"))
20+
include(joinpath("samplers", "balancedacceptance.jl"))
2021

2122
export sample
2223
export CandidatePool
2324
export BiodiversityObservationNetwork
2425

2526
export BONSampler
26-
export SimpleRandom, SpatiallyCorrelatedPoisson, Pivotal
27+
export SimpleRandom, SpatiallyCorrelatedPoisson, Pivotal, BalancedAcceptance
2728

2829
#=
2930
using DelaunayTriangulation

src/samplers/balancedacceptance.jl

Lines changed: 66 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,122 +1,112 @@
1-
#=
2-
31
"""
42
BalancedAcceptance <: BONSampler
53
6-
Implements Balanced Acceptance Sampling (BAS) using Halton sequences.
4+
Balanced Acceptance Sampling (BAS) using Halton sequences.
75
8-
# Fields
9-
- `num_nodes::Int`: The number of sites to select.
6+
Generates spatially balanced samples by mapping Halton
7+
sequences to the candidate coordinate space. When inclusion weights are
8+
non-uniform, a third Halton dimension acts as a threshold for acceptance,
9+
preferentially selecting higher-weighted candidates.
1010
11-
# Description
12-
BAS generates spatially balanced samples by mapping the domain to a Halton sequence.
13-
If `inclusion` probabilities are provided, it uses a 3D Halton sequence where the
14-
third dimension acts as an acceptance threshold against the probability surface.
11+
# Fields
12+
- `n::Int`: number of sites to select (default 50)
1513
1614
# References
17-
- Robertson, B. L., et al. (2013).
15+
- Robertson, B. L., et al. (2013).
1816
"""
1917
@kwdef struct BalancedAcceptance <: BONSampler
20-
num_nodes = _DEFAULT_NUM_NODES
21-
end
18+
n::Int = 50
19+
end
20+
21+
supports_inclusion(::BalancedAcceptance) = true
22+
guarantees_exact_n(::BalancedAcceptance) = true
2223

2324
_get_halton_value(base, offset, element) = haltonvalue(offset + element, base)
2425
_halton(bases, seeds, step, dims) = [_get_halton_value(bases[i], seeds[i], step) for i in 1:dims]
26+
_rescale_node(sz, x::Real, y::Real) = Int.(ceil.(sz .* [x, y]))
2527

28+
function _construct_explicit_mask_and_inclusion(cpool::CandidatePool, sz)
29+
mask, inclusion, key_index = zeros(Bool, sz), zeros(sz), zeros(Int, sz)
2630

27-
"""
28-
_rescale_node(domain, x::Real, y::Real)
29-
30-
Map unit-cube Halton coordinates `(x, y)` to integer raster indices in `domain`.
31-
"""
32-
function _rescale_node(domain, x::Real, y::Real)
33-
x_scaled, y_scaled = Int.(ceil.(size(domain) .* [x, y]))
34-
return x_scaled, y_scaled
35-
end
36-
37-
38-
"""
39-
_sample(sampler::BalancedAcceptance, domain; inclusion=nothing)
40-
41-
Generate a spatially balanced sample using BAS. With `inclusion`, perform 3D BAS
42-
to respect per-cell probabilities; otherwise perform 2D BAS over the mask.
43-
"""
44-
function _sample(
45-
sampler::BalancedAcceptance,
46-
domain;
47-
inclusion = nothing
48-
)
49-
if !isnothing(inclusion)
50-
return _3d_bas(sampler, domain, inclusion)
51-
else
52-
return _2d_bas(sampler, domain)
31+
for i in eachindex(cpool.keys)
32+
mask[cpool.keys[i]] = true
33+
inclusion[cpool.keys[i]] = cpool.inclusion[i]
34+
key_index[cpool.keys[i]] = i
5335
end
36+
return mask, inclusion, key_index
5437
end
5538

39+
function _sample(rng::AbstractRNG, sampler::BalancedAcceptance, cpool::CandidatePool)
40+
unequal_inclusion = !all((cpool.inclusion[1]), cpool.inclusion)
41+
if unequal_inclusion
42+
return _3d_bas(rng, sampler, cpool)
43+
else
44+
return _2d_bas(rng, sampler, cpool)
45+
end
46+
end
5647

5748
"""
58-
_3d_bas(sampler, domain, inclusion)
49+
_2d_bas(sampler, domain)
5950
60-
3D BAS using Halton bases `[2,3,5]`. A candidate `(i,j,z)` is accepted if the
61-
cell is unmasked and `z < inclusion[i,j]`.
51+
2D BAS using Halton bases `[2,3]` to generate spatially spread candidate cells,
52+
accepting those that fall on unmasked locations until `num_nodes` are selected.
6253
"""
63-
function _3d_bas(sampler, domain, inclusion)
64-
seeds = rand(Int.(1e0:1e7), 3)
65-
bases = [2, 3, 5]
54+
function _2d_bas(rng, sampler, cpool)
55+
seeds = rand(rng, Int.(1e0:1e7), 2)
56+
bases = [2, 3]
6657
attempt = 0
67-
nodes = CartesianIndex[]
68-
while length(nodes) < sampler.num_nodes
69-
i, j, z = _halton(bases, seeds, attempt, 3)
70-
i, j = _rescale_node(domain, i,j)
58+
selected = []
59+
xmax, ymax = maximum([x[1] for x in cpool.keys]), maximum([x[2] for x in cpool.keys])
60+
valid_sites, _, key_index = _construct_explicit_mask_and_inclusion(cpool, (xmax, ymax))
7161

72-
if !ismasked(domain, i,j) && z < inclusion[i,j]
73-
push!(nodes, CartesianIndex(i,j))
62+
while length(selected) < sampler.n
63+
i, j = _halton(bases, seeds, attempt, 2)
64+
i, j = _rescale_node((xmax, ymax), i, j)
65+
66+
if valid_sites[i,j]
67+
push!(selected, key_index[i,j])
7468
end
7569
attempt += 1
76-
end
77-
return nodes, domain[nodes]
70+
end
71+
return selected
7872
end
7973

8074
"""
81-
_2d_bas(sampler, domain)
75+
_3d_bas(rng, sampler, cpool)
8276
83-
2D BAS using Halton bases `[2,3]` to generate spatially spread candidate cells,
84-
accepting those that fall on unmasked locations until `num_nodes` are selected.
77+
3D BAS using Halton bases `[2,3,5]`. A candidate `(i,j,z)` is accepted if the
78+
cell is unmasked and `z < inclusion[i,j]`.
8579
"""
86-
function _2d_bas(sampler, domain)
87-
seeds = rand(Int.(1e0:1e7), 2)
88-
bases = [2, 3]
80+
function _3d_bas(rng, sampler, cpool)
81+
seeds = rand(rng, Int.(1e0:1e7), 3)
82+
bases = [2, 3, 5]
8983
attempt = 0
90-
nodes = CartesianIndex[]
91-
while length(nodes) < sampler.num_nodes
92-
i, j = _halton(bases, seeds, attempt, 2)
93-
i, j = _rescale_node(domain, i,j)
84+
selected = []
85+
xmax, ymax = maximum([x[1] for x in cpool.keys]), maximum([x[2] for x in cpool.keys])
86+
87+
valid_sites, inclusion, key_index = _construct_explicit_mask_and_inclusion(cpool, (xmax, ymax))
88+
89+
while length(selected) < sampler.n
90+
i, j, z = _halton(bases, seeds, attempt, 3)
91+
i, j = _rescale_node((xmax, ymax), i,j)
9492

95-
if !ismasked(domain, i,j)
96-
push!(nodes, CartesianIndex(i,j))
93+
if valid_sites[i,j] && z < inclusion[i,j]
94+
push!(selected, key_index[i,j])
9795
end
9896
attempt += 1
99-
end
100-
return nodes, domain[nodes]
97+
end
98+
return selected
10199
end
102100

103-
# ========================================================================
104-
# Tests
105-
# ========================================================================
106101

107-
@testitem "We can use Balanced Acceptance with a RasterDomain" begin
102+
@testitem "We can use Balanced Acceptance" begin
108103
bon = sample(BalancedAcceptance(), rand(30,20))
109-
110104
@test bon isa BiodiversityObservationNetwork
111-
@test first(bon) isa CartesianIndex
112105
end
113106

114-
@testitem "We can use Balanced Acceptance with a RasterDomain and Inclusion" begin
107+
@testitem "We can use Balanced Acceptance with custom inclusion" begin
115108
inclusion = rand(30,20)
116109
bon = sample(BalancedAcceptance(), rand(30,20), inclusion=inclusion)
117-
118110
@test bon isa BiodiversityObservationNetwork
119-
@test first(bon) isa CartesianIndex
120111
end
121112

122-
=#

src/samplers/pivotal.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,13 @@ function _sample(rng::AbstractRNG, sampler::Pivotal, cpool::CandidatePool)
103103
return selected
104104
end
105105

106+
107+
@testitem "We can use Pivotal" begin
108+
bon = sample(Pivotal(50), rand(30,20))
109+
@test bon isa BiodiversityObservationNetwork
110+
end
111+
112+
106113
@testitem "We can use Pivotal with a BON" begin
107114
candidate_bon = sample(SimpleRandom(100), rand(30,20))
108115

0 commit comments

Comments
 (0)