Skip to content

Commit

Permalink
Refactor InMemoryDispersalSampler checked construction
Browse files Browse the repository at this point in the history
  • Loading branch information
juntyr committed May 30, 2024
1 parent 4829e82 commit de6728f
Show file tree
Hide file tree
Showing 16 changed files with 141 additions and 215 deletions.
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions necsim/impls/no-std/src/array2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,7 @@ impl<T, B: ArrayBackend<T>> Array2D<T, B> {
/// # Examples
///
/// ```
/// # use necsim_impls_no_std::array2d::{VecArray2D, Error};
/// # use necsim_impls_no_std::array2d::{Error, VecArray2D};
/// # fn main() -> Result<(), Error> {
/// let rows = vec![vec![1, 2, 3], vec![4, 5, 6]];
/// let array = VecArray2D::from_rows(&rows)?;
Expand All @@ -482,11 +482,11 @@ impl<T, B: ArrayBackend<T>> Array2D<T, B> {
/// # Examples
///
/// ```
/// # use necsim_impls_no_std::array2d::{VecArray2D, Error};
/// # use necsim_impls_no_std::array2d::{BoxArray2D, Error, VecArray2D};
/// # fn main() -> Result<(), Error> {
/// let rows = vec![vec![1, 2, 3], vec![4, 5, 6]];
/// let array = VecArray2D::from_rows(&rows)?;
/// let array: BoxArray2D = array.into_backend();
/// let array: BoxArray2D = array.switch_backend();
/// assert_eq!(array.into_row_major(), vec![1, 2, 3, 4, 5, 6]);
/// # Ok(())
/// # }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ use crate::{
cogs::dispersal_sampler::in_memory::InMemoryDispersalSampler,
};

use super::{contract::check_in_memory_dispersal_contract, InMemoryDispersalSamplerError};

mod dispersal;

#[allow(clippy::module_name_repetitions)]
Expand All @@ -23,13 +25,15 @@ pub struct InMemoryAliasDispersalSampler<M: MathsCore, H: Habitat<M>, G: RngCore
marker: PhantomData<(M, H, G)>,
}

