causal_hub/random/models/bayesian_network/mixed/
model.rs1use rand::prelude::*;
2
3use crate::{
4 models::{BN, CatSupport, MixedBN, MixedCPD, MixedSupport},
5 random::{Random, RngCatCPD, RngDag, RngGaussCPD},
6 set,
7 types::{Error, Labels, Map, Result},
8};
9
10pub struct RngMixedBN<'a, R>
12where
13 R: Rng,
14{
15 rng: &'a mut R,
16 labels: &'a Labels,
17 support: &'a Map<String, MixedSupport>,
18 alpha: f64,
19 s_a: f64,
20 s_b: f64,
21 evidence: f64,
22 probability: f64,
23}
24
25impl<'a, R> RngMixedBN<'a, R>
26where
27 R: Rng,
28{
29 #[allow(clippy::too_many_arguments)]
53 pub fn new(
54 rng: &'a mut R,
55 labels: &'a Labels,
56 support: &'a Map<String, MixedSupport>,
57 alpha: f64,
58 s_a: f64,
59 s_b: f64,
60 evidence: f64,
61 probability: f64,
62 ) -> Result<Self> {
63 if alpha <= 0.0 {
64 return Err(Error::InvalidParameter("alpha", "must be positive"));
65 }
66 if s_a <= 0.0 {
67 return Err(Error::InvalidParameter("s_a", "must be positive"));
68 }
69 if s_b <= 0.0 {
70 return Err(Error::InvalidParameter("s_b", "must be positive"));
71 }
72 if evidence <= 0.0 {
73 return Err(Error::InvalidParameter("e", "must be positive"));
74 }
75 if !(0.0..=1.0).contains(&probability) {
76 return Err(Error::InvalidParameter("p", "must be in [0, 1]"));
77 }
78
79 Ok(Self {
80 rng,
81 labels,
82 support,
83 alpha,
84 s_a,
85 s_b,
86 evidence,
87 probability,
88 })
89 }
90}
91
92impl<R> Random for RngMixedBN<'_, R>
93where
94 R: Rng,
95{
96 type Output = Result<MixedBN>;
97
98 fn random(&mut self) -> Self::Output {
99 let graph = RngDag::new(self.rng, self.labels, self.probability)?.random()?;
100
101 let cpds = self
102 .labels
103 .iter()
104 .enumerate()
105 .map(|(i, x)| {
106 let pa_i = graph.parents(&set![i])?;
107 let mixed_support = &self.support[x];
108
109 match mixed_support {
110 MixedSupport::Categorical(cat_support) => {
111 let mut support = CatSupport::default();
112 support.insert(x.clone(), cat_support[x].clone());
113
114 let conditioning_support: CatSupport = pa_i
115 .iter()
116 .map(|&j| {
117 let y = &self.labels[j];
118 match &self.support[y] {
119 MixedSupport::Categorical(stats) => {
120 (y.clone(), stats[y].clone())
121 }
122 _ => unreachable!("parents must match CPD type"),
123 }
124 })
125 .collect();
126
127 let distribution =
128 RngCatCPD::new(self.rng, &support, &conditioning_support, self.alpha)?
129 .random()?;
130 Ok(MixedCPD::Categorical(distribution))
131 }
132 MixedSupport::Gaussian(_) => {
133 let v_labels = crate::labels![x.clone()];
134 let conditioning_labels: Labels =
135 pa_i.iter().map(|&j| self.labels[j].clone()).collect();
136
137 let distribution = RngGaussCPD::new(
138 self.rng,
139 &v_labels,
140 &conditioning_labels,
141 self.s_a,
142 self.s_b,
143 self.evidence,
144 )?
145 .random()?;
146 Ok(MixedCPD::Gaussian(distribution))
147 }
148 }
149 })
150 .collect::<Result<Vec<_>>>()?;
151
152 MixedBN::new(graph, cpds)
153 }
154}