causal_hub/estimators/structures/
scoring_criterion.rs1use std::marker::PhantomData;
2
3use dry::macro_for;
4
5use crate::{
6 estimators::CPDEstimator,
7 models::{CIM, CPD, CatCIM, CatCPD, GaussCPD, HasLabels},
8 types::{Error, Labels, Result, Set},
9};
10
11pub trait HasEstimator {
14 type Estimator;
16
17 fn estimator(&self) -> &Self::Estimator;
24}
25
26pub trait ScoringCriterion {
28 fn call(&self, x: &Set<usize>, z: &Set<usize>) -> Result<f64>;
40}
41
42macro_rules! impl_scoring_struct {
43 ($name:ident, $doc:expr) => {
44 #[doc = $doc]
45 pub struct $name<'a, E, P> {
46 _p_marker: PhantomData<P>,
47 estimator: &'a E,
48 }
49
50 impl<'a, E, P> $name<'a, E, P> {
51 #[doc = concat!("Creates a new `", stringify!($name), "` instance.")]
52 #[doc = concat!("A new `", stringify!($name), "` instance.")]
60 #[inline]
61 pub const fn new(estimator: &'a E) -> Self {
62 Self {
63 _p_marker: PhantomData,
64 estimator,
65 }
66 }
67 }
68
69 impl<E, P> HasEstimator for $name<'_, E, P> {
70 type Estimator = E;
71
72 #[inline]
73 fn estimator(&self) -> &Self::Estimator {
74 self.estimator
75 }
76 }
77
78 impl<'a, E, P> HasLabels for $name<'a, E, P>
79 where
80 E: HasLabels,
81 {
82 #[inline]
83 fn labels(&self) -> &Labels {
84 self.estimator.labels()
85 }
86 }
87 };
88}
89
90impl_scoring_struct!(LL, "The Log Likelihood (`LL`).");
91impl_scoring_struct!(AIC, "The Akaike Information Criterion (`AIC`).");
92impl_scoring_struct!(AICC, "The Akaike Information Criterion Corrected (`AICc`).");
93impl_scoring_struct!(BIC, "The Bayesian Information Criterion (`BIC`).");
94impl_scoring_struct!(
95 BICC,
96 "The Bayesian Information Criterion Corrected (`BICc`)."
97);
98impl_scoring_struct!(HQC, "The Hannan-Quinn Criterion (`HQC`).");
99
100macro_for!($type in [CatCPD, GaussCPD, CatCIM] {
101
102 impl<E> ScoringCriterion for LL<'_, E, $type>
103 where
104 E: CPDEstimator<$type>,
105 {
106 #[inline]
107 fn call(&self, x: &Set<usize>, z: &Set<usize>) -> Result<f64> {
108 let p_xz = self.estimator.fit(x, z)?;
110 let log_likelihood = p_xz
112 .fitted_log_likelihood()
113 .ok_or_else(|| Error::MissingLogLikelihood())?;
114
115 Ok(log_likelihood)
117 }
118 }
119
120 impl<E> ScoringCriterion for AIC<'_, E, $type>
121 where
122 E: CPDEstimator<$type>,
123 {
124 #[inline]
125 fn call(&self, x: &Set<usize>, z: &Set<usize>) -> Result<f64> {
126 let p_xz = self.estimator.fit(x, z)?;
128 let log_likelihood = p_xz
130 .fitted_log_likelihood()
131 .ok_or_else(|| Error::MissingLogLikelihood())?;
132 let k = p_xz.parameters_size() as f64;
134
135 Ok(log_likelihood - k)
137 }
138 }
139
140 impl<E> ScoringCriterion for AICC<'_, E, $type>
141 where
142 E: CPDEstimator<$type>,
143 {
144 #[inline]
145 fn call(&self, x: &Set<usize>, z: &Set<usize>) -> Result<f64> {
146 let p_xz = self.estimator.fit(x, z)?;
148 let n = p_xz
150 .fitted_statistics()
151 .ok_or_else(|| Error::MissingSufficientStatistics())?
152 .fitted_size();
153 let log_likelihood = p_xz
155 .fitted_log_likelihood()
156 .ok_or_else(|| Error::MissingLogLikelihood())?;
157 let k = p_xz.parameters_size() as f64;
159
160 let c = n / (f64::max(n - k - 2., 1.));
162
163 Ok(log_likelihood - k * c)
165 }
166 }
167
168 impl<E> ScoringCriterion for BIC<'_, E, $type>
169 where
170 E: CPDEstimator<$type>,
171 {
172 #[inline]
173 fn call(&self, x: &Set<usize>, z: &Set<usize>) -> Result<f64> {
174 let p_xz = self.estimator.fit(x, z)?;
176 let n = p_xz
178 .fitted_statistics()
179 .ok_or_else(|| Error::MissingSufficientStatistics())?
180 .fitted_size();
181 let log_likelihood = p_xz
183 .fitted_log_likelihood()
184 .ok_or_else(|| Error::MissingLogLikelihood())?;
185 let k = p_xz.parameters_size() as f64;
187
188 Ok(log_likelihood - 0.5 * k * f64::ln(n))
190 }
191 }
192
193 impl<E> ScoringCriterion for BICC<'_, E, $type>
194 where
195 E: CPDEstimator<$type>,
196 {
197 #[inline]
198 fn call(&self, x: &Set<usize>, z: &Set<usize>) -> Result<f64> {
199 let p_xz = self.estimator.fit(x, z)?;
201 let n = p_xz
203 .fitted_statistics()
204 .ok_or_else(|| Error::MissingSufficientStatistics())?
205 .fitted_size();
206 let log_likelihood = p_xz
208 .fitted_log_likelihood()
209 .ok_or_else(|| Error::MissingLogLikelihood())?;
210 let k = p_xz.parameters_size() as f64;
212
213 let c = n / (f64::max(n - k - 2., 1.));
215
216 Ok(log_likelihood - 0.5 * k * c * f64::ln(n))
218 }
219 }
220
221 impl<E> ScoringCriterion for HQC<'_, E, $type>
222 where
223 E: CPDEstimator<$type>,
224 {
225 #[inline]
226 fn call(&self, x: &Set<usize>, z: &Set<usize>) -> Result<f64> {
227 let p_xz = self.estimator.fit(x, z)?;
229 let n = p_xz
231 .fitted_statistics()
232 .ok_or_else(|| Error::MissingSufficientStatistics())?
233 .fitted_size();
234 let log_likelihood = p_xz
236 .fitted_log_likelihood()
237 .ok_or_else(|| Error::MissingLogLikelihood())?;
238 let k = p_xz.parameters_size() as f64;
240
241 Ok(log_likelihood - 0.5 * k * f64::ln(f64::ln(n)))
243 }
244 }
245
246});