#[contract_trait]
impl<M: MathsCore, H: Habitat<M>, G: RngCore<M>> InMemoryDispersalSampler<M, H, G>
for InMemoryAliasDispersalSampler<M, H, G>
{
/// Creates a new `InMemoryAliasDispersalSampler` from the
/// `dispersal` map and extent of the habitat map.
fn unchecked_new(dispersal: &Array2D<NonNegativeF64>, habitat: &H) -> Self {
fn new(
dispersal: &Array2D<NonNegativeF64>,
habitat: &H,
) -> Result<Self, InMemoryDispersalSamplerError> {
check_in_memory_dispersal_contract(dispersal, habitat)?;

let habitat_extent = habitat.get_extent();

let mut event_weights: Vec<(usize, NonNegativeF64)> =
Expand Down Expand Up @@ -71,10 +75,10 @@ impl<M: MathsCore, H: Habitat<M>, G: RngCore<M>> InMemoryDispersalSampler<M, H,
)
.unwrap(); // infallible by PRE

Self {
Ok(Self {
alias_dispersal,
marker: PhantomData::<(M, H, G)>,
}
})
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,22 @@ use necsim_core_bond::NonNegativeF64;

use crate::array2d::Array2D;

use super::InMemoryDispersalSamplerError;

#[allow(clippy::module_name_repetitions)]
pub fn explicit_in_memory_dispersal_check_contract<M: MathsCore, H: Habitat<M>>(
pub fn check_in_memory_dispersal_contract<M: MathsCore, H: Habitat<M>>(
dispersal: &Array2D<NonNegativeF64>,
habitat: &H,
) -> bool {
let habitat_width = habitat.get_extent().width();
) -> Result<(), InMemoryDispersalSamplerError> {
let habitat_extent = habitat.get_extent();

let habitat_area = usize::from(habitat_extent.width()) * usize::from(habitat_extent.height());

if dispersal.num_rows() != habitat_area || dispersal.num_columns() != habitat_area {
return Err(InMemoryDispersalSamplerError::DispersalMapSizeMismatch);
}

let habitat_width = habitat_extent.width();

for row_index in 0..dispersal.num_rows() {
#[allow(clippy::cast_possible_truncation)]
Expand All @@ -33,29 +43,27 @@ pub fn explicit_in_memory_dispersal_check_contract<M: MathsCore, H: Habitat<M>>(

if dispersal[(row_index, col_index)] > 0.0_f64 {
if habitat.get_habitat_at_location(&dispersal_target) == 0 {
// Dispersal from habitat to non-habitat
return false;
return Err(InMemoryDispersalSamplerError::DispersalToNonHabitat);
}

any_dispersal = true;
}
}

if !any_dispersal {
// No dispersal from habitat
return false;
return Err(InMemoryDispersalSamplerError::NoDispersalFromHabitat);
}
} else {
for col_index in 0..dispersal.num_columns() {
if dispersal[(row_index, col_index)] != 0.0_f64 {
// Dispersal probability from non-habitat must be 0.0
// - Dispersal from non-habitat (> 0.0)
// - Dispersal probabilities must be non-negative (< 0.0)
return false;
return Err(InMemoryDispersalSamplerError::DispersalFromNonHabitat);
}
}
}
}

true
Ok(())
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use necsim_core_bond::{ClosedUnitF64, NonNegativeF64};

use crate::{array2d::Array2D, cogs::dispersal_sampler::in_memory::InMemoryDispersalSampler};

use super::{contract::check_in_memory_dispersal_contract, InMemoryDispersalSamplerError};

mod contract;
mod dispersal;

Expand All @@ -21,18 +23,19 @@ pub struct InMemoryCumulativeDispersalSampler<M: MathsCore, H: Habitat<M>, G: Rn
marker: PhantomData<(M, H, G)>,
}

#[contract_trait]
impl<M: MathsCore, H: Habitat<M>, G: RngCore<M>> InMemoryDispersalSampler<M, H, G>
for InMemoryCumulativeDispersalSampler<M, H, G>
{
/// Creates a new `InMemoryCumulativeDispersalSampler` from the
/// `dispersal` map and extent of the habitat map.
#[allow(clippy::no_effect_underscore_binding)]
#[debug_ensures(ret
.explicit_only_valid_targets_dispersal_contract(old(habitat)),
"valid_dispersal_targets only allows dispersal to habitat"
)]
fn unchecked_new(dispersal: &Array2D<NonNegativeF64>, habitat: &H) -> Self {
#[debug_ensures(ret.as_ref().map_or(true, |ret| {
ret.explicit_only_valid_targets_dispersal_contract(old(habitat))
}), "valid_dispersal_targets only allows dispersal to habitat")]
fn new(
dispersal: &Array2D<NonNegativeF64>,
habitat: &H,
) -> Result<Self, InMemoryDispersalSamplerError> {
check_in_memory_dispersal_contract(dispersal, habitat)?;

let habitat_extent = habitat.get_extent();

let mut cumulative_dispersal =
Expand Down Expand Up @@ -101,11 +104,11 @@ impl<M: MathsCore, H: Habitat<M>, G: RngCore<M>> InMemoryDispersalSampler<M, H,
)
};

InMemoryCumulativeDispersalSampler {
Ok(Self {
cumulative_dispersal: Arc::from(cumulative_dispersal),
valid_dispersal_targets: Arc::from(valid_dispersal_targets),
marker: PhantomData::<(M, H, G)>,
}
})
}
}

Expand Down
56 changes: 38 additions & 18 deletions necsim/impls/no-std/src/cogs/dispersal_sampler/in_memory/mod.rs
Original file line number Diff line number Diff line change
@@ -1,35 +1,55 @@
#![allow(non_local_definitions)] // FIXME: displaydoc

use necsim_core::cogs::{DispersalSampler, Habitat, MathsCore, RngCore};
use necsim_core_bond::NonNegativeF64;

