use rand::prelude::*;
use crate::{
models::{BN, CatSupport, MixedBN, MixedCPD, MixedSupport},
random::{Random, RngCatCPD, RngDag, RngGaussCPD},
set,
types::{Error, Labels, Map, Result},
};
pub struct RngMixedBN<'a, R>
where
R: Rng,
{
rng: &'a mut R,
labels: &'a Labels,
support: &'a Map<String, MixedSupport>,
alpha: f64,
s_a: f64,
s_b: f64,
evidence: f64,
probability: f64,
}
impl<'a, R> RngMixedBN<'a, R>
where
R: Rng,
{
#[allow(clippy::too_many_arguments)]
pub fn new(
rng: &'a mut R,
labels: &'a Labels,
support: &'a Map<String, MixedSupport>,
alpha: f64,
s_a: f64,
s_b: f64,
evidence: f64,
probability: f64,
) -> Result<Self> {
if alpha <= 0.0 {
return Err(Error::InvalidParameter("alpha", "must be positive"));
}
if s_a <= 0.0 {
return Err(Error::InvalidParameter("s_a", "must be positive"));
}
if s_b <= 0.0 {
return Err(Error::InvalidParameter("s_b", "must be positive"));
}
if evidence <= 0.0 {
return Err(Error::InvalidParameter("e", "must be positive"));
}
if !(0.0..=1.0).contains(&probability) {
return Err(Error::InvalidParameter("p", "must be in [0, 1]"));
}
Ok(Self {
rng,
labels,
support,
alpha,
s_a,
s_b,
evidence,
probability,
})
}
}
impl<R> Random for RngMixedBN<'_, R>
where
R: Rng,
{
type Output = Result<MixedBN>;
fn random(&mut self) -> Self::Output {
let graph = RngDag::new(self.rng, self.labels, self.probability)?.random()?;
let cpds = self
.labels
.iter()
.enumerate()
.map(|(i, x)| {
let pa_i = graph.parents(&set![i])?;
let mixed_support = &self.support[x];
match mixed_support {
MixedSupport::Categorical(cat_support) => {
let mut support = CatSupport::default();
support.insert(x.clone(), cat_support[x].clone());
let conditioning_support: CatSupport = pa_i
.iter()
.map(|&j| {
let y = &self.labels[j];
match &self.support[y] {
MixedSupport::Categorical(stats) => {
(y.clone(), stats[y].clone())
}
_ => unreachable!("parents must match CPD type"),
}
})
.collect();
let distribution =
RngCatCPD::new(self.rng, &support, &conditioning_support, self.alpha)?
.random()?;
Ok(MixedCPD::Categorical(distribution))
}
MixedSupport::Gaussian(_) => {
let v_labels = crate::labels![x.clone()];
let conditioning_labels: Labels =
pa_i.iter().map(|&j| self.labels[j].clone()).collect();
let distribution = RngGaussCPD::new(
self.rng,
&v_labels,
&conditioning_labels,
self.s_a,
self.s_b,
self.evidence,
)?
.random()?;
Ok(MixedCPD::Gaussian(distribution))
}
}
})
.collect::<Result<Vec<_>>>()?;
MixedBN::new(graph, cpds)
}
}