causal_hub/random/models/bayesian_network/gaussian/
parameters.rs1use ndarray::prelude::*;
2use ndarray_linalg::{Cholesky, SVD, UPLO};
3use rand::{Rng, RngExt};
4use rand_distr::Normal;
5
6use crate::{
7 models::{GaussCPD, GaussCPDP},
8 random::Random,
9 types::{Error, Labels, Result},
10};
11
12struct RngGaussCPDP<'a, R>
14where
15 R: Rng,
16{
17 rng: &'a mut R,
18 x: usize,
19 z: usize,
20 s_a: f64,
21 s_b: f64,
22 evidence: f64,
23}
24
25impl<'a, R> RngGaussCPDP<'a, R>
26where
27 R: Rng,
28{
29 fn new(rng: &'a mut R, x: usize, z: usize, s_a: f64, s_b: f64, evidence: f64) -> Result<Self> {
51 if s_a <= 0.0 {
53 return Err(Error::InvalidParameter("s_a", "must be positive"));
54 }
55 if s_b <= 0.0 {
56 return Err(Error::InvalidParameter("s_b", "must be positive"));
57 }
58 if evidence <= 0.0 {
59 return Err(Error::InvalidParameter("e", "must be positive"));
60 }
61
62 Ok(Self {
63 rng,
64 x,
65 z,
66 s_a,
67 s_b,
68 evidence,
69 })
70 }
71}
72
73impl<R> Random for RngGaussCPDP<'_, R>
74where
75 R: Rng,
76{
77 type Output = Result<GaussCPDP>;
78
79 fn random(&mut self) -> Self::Output {
80 let mut a = if self.x > 0 && self.z > 0 {
82 let dist_a = Normal::new(0.0, self.s_a)
83 .map_err(|evidence| Error::InvalidParameter("s_a", &evidence.to_string()))?;
84 Array2::from_shape_fn((self.x, self.z), |_| self.rng.sample(dist_a))
85 } else {
86 Array2::zeros((self.x, self.z))
87 };
88
89 if self.x > 0 && self.z > 0 {
91 let (_, stats, _) = a
92 .svd(false, false)
93 .map_err(|evidence| Error::Linalg(&format!("Failed to compute SVD: {evidence}")))?;
94 let spectral_norm = stats[0];
95 if spectral_norm > 1.0 {
96 a /= spectral_norm;
97 }
98 }
99
100 let b = if self.x > 0 {
102 let dist_b = Normal::new(0.0, self.s_b)
103 .map_err(|evidence| Error::InvalidParameter("s_b", &evidence.to_string()))?;
104 Array1::from_shape_fn(self.x, |_| self.rng.sample(dist_b))
105 } else {
106 Array1::zeros(self.x)
107 };
108
109 let stats = if self.x > 0 {
111 let mut stats;
112 let dist_m = Normal::new(0.0, 1.0)
114 .map_err(|evidence| Error::InvalidParameter("sigma", &evidence.to_string()))?;
115 loop {
116 let model = Array2::from_shape_fn((self.x, self.x), |_| self.rng.sample(dist_m));
118
119 stats = model.dot(&model.t());
121
122 for i in 0..self.x {
124 stats[[i, i]] += self.evidence;
125 }
126
127 if stats.cholesky(UPLO::Lower).is_ok() {
129 break;
130 }
131 }
132 stats
133 } else {
134 Array2::zeros((self.x, self.x))
135 };
136
137 GaussCPDP::new(a, b, stats)
139 }
140}
141
142pub struct RngGaussCPD<'a, R>
144where
145 R: Rng,
146{
147 rng: &'a mut R,
148 labels: &'a Labels,
149 conditioning_labels: &'a Labels,
150 s_a: f64,
151 s_b: f64,
152 evidence: f64,
153}
154
155impl<'a, R> RngGaussCPD<'a, R>
156where
157 R: Rng,
158{
159 pub fn new(
175 rng: &'a mut R,
176 labels: &'a Labels,
177 conditioning_labels: &'a Labels,
178 s_a: f64,
179 s_b: f64,
180 evidence: f64,
181 ) -> Result<Self> {
182 Ok(Self {
183 rng,
184 labels,
185 conditioning_labels,
186 s_a,
187 s_b,
188 evidence,
189 })
190 }
191}
192
193impl<R> Random for RngGaussCPD<'_, R>
194where
195 R: Rng,
196{
197 type Output = Result<GaussCPD>;
198
199 fn random(&mut self) -> Self::Output {
200 let mut rng_params = RngGaussCPDP::new(
202 self.rng,
203 self.labels.len(),
204 self.conditioning_labels.len(),
205 self.s_a,
206 self.s_b,
207 self.evidence,
208 )?;
209 let parameters = rng_params.random()?;
210
211 GaussCPD::new(
213 self.labels.clone(),
214 self.conditioning_labels.clone(),
215 parameters,
216 )
217 }
218}