use crate::array2d::Array2D;

pub mod contract;
mod contract;

pub mod alias;
pub mod cumulative;
pub mod packed_alias;
pub mod packed_separable_alias;
pub mod separable_alias;

use contract::explicit_in_memory_dispersal_check_contract;

#[allow(clippy::module_name_repetitions)]
#[allow(clippy::inline_always, clippy::inline_fn_without_body)]
#[contract_trait]
pub trait InMemoryDispersalSampler<M: MathsCore, H: Habitat<M>, G: RngCore<M>>:
DispersalSampler<M, H, G> + Sized
{
// TODO: refactor to include contract and error here
#[debug_requires((
dispersal.num_columns() == (
usize::from(habitat.get_extent().width()) * usize::from(habitat.get_extent().height())
) && dispersal.num_rows() == (
usize::from(habitat.get_extent().width()) * usize::from(habitat.get_extent().height())
)
), "dispersal dimensions are consistent")]
#[debug_requires(
explicit_in_memory_dispersal_check_contract(dispersal, habitat),
"dispersal probabilities are consistent"
)]
fn unchecked_new(dispersal: &Array2D<NonNegativeF64>, habitat: &H) -> Self;
/// Creates a new in-memory dispersal sampler from the `dispersal` map and
/// the habitat.
///
/// # Errors
///
/// `Err(DispersalMapSizeMismatch)` is returned iff the dimensions of
/// `dispersal` are not `ExE` given `E=WxH` where habitat has width `W`
/// and height `W`.
///
/// `Err(DispersalToNonHabitat)` is returned iff any dispersal targets a
/// non-habitat cell.
///
/// `Err(DispersalFromNonHabitat)` is returned iff any non-habitat cell has
/// any dispersal.
///
/// `Err(NoDispersalFromHabitat)` is returned iff any habitat cell does not
/// have any dispersal.
fn new(
dispersal: &Array2D<NonNegativeF64>,
habitat: &H,
) -> Result<Self, InMemoryDispersalSamplerError>;
}

