use core::f64;
use std::cell::RefCell;
use ndarray::prelude::*;
use ndarray_stats::QuantileExt;
use rand::{
Rng, RngExt, SeedableRng,
distr::{Distribution, weighted::WeightedIndex},
};
use rand_distr::Exp;
use rayon::prelude::*;
use crate::{
datasets::{CatSample, CatTable, CatTrj, CatType, GaussTable},
models::{BN, CIM, CPD, CTBN, CatBN, CatCTBN, GaussBN, Labelled},
samplers::{BNSampler, CTBNSampler, ParBNSampler, ParCTBNSampler},
set,
types::{EPSILON, Error, Result},
};
#[derive(Debug)]
pub struct ForwardSampler<'a, R, M> {
rng: RefCell<&'a mut R>,
model: &'a M,
}
impl<'a, R, M> ForwardSampler<'a, R, M> {
#[inline]
pub fn new(rng: &'a mut R, model: &'a M) -> Result<Self> {
let rng = RefCell::new(rng);
Ok(Self { rng, model })
}
}
impl<R: Rng> BNSampler<CatBN> for ForwardSampler<'_, R, CatBN> {
type Sample = <CatBN as BN>::Sample;
type Samples = <CatBN as BN>::Samples;
fn sample(&self) -> Result<Self::Sample> {
let mut rng = self.rng.borrow_mut();
let mut sample = Array::zeros(self.model.labels().len());
for &i in self.model.topological_order() {
let cpd_i = &self.model.cpds()[i];
let pa_i = self.model.graph().parents(&set![i])?;
let pa_i = pa_i.iter().map(|&z| sample[z]).collect();
sample[i] = cpd_i.sample(&mut rng, &pa_i)?[0];
}
Ok(sample)
}
fn sample_n(&self, n: usize) -> Result<Self::Samples> {
let mut dataset = Array::zeros((n, self.model.labels().len()));
dataset
.rows_mut()
.into_iter()
.try_for_each(|mut row| -> Result<_> {
row.assign(&self.sample()?);
Ok(())
})?;
CatTable::new(self.model.states().clone(), dataset)
}
}
impl<R: Rng + SeedableRng> ParBNSampler<CatBN> for ForwardSampler<'_, R, CatBN> {
type Samples = <CatBN as BN>::Samples;
fn par_sample_n(&self, n: usize) -> Result<Self::Samples> {
let rng = self.rng.borrow_mut();
let seeds: Vec<_> = rng.random_iter().take(n).collect();
let mut samples = Array::zeros((n, self.model.labels().len()));
seeds
.into_par_iter()
.zip(samples.axis_iter_mut(Axis(0)))
.try_for_each(|(seed, mut row)| -> Result<()> {
let mut rng = R::seed_from_u64(seed);
let sampler = ForwardSampler::new(&mut rng, self.model)?;
row.assign(&sampler.sample()?);
Ok(())
})?;
CatTable::new(self.model.states().clone(), samples)
}
}
impl<R: Rng> BNSampler<GaussBN> for ForwardSampler<'_, R, GaussBN> {
type Sample = <GaussBN as BN>::Sample;
type Samples = <GaussBN as BN>::Samples;
fn sample(&self) -> Result<Self::Sample> {
let mut rng = self.rng.borrow_mut();
let mut sample = Array::zeros(self.model.labels().len());
for &i in self.model.topological_order() {
let cpd_i = &self.model.cpds()[i];
let pa_i = self.model.graph().parents(&set![i])?;
let pa_i = pa_i.iter().map(|&z| sample[z]).collect();
sample[i] = cpd_i.sample(&mut rng, &pa_i)?[0];
}
Ok(sample)
}
fn sample_n(&self, n: usize) -> Result<Self::Samples> {
let mut samples = Array::zeros((n, self.model.labels().len()));
samples
.rows_mut()
.into_iter()
.try_for_each(|mut row| -> Result<_> {
row.assign(&self.sample()?);
Ok(())
})?;
GaussTable::new(self.model.labels().clone(), samples)
}
}
impl<R: Rng + SeedableRng> ParBNSampler<GaussBN> for ForwardSampler<'_, R, GaussBN> {
type Samples = <GaussBN as BN>::Samples;
fn par_sample_n(&self, n: usize) -> Result<Self::Samples> {
let rng = self.rng.borrow_mut();
let seeds: Vec<_> = rng.random_iter().take(n).collect();
let mut samples = Array::zeros((n, self.model.labels().len()));
seeds
.into_par_iter()
.zip(samples.axis_iter_mut(Axis(0)))
.try_for_each(|(seed, mut row)| -> Result<()> {
let mut rng = R::seed_from_u64(seed);
let sampler = ForwardSampler::new(&mut rng, self.model)?;
row.assign(&sampler.sample()?);
Ok(())
})?;
GaussTable::new(self.model.labels().clone(), samples)
}
}
impl<R: Rng> ForwardSampler<'_, R, CatCTBN> {
fn sample_time(&self, event: &CatSample, i: usize) -> Result<f64> {
let x = event[i] as usize;
let cim_i = &self.model.cims()[i];
let pa_i = self.model.graph().parents(&set![i])?;
let pa_i = pa_i.iter().map(|&z| event[z] as usize);
let pa_i = cim_i.conditioning_multi_index().ravel(pa_i);
let q_i_x = -cim_i.parameters()[[pa_i, x, x]];
let exp_i_x =
Exp::new(q_i_x).map_err(|e| Error::RandDistr(&format!("Invalid lambda: {}", e)))?;
Ok(exp_i_x.sample(&mut self.rng.borrow_mut()))
}
}
impl<R: Rng> CTBNSampler<CatCTBN> for ForwardSampler<'_, R, CatCTBN> {
type Sample = <CatCTBN as CTBN>::Trajectory;
type Samples = <CatCTBN as CTBN>::Trajectories;
#[inline]
fn sample_by_length(&self, max_length: usize) -> Result<Self::Sample> {
self.sample_by_length_or_time(max_length, f64::MAX)
}
#[inline]
fn sample_by_time(&self, max_time: f64) -> Result<Self::Sample> {
self.sample_by_length_or_time(usize::MAX, max_time)
}
fn sample_by_length_or_time(&self, max_length: usize, max_time: f64) -> Result<Self::Sample> {
if max_length == 0 {
return Err(Error::InvalidParameter(
"max_length",
"The maximum length of the trajectory must be strictly positive.",
));
}
if max_time <= 0. {
return Err(Error::InvalidParameter("max_time", "must be positive"));
}
let mut sample_events = Vec::new();
let mut sample_times = Vec::new();
let mut event = {
let mut rng = self.rng.borrow_mut();
let initial = self.model.initial_distribution();
let initial = ForwardSampler::new(&mut rng, initial)?;
initial.sample()?
};
sample_events.push(event.clone());
sample_times.push(0.);
let mut times: Array1<_> = (0..event.len())
.map(|i| self.sample_time(&event, i))
.collect::<Result<_>>()?;
let mut i = times
.argmin()
.map_err(|e| Error::Stats(&format!("Failed to find min time: {}", e)))?;
let mut time = times[i];
while sample_events.len() < max_length && time < max_time {
let x = event[i] as usize;
let cim_i = &self.model.cims()[i];
let pa_i = self.model.graph().parents(&set![i])?;
let pa_i = pa_i.iter().map(|&z| event[z] as usize);
let pa_i = cim_i.conditioning_multi_index().ravel(pa_i);
let mut q_i_zx = cim_i.parameters().slice(s![pa_i, x, ..]).to_owned();
q_i_zx[x] = 0.;
q_i_zx /= q_i_zx.sum();
let s_i_zx = WeightedIndex::new(&q_i_zx)
.map_err(|e| Error::RandDistr(&format!("Invalid probabilities: {}", e)))?;
event[i] = s_i_zx.sample(&mut self.rng.borrow_mut()) as CatType;
sample_events.push(event.clone());
sample_times.push(time);
for j in std::iter::once(i).chain(self.model.graph().children(&set![i])?) {
times[j] = time + self.sample_time(&event, j)?;
}
times += EPSILON;
i = times
.argmin()
.map_err(|e| Error::Stats(&format!("Failed to find min time: {}", e)))?;
time = times[i];
}
let states = self.model.states().clone();
let shape = (sample_events.len(), sample_events[0].len());
let sample_events = Array::from_iter(sample_events.into_iter().flatten())
.into_shape_with_order(shape)
.map_err(|e| Error::Shape(&e.to_string()))?;
let sample_times = Array::from_iter(sample_times);
CatTrj::new(states, sample_events, sample_times)
}
#[inline]
fn sample_n_by_length(&self, max_length: usize, n: usize) -> Result<Self::Samples> {
(0..n).map(|_| self.sample_by_length(max_length)).collect()
}
#[inline]
fn sample_n_by_time(&self, max_time: f64, n: usize) -> Result<Self::Samples> {
(0..n).map(|_| self.sample_by_time(max_time)).collect()
}
#[inline]
fn sample_n_by_length_or_time(
&self,
max_length: usize,
max_time: f64,
n: usize,
) -> Result<Self::Samples> {
(0..n)
.map(|_| self.sample_by_length_or_time(max_length, max_time))
.collect()
}
}
impl<R: Rng + SeedableRng> ParCTBNSampler<CatCTBN> for ForwardSampler<'_, R, CatCTBN> {
type Samples = <CatCTBN as CTBN>::Trajectories;
#[inline]
fn par_sample_n_by_length(&self, max_length: usize, n: usize) -> Result<Self::Samples> {
self.par_sample_n_by_length_or_time(max_length, f64::MAX, n)
}
#[inline]
fn par_sample_n_by_time(&self, max_time: f64, n: usize) -> Result<Self::Samples> {
self.par_sample_n_by_length_or_time(usize::MAX, max_time, n)
}
fn par_sample_n_by_length_or_time(
&self,
max_length: usize,
max_time: f64,
n: usize,
) -> Result<Self::Samples> {
let rng = self.rng.borrow_mut();
let seeds: Vec<_> = rng.random_iter().take(n).collect();
seeds
.into_par_iter()
.map(|seed| {
let mut rng = R::seed_from_u64(seed);
let sampler = ForwardSampler::new(&mut rng, self.model)?;
sampler.sample_by_length_or_time(max_length, max_time)
})
.collect()
}
}