diff --git a/Project.toml b/Project.toml index cbea966..60697ac 100644 --- a/Project.toml +++ b/Project.toml @@ -21,9 +21,11 @@ TestItems = "1c621080-faea-4a02-84b6-bbd5e436b8fe" [weakdeps] SpeciesDistributionToolkit = "72b53823-5c0b-4575-ad0e-8e97227ad13b" +NeutralLandscapes = "71847384-8354-4223-ac08-659a5128069f" [extensions] SDMToolkitExt = ["SpeciesDistributionToolkit"] +NLExt = ["NeutralLandscapes"] [compat] Distributions = "0.25" diff --git a/src/balancedacceptance.jl b/src/balancedacceptance.jl index dcfaeaa..b41afe5 100644 --- a/src/balancedacceptance.jl +++ b/src/balancedacceptance.jl @@ -8,11 +8,15 @@ Base.@kwdef struct BalancedAcceptance{I <: Integer} <: BONSeeder numpoints::I = 30 function BalancedAcceptance(numpoints) bas = new{typeof(numpoints)}(numpoints) - _check_arguments(bas) + check_arguments(bas) return bas end end +function check_arguments(bas::BalancedAcceptance) + return check(TooFewSites, bas) +end + function _generate!( coords::Vector{CartesianIndex}, ::BalancedAcceptance, diff --git a/src/cubesampling.jl b/src/cubesampling.jl index 5bf7904..18dfcb1 100644 --- a/src/cubesampling.jl +++ b/src/cubesampling.jl @@ -22,24 +22,29 @@ Base.@kwdef struct CubeSampling{I <: Integer, M <: Matrix, V <: Vector} <: BONRe function CubeSampling(numpoints, fast, x, πₖ) cs = new{typeof(numpoints), typeof(x), typeof(πₖ)}(numpoints, fast, x, πₖ) _check_arguments(cs) - if numpoints > length(πₖ) - throw( - ArgumentError( - "You cannot select more points than the number of candidate points.", - ), - ) - end - if length(πₖ) != size(x, 2) - throw( - DimensionMismatch( - "The number of inclusion probabilites does not match the dimensions of the auxillary variable matrix.", - ), - ) - end + return cs end end +function check_arguments(cs::CubeSampling) + check(TooFewSites, cs) + if numpoints > length(πₖ) + throw( + ArgumentError( + "You cannot select more points than the number of candidate points.", + ), + ) + end + if length(πₖ) != size(x, 2) + throw( + DimensionMismatch( + "The number of inclusion probabilites does not match the dimensions of the auxillary variable matrix.", + ), + ) + end +end + function check_conditions(coords, pool, sampler) πₖ = sampler.πₖ if sum(sampler.πₖ) == 0 @@ -441,4 +446,3 @@ end @test_throws TooFewSites CubeSampling(numpoints = 0) @test_throws TooFewSites CubeSampling(numpoints = 1) end - diff --git a/src/exceptions.jl b/src/exceptions.jl index ed7ffa9..afabfcc 100644 --- a/src/exceptions.jl +++ b/src/exceptions.jl @@ -11,6 +11,10 @@ function _check_arguments(sampler::S) where {S <: Union{BONSeeder, BONRefiner}} return sampler.numpoints > 1 || throw(TooFewSites(sampler.numpoints)) end +function check(TooFewSites, sampler) + return sampler.numpoints > 1 || throw(TooFewSites(sampler.numpoints)) +end + @kwdef struct TooFewSites <: BONException message = "Number of sites to select must be at least two." end diff --git a/src/refine.jl b/src/refine.jl index 95ba701..3680c1b 100644 --- a/src/refine.jl +++ b/src/refine.jl @@ -2,14 +2,14 @@ refine!(cooords::Vector{CartesianIndex}, pool::Vector{CartesianIndex}, sampler::ST, uncertainty::Matrix{T}) Refines a set of candidate sampling locations in the preallocated vector `coords` -from a vector of coordinates `pool` using `sampler`, where `sampler` is a [`BONRefiner`](@ref). +from a vector of coordinates `pool` using `sampler`, where `sampler` is a [`BONRefiner`](@ref). """ function refine!( coords::Vector{CartesianIndex}, pool::Vector{CartesianIndex}, sampler::ST, - uncertainty::Matrix{T} -) where {ST <: BONRefiner, T <: AbstractFloat, N} + uncertainty::Matrix{T}, +) where {ST <: BONRefiner, T <: AbstractFloat} if length(coords) != sampler.numpoints throw( DimensionMismatch( @@ -74,7 +74,7 @@ end """ refine(pack, sampler::BONRefiner) - + Calls `refine` on the appropriatedly splatted version of `pack`. """ function refine( @@ -82,4 +82,4 @@ function refine( sampler::ST, ) where {ST <: BONRefiner} return refine(first(pack), sampler, last(pack)) -end \ No newline at end of file +end diff --git a/src/simplerandom.jl b/src/simplerandom.jl index f8b988c..d636f22 100644 --- a/src/simplerandom.jl +++ b/src/simplerandom.jl @@ -7,11 +7,15 @@ Base.@kwdef struct SimpleRandom{I <: Integer} <: BONSeeder numpoints::I = 50 function SimpleRandom(numpoints) srs = new{typeof(numpoints)}(numpoints) - _check_arguments(srs) + check_arguments(srs) return srs end end +function check_arguments(srs) + return check(TooFewSites, srs) +end + function _generate!( coords::Vector{CartesianIndex}, sampler::SimpleRandom, diff --git a/src/spatialstratified.jl b/src/spatialstratified.jl index 79abe70..c903c29 100644 --- a/src/spatialstratified.jl +++ b/src/spatialstratified.jl @@ -5,6 +5,28 @@ numpoints::I = 50 strata::Matrix{I} = _default_strata((50, 50)) inclusion_probability_by_stratum::Vector{F} = ones(3) ./ 3 + function SpatiallyStratified(numpoints, strata, inclusion_probability_by_stratum) + ss = new{typeof(numpoints), typeof(inclusion_probability_by_stratum[begin])}( + numpoints, + strata, + inclusion_probability_by_stratum, + ) + check_arguments(ss) + return ss + end +end + +function check_arguments(ss::SpatiallyStratified) + check(TooFewSites, ss) + + length(unique(ss.strata)) == length(ss.inclusion_probability_by_stratum) || throw( + ArgumentError( + "Inclusion probability vector does not have the same number of strata as there are unique values in the strata matrix", + ), + ) + + return sum(ss.inclusion_probability_by_stratum) ≈ 1.0 || + throw(ArgumentError("Inclusion probabilities for each strata do not sum to 1.")) end function _default_strata(sz) @@ -56,3 +78,39 @@ end coords = seed(ss, uncert) |> first @test typeof(coords) <: Vector{CartesianIndex} end + +@testitem "SpatiallyStratified throws error when number of sites is below 2" begin + @test_throws TooFewSites SpatiallyStratified(numpoints = -1) + @test_throws TooFewSites SpatiallyStratified(numpoints = 0) + @test_throws TooFewSites SpatiallyStratified(numpoints = 1) +end + +@testitem "SpatiallyStratified can use custom number of points as keyword argument" begin + NUM_POINTS = 42 + ss = SpatiallyStratified(; numpoints = NUM_POINTS) + @test ss.numpoints == NUM_POINTS +end + +@testitem "SpatiallyStratified can use custom strata as keyword argument" begin + dims = (42, 30) + strata = rand(1:10, dims...) + uncert = rand(dims...) + inclusion_probability = [0.1 for i in 1:10] + ss = SpatiallyStratified(; + strata = strata, + inclusion_probability_by_stratum = inclusion_probability, + ) + coords = seed(ss, uncert) |> first + @test typeof(coords) <: Vector{CartesianIndex} +end + +@testitem "SpatiallyStratified throws error if the number of inclusion probabilities are different than the number of unique strata" begin + dims = (42, 42) + inclusion_probability = [0.5, 0.5] + strata = rand(1:5, dims...) + + @test_throws ArgumentError SpatiallyStratified(; + strata = strata, + inclusion_probability_by_stratum = inclusion_probability, + ) +end diff --git a/src/weightedbas.jl b/src/weightedbas.jl index 0eb0da5..a1d68fc 100644 --- a/src/weightedbas.jl +++ b/src/weightedbas.jl @@ -8,17 +8,25 @@ Base.@kwdef struct WeightedBalancedAcceptance{I <: Integer, F <: Real} <: BONSee α::F = 1.0 function WeightedBalancedAcceptance(numpoints, α) wbas = new{typeof(numpoints), typeof(α)}(numpoints, α) - _check_arguments(wbas) + check_arguments(wbas) return wbas end end +function check_arguments(wbas::WeightedBalancedAcceptance) + check(TooFewSites, wbas) + return wbas.α > 0 || + throw( + ArgumentError("WeightedBalancedAcceptance requires α to be greater than 0 "), + ) +end + function _generate!( coords::Vector{CartesianIndex}, - sampler::WeightedBalancedAcceptance, + sampler::WeightedBalancedAcceptance{I}, uncertainty::Matrix{T}, -) where {T <: AbstractFloat} - seed = rand(Int32.(1e0:1e7), 2) +) where {I <: Integer, T <: AbstractFloat} + seed = rand(I.(1e0:1e7), 2) α = sampler.α x, y = size(uncertainty)