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
5437end
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
7872end
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
10199end
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
112105end
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
120111end
121112
122- =#
0 commit comments