causal_hub/models/mod.rs
1mod bayesian_network;
2use std::{
3 borrow::Cow,
4 ops::{DivAssign, MulAssign},
5};
6
7use approx::{AbsDiffEq, RelativeEq};
8pub use bayesian_network::*;
9
10mod continuous_time_bayesian_network;
11pub use continuous_time_bayesian_network::*;
12use itertools::Either;
13use rand::Rng;
14
15mod graphs;
16use std::fmt::Debug;
17
18pub use graphs::*;
19
20use crate::types::{Error, Labels, Result, Set};
21
22/// A trait for models with labelled variables.
23pub trait HasLabels {
24 /// Returns the labels of the variables.
25 ///
26 /// # Returns
27 ///
28 /// A reference to the labels.
29 ///
30 fn labels(&self) -> &Labels;
31
32 /// Return the variable index for a given label.
33 ///
34 /// # Arguments
35 ///
36 /// * `x` - The label of the variable.
37 ///
38 /// # Errors
39 ///
40 /// * If the label is not found.
41 ///
42 /// # Returns
43 ///
44 /// The index of the variable.
45 ///
46 #[inline]
47 fn label_to_index(&self, x: &str) -> Result<usize> {
48 self.labels()
49 .get_index_of(x)
50 .ok_or_else(|| Error::MissingLabel(x))
51 }
52
53 /// Return the label for a given variable index.
54 ///
55 /// # Arguments
56 ///
57 /// * `x` - The index of the variable.
58 ///
59 /// # Errors
60 ///
61 /// * If the index is out of bounds.
62 ///
63 /// # Returns
64 ///
65 /// The label of the variable.
66 ///
67 #[inline]
68 fn index_to_label(&self, x: usize) -> Result<&str> {
69 self.labels()
70 .get_index(x)
71 .map(|x| x.as_str())
72 .ok_or_else(|| Error::IndexOutOfBounds(x))
73 }
74
75 /// Maps an index from this model to another model with the same label.
76 ///
77 /// # Arguments
78 ///
79 /// * `x` - The index in this model.
80 /// * `other` - The labels of the other model.
81 ///
82 /// # Errors
83 ///
84 /// * If the index is out of bounds.
85 /// * If the label does not exist in the other model.
86 ///
87 /// # Returns
88 ///
89 /// The index in the other model.
90 ///
91 #[inline]
92 fn index_to(&self, x: usize, other: &Labels) -> Result<usize> {
93 // Get the label of the variable in this model.
94 let label = self.index_to_label(x)?;
95 // Get the index of the variable in the other model.
96 other
97 .get_index_of(label)
98 .ok_or_else(|| Error::MissingLabel(label))
99 }
100
101 /// Maps a set of indices from this model to another model with the same labels.
102 ///
103 /// # Arguments
104 ///
105 /// * `x` - The set of indices in this model.
106 /// * `other` - The labels of the other model.
107 ///
108 /// # Errors
109 ///
110 /// * If any index is out of bounds.
111 /// * If any label does not exist in the other model.
112 ///
113 /// # Returns
114 ///
115 /// The set of indices in the other model.
116 ///
117 #[inline]
118 fn indices_to(&self, x: &Set<usize>, other: &Labels) -> Result<Set<usize>> {
119 x.iter().map(|&x| self.index_to(x, other)).collect()
120 }
121
122 /// Maps an index from another model to this model with the same label.
123 ///
124 /// # Arguments
125 ///
126 /// * `x` - The index in the other model.
127 /// * `other` - The labels of the other model.
128 ///
129 /// # Errors
130 ///
131 /// * If the index is out of bounds.
132 /// * If the label does not exist in this model.
133 ///
134 /// # Returns
135 ///
136 /// The index in this model.
137 ///
138 #[inline]
139 fn index_from(&self, x: usize, other: &Labels) -> Result<usize> {
140 // Get the label of the variable in the other model.
141 let label = other
142 .get_index(x)
143 .ok_or_else(|| Error::IndexOutOfBounds(x))?;
144 // Get the index of the variable in this model.
145 self.labels()
146 .get_index_of(label)
147 .ok_or_else(|| Error::MissingLabel(label))
148 }
149
150 /// Maps a set of indices from another model to this model with the same labels.
151 ///
152 /// # Arguments
153 ///
154 /// * `x` - The set of indices in the other model.
155 /// * `other` - The labels of the other model.
156 ///
157 /// # Errors
158 ///
159 /// * If any index is out of bounds.
160 /// * If any label does not exist in this model.
161 ///
162 /// # Returns
163 ///
164 /// The set of indices in this model.
165 ///
166 #[inline]
167 fn indices_from(&self, x: &Set<usize>, other: &Labels) -> Result<Set<usize>> {
168 x.iter().map(|&x| self.index_from(x, other)).collect()
169 }
170}
171
172impl<L, R> HasLabels for Either<L, R>
173where
174 L: HasLabels,
175 R: HasLabels,
176{
177 fn labels(&self) -> &Labels {
178 match self {
179 Either::Left(l) => l.labels(),
180 Either::Right(r) => r.labels(),
181 }
182 }
183}
184
185/// A trait for conditional probability distributions.
186pub trait CPD: Clone + Debug + HasLabels + PartialEq + AbsDiffEq + RelativeEq {
187 /// The type of the samples.
188 type Sample;
189 /// The type of the support metadata (variable ranges / state sets).
190 type Support: Clone;
191 /// The type of the parameters.
192 type Parameters;
193 /// The type of the sufficient statistics.
194 type Statistics: Clone;
195
196 /// Returns the labels of the conditioned variables.
197 ///
198 /// # Returns
199 ///
200 /// A reference to the conditioning labels.
201 ///
202 fn conditioning_labels(&self) -> &Labels;
203
204 /// Returns the support metadata of the CPD.
205 ///
206 /// For categorical CPDs this returns the discrete support (possible states per variable).
207 /// For Gaussian CPDs this returns the range of each variable (defaults to (-inf, +inf)).
208 ///
209 /// # Returns
210 ///
211 /// A reference to the support metadata.
212 ///
213 fn support(&self) -> Cow<'_, Self::Support>;
214
215 /// Returns the conditioning support metadata of the CPD.
216 ///
217 /// For categorical CPDs this returns the discrete support of the conditioning variables.
218 /// For Gaussian CPDs this returns the range of each conditioning variable.
219 ///
220 /// # Returns
221 ///
222 /// A reference to the conditioning support metadata.
223 ///
224 fn conditioning_support(&self) -> Cow<'_, Self::Support>;
225
226 /// Returns the parameters.
227 ///
228 /// # Returns
229 ///
230 /// A reference to the parameters.
231 ///
232 fn parameters(&self) -> &Self::Parameters;
233
234 /// Returns the parameters size.
235 ///
236 /// # Returns
237 ///
238 /// The parameters size.
239 ///
240 fn parameters_size(&self) -> usize;
241
242 /// Returns the sufficient statistics, if any.
243 ///
244 /// # Returns
245 ///
246 /// An option containing the sufficient statistics, either borrowed or owned.
247 ///
248 fn fitted_statistics(&self) -> Option<Cow<'_, Self::Statistics>>;
249
250 /// Returns the log-likelihood of the fitted dataset, if any.
251 ///
252 /// # Returns
253 ///
254 /// An option containing the log-likelihood.
255 ///
256 fn fitted_log_likelihood(&self) -> Option<f64>;
257
258 /// Returns the value of probability (mass or density) function for P(X = x | Z = z).
259 ///
260 /// # Arguments
261 ///
262 /// * `x` - The value of the conditioned variables.
263 /// * `z` - The value of the conditioning variables.
264 ///
265 /// # Errors
266 ///
267 /// * If the value of the conditioned variables is out of bounds.
268 /// * If the value of the conditioning variables is out of bounds.
269 ///
270 /// # Returns
271 ///
272 /// The probability P(X = x | Z = z).
273 ///
274 fn pf(&self, x: &Self::Sample, z: &Self::Sample) -> Result<f64>;
275
276 /// Samples from the conditional distribution P(X | Z = z).
277 ///
278 /// # Arguments
279 ///
280 /// * `rng` - A mutable reference to a random number generator.
281 /// * `z` - The value of the conditioning variables.
282 ///
283 /// # Errors
284 ///
285 /// * If the value of the conditioning variables is out of bounds.
286 ///
287 /// # Returns
288 ///
289 /// A sample from P(X | Z = z).
290 ///
291 fn sample<R: Rng>(&self, rng: &mut R, z: &Self::Sample) -> Result<Self::Sample>;
292}
293
294/// A trait for conditional intensity matrices.
295pub trait CIM: Clone + Debug + HasLabels + PartialEq + AbsDiffEq + RelativeEq {
296 /// The type of the samples.
297 type Sample;
298 /// The type of the support metadata (variable ranges / state sets).
299 type Support: Clone;
300 /// The type of the parameters.
301 type Parameters;
302 /// The type of the sufficient statistics.
303 type Statistics: Clone;
304
305 /// Returns the labels of the conditioned variables.
306 ///
307 /// # Returns
308 ///
309 /// A reference to the conditioning labels.
310 ///
311 fn conditioning_labels(&self) -> &Labels;
312
313 /// Returns the support metadata of the CIM.
314 ///
315 /// # Returns
316 ///
317 /// A reference to the support metadata.
318 ///
319 fn support(&self) -> Cow<'_, Self::Support>;
320
321 /// Returns the conditioning support metadata of the CIM.
322 ///
323 /// # Returns
324 ///
325 /// A reference to the conditioning support metadata.
326 ///
327 fn conditioning_support(&self) -> Cow<'_, Self::Support>;
328
329 /// Returns the parameters.
330 ///
331 /// # Returns
332 ///
333 /// A reference to the parameters.
334 ///
335 fn parameters(&self) -> &Self::Parameters;
336
337 /// Returns the parameters size.
338 ///
339 /// # Returns
340 ///
341 /// The parameters size.
342 ///
343 fn parameters_size(&self) -> usize;
344
345 /// Returns the sufficient statistics, if any.
346 ///
347 /// # Returns
348 ///
349 /// An option containing the sufficient statistics, either borrowed or owned.
350 ///
351 fn fitted_statistics(&self) -> Option<Cow<'_, Self::Statistics>>;
352
353 /// Returns the log-likelihood of the fitted dataset, if any.
354 ///
355 /// # Returns
356 ///
357 /// An option containing the log-likelihood.
358 ///
359 fn fitted_log_likelihood(&self) -> Option<f64>;
360}
361
362/// A trait for potential functions.
363pub trait Phi:
364 Clone
365 + Debug
366 + HasLabels
367 + PartialEq
368 + AbsDiffEq
369 + RelativeEq
370 + for<'a> MulAssign<&'a Self>
371 + for<'a> DivAssign<&'a Self>
372{
373 /// The type of the CPD.
374 type CPD;
375 /// The type of the support metadata (variable ranges / state sets).
376 type Support: Clone;
377 /// The type of the parameters.
378 type Parameters;
379 /// The type of the evidence.
380 type Evidence;
381
382 /// Returns the support metadata of the potential.
383 ///
384 /// # Returns
385 ///
386 /// A reference to the support metadata.
387 ///
388 fn support(&self) -> Cow<'_, Self::Support>;
389
390 /// Returns the parameters.
391 ///
392 /// # Returns
393 ///
394 /// A reference to the parameters.
395 ///
396 fn parameters(&self) -> &Self::Parameters;
397
398 /// Returns the parameters size.
399 ///
400 /// # Returns
401 ///
402 /// The parameters size.
403 ///
404 fn parameters_size(&self) -> usize;
405
406 /// Conditions the potential on a set of variables.
407 ///
408 /// # Arguments
409 ///
410 /// * `e` - A map from variable indices to their observed support.
411 ///
412 /// # Returns
413 ///
414 /// A new potential instance.
415 ///
416 fn condition(&self, evidence: &Self::Evidence) -> Result<Self>;
417
418 /// Marginalizes the potential over a set of variables.
419 ///
420 /// # Arguments
421 ///
422 /// * `x` - A set of variable indices to marginalize over.
423 ///
424 /// # Returns
425 ///
426 /// A new potential instance.
427 ///
428 fn marginalize(&self, x: &Set<usize>) -> Result<Self>;
429
430 /// Normalizes the potential.
431 ///
432 /// # Returns
433 ///
434 /// The normalized potential.
435 ///
436 fn normalize(&self) -> Result<Self>;
437
438 /// Converts a CPD P(X | Z) to a potential \phi(X \cup Z).
439 ///
440 /// # Arguments
441 ///
442 /// * `cpd` - The CPD to convert.
443 ///
444 /// # Returns
445 ///
446 /// The corresponding potential.
447 ///
448 fn from_cpd(distribution: Self::CPD) -> Result<Self>;
449
450 /// Converts a potential \phi(X \cup Z) to a CPD P(X | Z).
451 ///
452 /// # Arguments
453 ///
454 /// * `x` - The set of variables.
455 /// * `z` - The set of conditioning variables.
456 ///
457 /// # Returns
458 ///
459 /// The corresponding CPD.
460 ///
461 fn into_cpd(self, x: &Set<usize>, z: &Set<usize>) -> Result<Self::CPD>;
462}