Skip to content

Commit 79c22a3

Browse files
committed
Implemented untested modular distribution samplers
1 parent f6071d1 commit 79c22a3

19 files changed

+1191
-432
lines changed

necsim/core/bond/src/closed_open_unit_f64.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ impl From<ClosedOpenUnitF64> for f64 {
4545
}
4646
}
4747

48+
impl TryFrom<ClosedUnitF64> for ClosedOpenUnitF64 {
49+
type Error = ClosedOpenUnitF64Error;
50+
51+
fn try_from(value: ClosedUnitF64) -> Result<Self, Self::Error> {
52+
Self::new(value.get())
53+
}
54+
}
55+
4856
impl fmt::Debug for ClosedOpenUnitF64 {
4957
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
5058
struct ClosedOpenUnitF64Range(f64);

necsim/core/bond/src/open_closed_unit_f64.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use core::{
88
use necsim_core_maths::MathsCore;
99
use serde::{Deserialize, Serialize};
1010

11-
use crate::NonPositiveF64;
11+
use crate::{ClosedUnitF64, NonPositiveF64};
1212

1313
#[derive(Debug)]
1414
#[allow(clippy::module_name_repetitions)]
@@ -46,6 +46,14 @@ impl From<OpenClosedUnitF64> for f64 {
4646
}
4747
}
4848

49+
impl TryFrom<ClosedUnitF64> for OpenClosedUnitF64 {
50+
type Error = OpenClosedUnitF64Error;
51+
52+
fn try_from(value: ClosedUnitF64) -> Result<Self, Self::Error> {
53+
Self::new(value.get())
54+
}
55+
}
56+
4957
impl fmt::Debug for OpenClosedUnitF64 {
5058
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
5159
struct OpenClosedUnitF64Range(f64);
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
use necsim_core::cogs::{distribution::Bernoulli, DistributionSampler, MathsCore, RngCore};
2+
use necsim_core_bond::ClosedUnitF64;
3+
4+
#[allow(clippy::module_name_repetitions)]
5+
pub struct Bernoulli64BitSampler;
6+
7+
impl<M: MathsCore, R: RngCore, S> DistributionSampler<M, R, S, Bernoulli>
8+
for Bernoulli64BitSampler
9+
{
10+
type ConcreteSampler = Self;
11+
12+
fn concrete(&self) -> &Self::ConcreteSampler {
13+
self
14+
}
15+
16+
fn sample_distribution(&self, rng: &mut R, _samplers: &S, probability: ClosedUnitF64) -> bool {
17+
#[allow(clippy::cast_precision_loss)]
18+
const SCALE: f64 = 2.0 * (1u64 << 63) as f64;
19+
20+
// Safety:
21+
// (a) 0 <= probability < 1: probability * SCALE is in [0, 2^64)
22+
// since 1 - 2^-53 is before 1.0
23+
// (b) probability == 1 : p_u64 is undefined
24+
// this case is checked for in the return
25+
let p_u64 = unsafe { (probability.get() * SCALE).to_int_unchecked::<u64>() };
26+
27+
#[allow(clippy::float_cmp)]
28+
{
29+
(rng.sample_u64() < p_u64) || (probability == 1.0_f64)
30+
}
31+
}
32+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
use necsim_core::cogs::{
2+
distribution::{Bernoulli, RawDistribution, UniformClosedOpenUnit},
3+
DistributionSampler, MathsCore, RngCore,
4+
};
5+
use necsim_core_bond::ClosedUnitF64;
6+
7+
#[allow(clippy::module_name_repetitions)]
8+
pub struct BernoulliFromUnitSampler;
9+
10+
impl<M: MathsCore, R: RngCore, S: DistributionSampler<M, R, S, UniformClosedOpenUnit>>
11+
DistributionSampler<M, R, S, Bernoulli> for BernoulliFromUnitSampler
12+
{
13+
type ConcreteSampler = Self;
14+
15+
fn concrete(&self) -> &Self::ConcreteSampler {
16+
self
17+
}
18+
19+
fn sample_distribution(&self, rng: &mut R, samplers: &S, probability: ClosedUnitF64) -> bool {
20+
// if probability == 1, then U[0, 1) always < 1.0
21+
// if probability == 0, then U[0, 1) never < 0.0
22+
UniformClosedOpenUnit::sample_raw(rng, samplers) < probability
23+
}
24+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use necsim_core::cogs::{
2+
distribution::{Exponential, Lambda, RawDistribution, UniformOpenClosedUnit},
3+
DistributionSampler, MathsCore, RngCore,
4+
};
5+
use necsim_core_bond::NonNegativeF64;
6+
7+
#[allow(clippy::module_name_repetitions)]
8+
pub struct ExponentialInverseTransformSampler;
9+
10+
impl<M: MathsCore, R: RngCore, S: DistributionSampler<M, R, S, UniformOpenClosedUnit>>
11+
DistributionSampler<M, R, S, Exponential> for ExponentialInverseTransformSampler
12+
{
13+
type ConcreteSampler = Self;
14+
15+
fn concrete(&self) -> &Self::ConcreteSampler {
16+
self
17+
}
18+
19+
fn sample_distribution(
20+
&self,
21+
rng: &mut R,
22+
samplers: &S,
23+
Lambda(lambda): Lambda,
24+
) -> NonNegativeF64 {
25+
let u01 = UniformOpenClosedUnit::sample_raw(rng, samplers);
26+
27+
// Inverse transform sample: X = -ln(U(0,1]) / lambda
28+
-u01.ln::<M>() / lambda
29+
}
30+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
use core::num::{NonZeroU128, NonZeroU32, NonZeroU64, NonZeroUsize};
2+
3+
use necsim_core::cogs::{
4+
distribution::{
5+
IndexU128, IndexU32, IndexU64, IndexUsize, Length, RawDistribution, UniformClosedOpenUnit,
6+
},
7+
DistributionSampler, MathsCore, RngCore,
8+
};
9+
10+
#[allow(clippy::module_name_repetitions)]
11+
pub struct IndexFromUnitSampler;
12+
13+
impl<M: MathsCore, R: RngCore, S: DistributionSampler<M, R, S, UniformClosedOpenUnit>>
14+
DistributionSampler<M, R, S, IndexUsize> for IndexFromUnitSampler
15+
{
16+
type ConcreteSampler = Self;
17+
18+
fn concrete(&self) -> &Self::ConcreteSampler {
19+
self
20+
}
21+
22+
fn sample_distribution(
23+
&self,
24+
rng: &mut R,
25+
samplers: &S,
26+
Length(length): Length<NonZeroUsize>,
27+
) -> usize {
28+
let u01 = UniformClosedOpenUnit::sample_raw(rng, samplers);
29+
30+
// Safety: U[0, 1) * length in [0, 2^[32/64]) is a valid [u32/u64]
31+
// since (1 - 2^-53) * 2^[32/64] <= (2^[32/64] - 1)
32+
#[allow(clippy::cast_precision_loss)]
33+
let index =
34+
unsafe { M::floor(u01.get() * (length.get() as f64)).to_int_unchecked::<usize>() };
35+
36+
if cfg!(target_pointer_width = "32") {
37+
// Note: [0, 2^32) is losslessly represented in f64
38+
index
39+
} else {
40+
// Note: Ensure index < length despite
41+
// usize->f64->usize precision loss
42+
index.min(length.get() - 1)
43+
}
44+
}
45+
}
46+
47+
impl<M: MathsCore, R: RngCore, S: DistributionSampler<M, R, S, UniformClosedOpenUnit>>
48+
DistributionSampler<M, R, S, IndexU32> for IndexFromUnitSampler
49+
{
50+
type ConcreteSampler = Self;
51+
52+
fn concrete(&self) -> &Self::ConcreteSampler {
53+
self
54+
}
55+
56+
fn sample_distribution(
57+
&self,
58+
rng: &mut R,
59+
samplers: &S,
60+
Length(length): Length<NonZeroU32>,
61+
) -> u32 {
62+
let u01 = UniformClosedOpenUnit::sample_raw(rng, samplers);
63+
64+
// Safety: U[0, 1) * length in [0, 2^32) is losslessly represented
65+
// in both f64 and u32
66+
unsafe { M::floor(u01.get() * f64::from(length.get())).to_int_unchecked::<u32>() }
67+
}
68+
}
69+
70+
impl<M: MathsCore, R: RngCore, S: DistributionSampler<M, R, S, UniformClosedOpenUnit>>
71+
DistributionSampler<M, R, S, IndexU64> for IndexFromUnitSampler
72+
{
73+
type ConcreteSampler = Self;
74+
75+
fn concrete(&self) -> &Self::ConcreteSampler {
76+
self
77+
}
78+
79+
fn sample_distribution(
80+
&self,
81+
rng: &mut R,
82+
samplers: &S,
83+
Length(length): Length<NonZeroU64>,
84+
) -> u64 {
85+
let u01 = UniformClosedOpenUnit::sample_raw(rng, samplers);
86+
87+
// Safety: U[0, 1) * length in [0, 2^64) is a valid u64
88+
// since (1 - 2^-53) * 2^64 <= (2^64 - 1)
89+
#[allow(clippy::cast_precision_loss)]
90+
let index =
91+
unsafe { M::floor(u01.get() * (length.get() as f64)).to_int_unchecked::<u64>() };
92+
93+
// Note: Ensure index < length despite u64->f64->u64 precision loss
94+
index.min(length.get() - 1)
95+
}
96+
}
97+
98+
impl<M: MathsCore, R: RngCore, S: DistributionSampler<M, R, S, UniformClosedOpenUnit>>
99+
DistributionSampler<M, R, S, IndexU128> for IndexFromUnitSampler
100+
{
101+
type ConcreteSampler = Self;
102+
103+
fn concrete(&self) -> &Self::ConcreteSampler {
104+
self
105+
}
106+
107+
fn sample_distribution(
108+
&self,
109+
rng: &mut R,
110+
samplers: &S,
111+
Length(length): Length<NonZeroU128>,
112+
) -> u128 {
113+
let u01 = UniformClosedOpenUnit::sample_raw(rng, samplers);
114+
115+
// Safety: U[0, 1) * length in [0, 2^128) is a valid u128
116+
// since (1 - 2^-53) * 2^128 <= (2^128 - 1)
117+
#[allow(clippy::cast_precision_loss)]
118+
let index =
119+
unsafe { M::floor(u01.get() * (length.get() as f64)).to_int_unchecked::<u128>() };
120+
121+
// Note: Ensure index < length despite u128->f64->u128 precision loss
122+
index.min(length.get() - 1)
123+
}
124+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
use core::num::{NonZeroU128, NonZeroU32, NonZeroU64, NonZeroUsize};
2+
3+
use necsim_core::cogs::{
4+
distribution::{IndexU128, IndexU32, IndexU64, IndexUsize, Length, RawDistribution},
5+
DistributionSampler, MathsCore, RngCore,
6+
};
7+
8+
#[allow(clippy::module_name_repetitions)]
9+
pub struct IndexFromWideMulSampler;
10+
11+
impl<M: MathsCore, R: RngCore, S> DistributionSampler<M, R, S, IndexUsize>
12+
for IndexFromWideMulSampler
13+
{
14+
type ConcreteSampler = Self;
15+
16+
fn concrete(&self) -> &Self::ConcreteSampler {
17+
self
18+
}
19+
20+
#[inline]
21+
fn sample_distribution(
22+
&self,
23+
rng: &mut R,
24+
_samplers: &S,
25+
Length(length): Length<NonZeroUsize>,
26+
) -> usize {
27+
#[allow(clippy::cast_possible_truncation)]
28+
if cfg!(target_pointer_width = "32") {
29+
IndexU32::sample_raw_with::<M, R, Self>(
30+
rng,
31+
self,
32+
Length(unsafe { NonZeroU32::new_unchecked(length.get() as u32) }),
33+
) as usize
34+
} else {
35+
IndexU64::sample_raw_with::<M, R, Self>(
36+
rng,
37+
self,
38+
Length(unsafe { NonZeroU64::new_unchecked(length.get() as u64) }),
39+
) as usize
40+
}
41+
}
42+
}
43+
44+
impl<M: MathsCore, R: RngCore, S> DistributionSampler<M, R, S, IndexU32>
45+
for IndexFromWideMulSampler
46+
{
47+
type ConcreteSampler = Self;
48+
49+
fn concrete(&self) -> &Self::ConcreteSampler {
50+
self
51+
}
52+
53+
fn sample_distribution(
54+
&self,
55+
rng: &mut R,
56+
_samplers: &S,
57+
Length(length): Length<NonZeroU32>,
58+
) -> u32 {
59+
// Sample U(0, length - 1) using a widening multiplication
60+
// Note: Some slight bias is traded for only needing one u64 sample
61+
// Note: Should optimise to a single 64 bit (high-only) multiplication
62+
#[allow(clippy::cast_possible_truncation)]
63+
{
64+
(((u128::from(rng.sample_u64()) * u128::from(length.get())) >> 64) & u128::from(!0_u32))
65+
as u32
66+
}
67+
}
68+
}
69+
70+
impl<M: MathsCore, R: RngCore, S> DistributionSampler<M, R, S, IndexU64>
71+
for IndexFromWideMulSampler
72+
{
73+
type ConcreteSampler = Self;
74+
75+
fn concrete(&self) -> &Self::ConcreteSampler {
76+
self
77+
}
78+
79+
fn sample_distribution(
80+
&self,
81+
rng: &mut R,
82+
_samplers: &S,
83+
Length(length): Length<NonZeroU64>,
84+
) -> u64 {
85+
// Sample U(0, length - 1) using a widening multiplication
86+
// Note: Some slight bias is traded for only needing one u64 sample
87+
// Note: Should optimise to a single 64 bit (high-only) multiplication
88+
#[allow(clippy::cast_possible_truncation)]
89+
{
90+
(((u128::from(rng.sample_u64()) * u128::from(length.get())) >> 64) & u128::from(!0_u64))
91+
as u64
92+
}
93+
}
94+
}
95+
96+
impl<M: MathsCore, R: RngCore, S> DistributionSampler<M, R, S, IndexU128>
97+
for IndexFromWideMulSampler
98+
{
99+
type ConcreteSampler = Self;
100+
101+
fn concrete(&self) -> &Self::ConcreteSampler {
102+
self
103+
}
104+
105+
fn sample_distribution(
106+
&self,
107+
rng: &mut R,
108+
_samplers: &S,
109+
Length(length): Length<NonZeroU128>,
110+
) -> u128 {
111+
// Sample U(0, length - 1) using a widening multiplication
112+
// Note: Some slight bias is traded for only needing one u128 sample
113+
114+
const LOWER_MASK: u128 = !0 >> 64;
115+
116+
let raw_hi = u128::from(rng.sample_u64());
117+
let raw_lo = u128::from(rng.sample_u64());
118+
119+
// 256-bit multiplication (hi, lo) = (raw_hi, raw_lo) * length
120+
let mut low = raw_lo * (length.get() & LOWER_MASK);
121+
let mut t = low >> 64;
122+
low &= LOWER_MASK;
123+
t += raw_hi * (length.get() & LOWER_MASK);
124+
low += (t & LOWER_MASK) << 64;
125+
let mut high = t >> 64;
126+
t = low >> 64;
127+
// low-only: low &= LOWER_MASK;
128+
t += (length.get() >> 64) * raw_lo;
129+
// low-only: low += (t & LOWER_MASK) << 64;
130+
high += t >> 64;
131+
high += raw_hi * (length.get() >> 64);
132+
133+
high
134+
}
135+
}

0 commit comments

Comments
 (0)