Skip to main content

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}