#[allow(clippy::module_name_repetitions)]
#[derive(Debug, displaydoc::Display)]
pub enum InMemoryDispersalSamplerError {
/** The size of the dispersal map is inconsistent with the size of the
habitat. */
DispersalMapSizeMismatch,
/// Some dispersal targets a non-habitat cell.
DispersalToNonHabitat,
/// Some non-habitat cell has outgoing dispersals.
DispersalFromNonHabitat,
/// Some habitat cell does not have any outgoing dispersals.
NoDispersalFromHabitat,
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ use crate::{

mod dispersal;

use super::InMemoryDispersalSampler;
use super::{
contract::check_in_memory_dispersal_contract, InMemoryDispersalSampler,
InMemoryDispersalSamplerError,
};

#[derive(Clone, Debug, TypeLayout)]
#[allow(clippy::module_name_repetitions)]
Expand Down Expand Up @@ -51,13 +54,15 @@ pub struct InMemoryPackedAliasDispersalSampler<M: MathsCore, H: Habitat<M>, G: R
marker: PhantomData<(M, H, G)>,
}

#[contract_trait]
impl<M: MathsCore, H: Habitat<M>, G: RngCore<M>> InMemoryDispersalSampler<M, H, G>
for InMemoryPackedAliasDispersalSampler<M, H, G>
{
/// Creates a new `InMemoryPackedAliasDispersalSampler` from the
/// `dispersal` map and extent of the habitat map.
fn unchecked_new(dispersal: &Array2D<NonNegativeF64>, habitat: &H) -> Self {
fn new(
dispersal: &Array2D<NonNegativeF64>,
habitat: &H,
) -> Result<Self, InMemoryDispersalSamplerError> {
check_in_memory_dispersal_contract(dispersal, habitat)?;

let habitat_extent = habitat.get_extent();

let mut event_weights: Vec<(usize, NonNegativeF64)> =
Expand Down Expand Up @@ -106,11 +111,11 @@ impl<M: MathsCore, H: Habitat<M>, G: RngCore<M>> InMemoryDispersalSampler<M, H,
)
.unwrap(); // infallible by PRE;

Self {
Ok(Self {
alias_dispersal_ranges,
alias_dispersal_buffer: Arc::from(alias_dispersal_buffer.into_boxed_slice()),
marker: PhantomData::<(M, H, G)>,
}
})
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ use crate::{

mod dispersal;

use super::InMemoryDispersalSampler;
use super::{
contract::check_in_memory_dispersal_contract, InMemoryDispersalSampler,
InMemoryDispersalSamplerError,
};

#[derive(Clone, Debug, TypeLayout)]
#[allow(clippy::module_name_repetitions)]
Expand Down Expand Up @@ -52,13 +55,15 @@ pub struct InMemoryPackedSeparableAliasDispersalSampler<M: MathsCore, H: Habitat
marker: PhantomData<(M, H, G)>,
}

#[contract_trait]
impl<M: MathsCore, H: Habitat<M>, G: RngCore<M>> InMemoryDispersalSampler<M, H, G>
for InMemoryPackedSeparableAliasDispersalSampler<M, H, G>
{
/// Creates a new `InMemoryPackedSeparableAliasDispersalSampler` from the
/// `dispersal` map and extent of the habitat map.
fn unchecked_new(dispersal: &Array2D<NonNegativeF64>, habitat: &H) -> Self {
fn new(
dispersal: &Array2D<NonNegativeF64>,
habitat: &H,
) -> Result<Self, InMemoryDispersalSamplerError> {
check_in_memory_dispersal_contract(dispersal, habitat)?;

let habitat_extent = habitat.get_extent();

let mut event_weights: Vec<(usize, NonNegativeF64)> =
Expand Down Expand Up @@ -178,12 +183,12 @@ impl<M: MathsCore, H: Habitat<M>, G: RngCore<M>> InMemoryDispersalSampler<M, H,
)
.unwrap(); // infallible by PRE;

Self {
Ok(Self {
alias_dispersal_ranges,
self_dispersal: self_dispersal.switch_backend(),
alias_dispersal_buffer: Arc::from(alias_dispersal_buffer.into_boxed_slice()),
marker: PhantomData::<(M, H, G)>,
}
})
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ use crate::{
cogs::dispersal_sampler::in_memory::InMemoryDispersalSampler,
};

use super::{contract::check_in_memory_dispersal_contract, InMemoryDispersalSamplerError};

mod dispersal;

#[allow(clippy::module_name_repetitions)]
Expand All @@ -24,13 +26,15 @@ pub struct InMemorySeparableAliasDispersalSampler<M: MathsCore, H: Habitat<M>, G
_marker: PhantomData<(M, H, G)>,
}

#[contract_trait]
impl<M: MathsCore, H: Habitat<M>, G: RngCore<M>> InMemoryDispersalSampler<M, H, G>
for InMemorySeparableAliasDispersalSampler<M, H, G>
{
/// Creates a new `InMemorySeparableAliasDispersalSampler` from the
/// `dispersal` map and extent of the habitat map.
fn unchecked_new(dispersal: &Array2D<NonNegativeF64>, habitat: &H) -> Self {
fn new(
dispersal: &Array2D<NonNegativeF64>,
habitat: &H,
) -> Result<Self, InMemoryDispersalSamplerError> {
check_in_memory_dispersal_contract(dispersal, habitat)?;

let habitat_extent = habitat.get_extent();

let mut event_weights: Vec<(usize, NonNegativeF64)> =
Expand Down Expand Up @@ -105,11 +109,11 @@ impl<M: MathsCore, H: Habitat<M>, G: RngCore<M>> InMemoryDispersalSampler<M, H,
)
.unwrap(); // infallible by PRE

Self {
Ok(Self {
alias_dispersal,
self_dispersal: self_dispersal.switch_backend(),
_marker: PhantomData::<(M, H, G)>,
}
})
}
}

Expand Down
Loading

0 comments on commit de6728f

Please sign in to comment.