use anyhow::{Context, Result};
use itertools::izip;
use num::Float;
use rand::prelude::*;
use rand_distr::{Normal, StandardNormal};
use core::fmt::Debug;
use crate::{utils, Bounds, Point};
pub type Custom<'a, F, R, const N: usize> =
Box<dyn Fn(&Point<F, N>, &Bounds<F, N>, &mut R) -> Result<Point<F, N>> + 'a>;
pub enum Method<'a, F, R, const N: usize>
where
F: Float + Debug,
StandardNormal: Distribution<F>,
R: Rng,
{
Normal {
sd: F,
},
#[allow(clippy::complexity)]
Custom {
f: Custom<'a, F, R, N>,
},
}
impl<'a, F, R, const N: usize> Method<'a, F, R, N>
where
F: Float + Debug,
StandardNormal: Distribution<F>,
R: Rng,
{
pub fn neighbour(
&self,
p: &Point<F, N>,
bounds: &Bounds<F, N>,
rng: &mut R,
) -> Result<Point<F, N>> {
match *self {
Method::Normal { sd } => {
let mut new_p = [F::zero(); N];
izip!(&mut new_p, p, bounds)
.try_for_each(|(new_c, &c, r)| -> Result<()> {
let d = Normal::new(c, sd)
.with_context(|| "Couldn't create a normal distribution")?;
let mut s = d.sample(rng);
while !r.contains(&s) {
s = d.sample(rng);
}
*new_c = utils::cast(s)?;
Ok(())
})
.with_context(|| "Couldn't generate a new point")?;
Ok(new_p)
}
Method::Custom { ref f } => f(p, bounds, rng),
}
}
}