1use crate::error::{MetricsError, Result};
8use scirs2_core::ndarray::ArrayStatCompat;
9use scirs2_core::ndarray::{Array1, Array2, Axis};
10use statrs::statistics::Statistics;
11
12#[derive(Debug, Clone)]
14pub struct BayesianComparisonResults {
15 pub bayes_factor: f64,
17 pub log_bayes_factor: f64,
19 pub evidence_a: f64,
21 pub evidence_b: f64,
23 pub interpretation: String,
25}
26
27#[derive(Debug, Clone)]
29pub struct BayesianInformationResults {
30 pub bic: f64,
32 pub waic: f64,
34 pub loo_cv: f64,
36 pub dic: f64,
38 pub p_waic: f64,
40 pub model_rank: usize,
42}
43
44#[derive(Debug, Clone)]
46pub struct PosteriorPredictiveResults {
47 pub bayesian_p_value: f64,
49 pub observed_statistic: f64,
51 pub predicted_statistic_mean: f64,
53 pub predicted_statistic_std: f64,
55 pub tail_probability: f64,
57 pub model_adequate: bool,
59}
60
61#[derive(Debug, Clone)]
63pub struct CredibleIntervalResults {
64 pub lower_bound: f64,
66 pub upper_bound: f64,
68 pub credible_level: f64,
70 pub posterior_mean: f64,
72 pub posterior_median: f64,
74 pub contains_null: bool,
76 pub hpd_interval: (f64, f64),
78}
79
80#[derive(Debug, Clone)]
82pub struct BayesianModelAveragingResults {
83 pub averaged_prediction: Array1<f64>,
85 pub model_weights: Array1<f64>,
87 pub individual_predictions: Array2<f64>,
89 pub model_uncertainty: Array1<f64>,
91 pub total_variance: Array1<f64>,
93}
94
95pub struct BayesianModelComparison {
97 evidence_method: EvidenceMethod,
99 num_samples: usize,
101}
102
103#[derive(Debug, Clone, Copy)]
105pub enum EvidenceMethod {
106 HarmonicMean,
108 ThermodynamicIntegration,
110 BridgeSampling,
112 NestedSampling,
114}
115
116impl Default for BayesianModelComparison {
117 fn default() -> Self {
118 Self::new()
119 }
120}
121
122impl BayesianModelComparison {
123 pub fn new() -> Self {
125 Self {
126 evidence_method: EvidenceMethod::HarmonicMean,
127 num_samples: 1000,
128 }
129 }
130
131 pub fn with_evidence_method(mut self, method: EvidenceMethod) -> Self {
133 self.evidence_method = method;
134 self
135 }
136
137 pub fn with_num_samples(mut self, numsamples: usize) -> Self {
139 self.num_samples = numsamples;
140 self
141 }
142
143 pub fn compare_models(
145 &self,
146 log_likelihood_a: &Array1<f64>,
147 log_likelihood_b: &Array1<f64>,
148 log_prior_a: Option<&Array1<f64>>,
149 log_prior_b: Option<&Array1<f64>>,
150 ) -> Result<BayesianComparisonResults> {
151 if log_likelihood_a.len() != log_likelihood_b.len() {
152 return Err(MetricsError::InvalidInput(
153 "Likelihood arrays must have same length".to_string(),
154 ));
155 }
156
157 let evidence_a = self.estimate_evidence(log_likelihood_a, log_prior_a)?;
159 let evidence_b = self.estimate_evidence(log_likelihood_b, log_prior_b)?;
160
161 let log_bayes_factor = evidence_a - evidence_b;
163 let bayes_factor = log_bayes_factor.exp();
164
165 let interpretation = Self::interpret_bayes_factor(bayes_factor);
167
168 Ok(BayesianComparisonResults {
169 bayes_factor,
170 log_bayes_factor,
171 evidence_a,
172 evidence_b,
173 interpretation,
174 })
175 }
176
177 fn estimate_evidence(
179 &self,
180 log_likelihood: &Array1<f64>,
181 logprior: Option<&Array1<f64>>,
182 ) -> Result<f64> {
183 match self.evidence_method {
184 EvidenceMethod::HarmonicMean => self.harmonic_mean_evidence(log_likelihood, logprior),
185 EvidenceMethod::ThermodynamicIntegration => {
186 self.thermodynamic_integration_evidence(log_likelihood, logprior)
187 }
188 EvidenceMethod::BridgeSampling => {
189 self.bridge_sampling_evidence(log_likelihood, logprior)
190 }
191 EvidenceMethod::NestedSampling => {
192 self.nested_sampling_evidence(log_likelihood, logprior)
193 }
194 }
195 }
196
197 fn harmonic_mean_evidence(
199 &self,
200 log_likelihood: &Array1<f64>,
201 logprior: Option<&Array1<f64>>,
202 ) -> Result<f64> {
203 if log_likelihood.is_empty() {
204 return Err(MetricsError::InvalidInput(
205 "Empty _likelihood array".to_string(),
206 ));
207 }
208
209 let log_posterior: Array1<f64> = if let Some(prior) = logprior {
211 if prior.len() != log_likelihood.len() {
212 return Err(MetricsError::InvalidInput(
213 "Prior and _likelihood arrays must have same length".to_string(),
214 ));
215 }
216 log_likelihood + prior
217 } else {
218 log_likelihood.clone()
219 };
220
221 let max_log_posterior = log_posterior
224 .iter()
225 .fold(f64::NEG_INFINITY, |a, &b| a.max(b));
226
227 let sum_inv_exp: f64 = log_posterior
228 .iter()
229 .map(|&x| (-x + max_log_posterior).exp())
230 .sum();
231
232 let harmonic_mean_log =
233 -((sum_inv_exp / log_posterior.len() as f64).ln()) + max_log_posterior;
234
235 Ok(harmonic_mean_log)
236 }
237
238 fn thermodynamic_integration_evidence(
246 &self,
247 log_likelihood: &Array1<f64>,
248 logprior: Option<&Array1<f64>>,
249 ) -> Result<f64> {
250 if log_likelihood.is_empty() {
251 return Err(MetricsError::InvalidInput(
252 "Empty log _likelihood array".to_string(),
253 ));
254 }
255
256 let numtemps = 20;
258 let temperatures = self.generate_temperature_schedule(numtemps)?;
259
260 let ess = self.estimate_effective_sample_size(log_likelihood)?;
262 let thinning_factor = (log_likelihood.len() as f64 / ess.max(1.0)).ceil() as usize;
263
264 let thinned_indices: Vec<usize> = (0..log_likelihood.len())
266 .step_by(thinning_factor.max(1))
267 .collect();
268
269 let mut mean_log_likelihoods = Vec::new();
270
271 for &beta in &temperatures {
273 let mean_log_like = if beta == 0.0 {
274 self.compute_marginal_log_likelihood(log_likelihood, logprior)?
276 } else {
277 self.compute_tempered_expectation(log_likelihood, logprior, beta, &thinned_indices)?
279 };
280
281 mean_log_likelihoods.push(mean_log_like);
282 }
283
284 let integral = self.adaptive_integration(&temperatures, &mean_log_likelihoods)?;
286
287 Ok(integral)
288 }
289
290 fn generate_temperature_schedule(&self, numtemps: usize) -> Result<Vec<f64>> {
292 if numtemps < 2 {
293 return Err(MetricsError::InvalidInput(
294 "Need at least 2 temperature points".to_string(),
295 ));
296 }
297
298 let mut temperatures = Vec::with_capacity(numtemps);
299
300 for i in 0..numtemps {
303 let t = i as f64 / (numtemps - 1) as f64;
304
305 let beta = if t < 0.5 {
307 2.0 * t * t
309 } else {
310 2.0 * t - 1.0
312 };
313
314 temperatures.push(beta.clamp(0.0, 1.0));
315 }
316
317 temperatures[0] = 0.0;
319 temperatures[numtemps - 1] = 1.0;
320
321 Ok(temperatures)
322 }
323
324 fn estimate_effective_sample_size(&self, samples: &Array1<f64>) -> Result<f64> {
326 let n = samples.len();
327 if n < 4 {
328 return Ok(n as f64);
329 }
330
331 let mean = samples.mean_or(0.0);
333 let variance = samples.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / (n - 1) as f64;
334
335 if variance == 0.0 {
336 return Ok(n as f64);
337 }
338
339 let max_lag = n / 4;
341 let mut autocorr_sum = 1.0; let mut tau_int = 1.0;
343
344 for lag in 1..max_lag {
345 if n <= lag {
346 break;
347 }
348
349 let mut covariance = 0.0;
350 let count = n - lag;
351
352 for i in 0..count {
353 covariance += (samples[i] - mean) * (samples[i + lag] - mean);
354 }
355 covariance /= count as f64;
356
357 let autocorr = covariance / variance;
358
359 if autocorr < 0.01 {
361 break;
362 }
363
364 autocorr_sum += 2.0 * autocorr;
365 tau_int = autocorr_sum;
366
367 if lag as f64 >= 6.0 * tau_int {
369 break;
370 }
371 }
372
373 let ess = n as f64 / (2.0 * tau_int);
375 Ok(ess.max(1.0))
376 }
377
378 fn compute_marginal_log_likelihood(
380 &self,
381 log_likelihood: &Array1<f64>,
382 _log_prior: Option<&Array1<f64>>,
383 ) -> Result<f64> {
384 let n = log_likelihood.len() as f64;
390 let harmonic_mean = if log_likelihood
391 .iter()
392 .any(|&x| x.is_infinite() || x.is_nan())
393 {
394 log_likelihood
396 .iter()
397 .filter(|&&x| x.is_finite())
398 .map(|&x| (-x).exp())
399 .sum::<f64>()
400 } else {
401 log_likelihood.iter().map(|&x| (-x).exp()).sum::<f64>()
402 };
403
404 if harmonic_mean > 0.0 {
405 Ok(-((harmonic_mean / n).ln()))
406 } else {
407 Ok(-1000.0) }
409 }
410
411 fn compute_tempered_expectation(
413 &self,
414 log_likelihood: &Array1<f64>,
415 logprior: Option<&Array1<f64>>,
416 beta: f64,
417 indices: &[usize],
418 ) -> Result<f64> {
419 if indices.is_empty() {
420 return Ok(0.0);
421 }
422
423 let mut weighted_sum = 0.0;
427 let mut weight_sum = 0.0;
428
429 let max_log_like = indices
431 .iter()
432 .map(|&i| log_likelihood[i])
433 .fold(f64::NEG_INFINITY, f64::max);
434
435 for &i in indices {
436 let log_like = log_likelihood[i];
437 let log_prior_val = logprior.map(|lp| lp[i]).unwrap_or(0.0);
438
439 let _log_tempered_posterior = beta * log_like + log_prior_val;
441
442 let log_weight = (beta - 1.0) * (log_like - max_log_like);
444 let weight = log_weight.exp();
445
446 if weight.is_finite() && weight > 0.0 {
447 weighted_sum += weight * log_like;
448 weight_sum += weight;
449 }
450 }
451
452 if weight_sum > 0.0 {
453 Ok(weighted_sum / weight_sum)
454 } else {
455 let avg =
457 indices.iter().map(|&i| log_likelihood[i]).sum::<f64>() / indices.len() as f64;
458 Ok(avg)
459 }
460 }
461
462 fn adaptive_integration(&self, x: &[f64], y: &[f64]) -> Result<f64> {
464 if x.len() != y.len() || x.len() < 2 {
465 return Err(MetricsError::InvalidInput(
466 "Invalid integration data".to_string(),
467 ));
468 }
469
470 let n = x.len();
471 let mut integral = 0.0;
472
473 if n >= 3 && n % 2 == 1 {
475 let h = (x[n - 1] - x[0]) / (n - 1) as f64;
477 integral = y[0] + y[n - 1];
478
479 for i in 1..n - 1 {
480 let coeff = if i % 2 == 1 { 4.0 } else { 2.0 };
481 integral += coeff * y[i];
482 }
483 integral *= h / 3.0;
484 } else {
485 for i in 0..n - 1 {
487 let h = x[i + 1] - x[i];
488 integral += 0.5 * h * (y[i] + y[i + 1]);
489 }
490 }
491
492 Ok(integral)
493 }
494
495 fn bridge_sampling_evidence(
506 &self,
507 log_likelihood: &Array1<f64>,
508 logprior: Option<&Array1<f64>>,
509 ) -> Result<f64> {
510 if log_likelihood.is_empty() {
511 return Err(MetricsError::InvalidInput(
512 "Empty log _likelihood array".to_string(),
513 ));
514 }
515
516 let n_samples = log_likelihood.len();
517
518 let n_prior_samples = (n_samples / 2).max(100); let prior_samples =
521 self.generate_prior_samples(log_likelihood, logprior, n_prior_samples)?;
522
523 let log_evidence = self.iterative_bridge_sampling(
525 log_likelihood,
526 logprior,
527 &prior_samples,
528 20, 1e-6, )?;
531
532 Ok(log_evidence)
533 }
534
535 fn generate_prior_samples(
537 &self,
538 log_likelihood: &Array1<f64>,
539 logprior: Option<&Array1<f64>>,
540 n_samples: usize,
541 ) -> Result<Array1<f64>> {
542 let mut prior_log_likes = Vec::new();
546
547 if let Some(lp) = logprior {
548 let weights = self.compute_prior_weights(lp)?;
550
551 for _ in 0..n_samples {
553 let sampled_idx = self.weighted_sample(&weights)?;
554 if sampled_idx < log_likelihood.len() {
555 prior_log_likes.push(log_likelihood[sampled_idx]);
556 }
557 }
558 } else {
559 let min_log_like = log_likelihood.iter().fold(f64::INFINITY, |a, &b| a.min(b));
562 let _range = log_likelihood
563 .iter()
564 .fold(f64::NEG_INFINITY, |a, &b| a.max(b))
565 - min_log_like;
566
567 for i in 0..n_samples {
568 let bias_factor = 0.3; let u = (i as f64 + bias_factor) / n_samples as f64;
571 let target_quantile = u * 0.5; let idx = ((target_quantile * log_likelihood.len() as f64) as usize)
574 .min(log_likelihood.len() - 1);
575 prior_log_likes.push(log_likelihood[idx]);
576 }
577 }
578
579 if prior_log_likes.is_empty() {
580 return Err(MetricsError::InvalidInput(
581 "Failed to generate _prior _samples".to_string(),
582 ));
583 }
584
585 Ok(Array1::from_vec(prior_log_likes))
586 }
587
588 fn compute_prior_weights(&self, logprior: &Array1<f64>) -> Result<Array1<f64>> {
590 let max_log_prior = logprior.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
591
592 let weights = logprior
593 .iter()
594 .map(|&lp| (lp - max_log_prior).exp())
595 .collect::<Vec<f64>>();
596
597 Ok(Array1::from_vec(weights))
598 }
599
600 fn weighted_sample(&self, weights: &Array1<f64>) -> Result<usize> {
602 let total_weight: f64 = weights.sum();
603 if total_weight <= 0.0 {
604 return Ok(0); }
606
607 let n = weights.len();
609 let u = 0.5; let target = u * total_weight;
611
612 let mut cumsum = 0.0;
613 for (i, &w) in weights.iter().enumerate() {
614 cumsum += w;
615 if cumsum >= target {
616 return Ok(i);
617 }
618 }
619
620 Ok(n - 1)
621 }
622
623 fn iterative_bridge_sampling(
625 &self,
626 log_likelihood: &Array1<f64>,
627 logprior: Option<&Array1<f64>>,
628 prior_samples: &Array1<f64>,
629 max_iter: usize,
630 tolerance: f64,
631 ) -> Result<f64> {
632 let n1 = log_likelihood.len(); let n2 = prior_samples.len(); let mut log_r = self.initialize_bridge_estimate(log_likelihood, prior_samples)?;
637
638 for _iter in 0..max_iter {
639 let log_r_new =
640 self.bridge_iteration(log_likelihood, logprior, prior_samples, log_r, n1, n2)?;
641
642 if (log_r_new - log_r).abs() < tolerance {
644 return Ok(log_r_new);
645 }
646
647 log_r = log_r_new;
648 }
649
650 Ok(log_r)
651 }
652
653 fn initialize_bridge_estimate(
655 &self,
656 posterior_log_likes: &Array1<f64>,
657 prior_log_likes: &Array1<f64>,
658 ) -> Result<f64> {
659 let posterior_mean = posterior_log_likes.mean_or(0.0);
661 let prior_mean = prior_log_likes.mean_or(0.0);
662
663 Ok(posterior_mean - prior_mean)
664 }
665
666 fn bridge_iteration(
668 &self,
669 log_likelihood: &Array1<f64>,
670 logprior: Option<&Array1<f64>>,
671 prior_samples: &Array1<f64>,
672 log_r_current: f64,
673 n1: usize,
674 n2: usize,
675 ) -> Result<f64> {
676 let s1 = n1 as f64;
680 let s2 = n2 as f64;
681
682 let mut num_1 = 0.0;
684 let mut den_1 = 0.0;
685
686 for (i, &log_like) in log_likelihood.iter().enumerate() {
687 let log_prior_val = logprior.map(|lp| lp[i]).unwrap_or(0.0);
688 let log_p1 = log_like + log_prior_val; let log_p2 = log_prior_val; let log_denom =
693 self.log_sum_exp(&[(s1 * log_p1).ln() + log_r_current, (s2 * log_p2).ln()]);
694
695 let bridge_weight_1 = ((s2 * log_p2).ln() - log_denom).exp();
696 let bridge_weight_2 = ((s1 * log_p1).ln() + log_r_current - log_denom).exp();
697
698 if bridge_weight_1.is_finite() && bridge_weight_2.is_finite() {
699 num_1 += bridge_weight_1;
700 den_1 += bridge_weight_2;
701 }
702 }
703
704 let mut num_2 = 0.0;
706 let mut den_2 = 0.0;
707
708 for &prior_log_like in prior_samples.iter() {
709 let log_p1 = prior_log_like; let log_p2 = 0.0; let log_denom =
714 self.log_sum_exp(&[(s1 * log_p1).ln() + log_r_current, (s2 * log_p2).ln()]);
715
716 let bridge_weight_1 = ((s1 * log_p1).ln() + log_r_current - log_denom).exp();
717 let bridge_weight_2 = ((s2 * log_p2).ln() - log_denom).exp();
718
719 if bridge_weight_1.is_finite() && bridge_weight_2.is_finite() {
720 num_2 += bridge_weight_1;
721 den_2 += bridge_weight_2;
722 }
723 }
724
725 let total_num = num_1 + num_2;
727 let total_den = den_1 + den_2;
728
729 if total_den > 0.0 && total_num > 0.0 {
730 Ok((total_num / total_den).ln())
731 } else {
732 Ok(log_r_current) }
734 }
735
736 fn log_sum_exp(&self, logvalues: &[f64]) -> f64 {
738 if logvalues.is_empty() {
739 return f64::NEG_INFINITY;
740 }
741
742 let max_val = logvalues.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
743
744 if max_val.is_infinite() {
745 return max_val;
746 }
747
748 let sum_exp: f64 = logvalues.iter().map(|&x| (x - max_val).exp()).sum();
749
750 max_val + sum_exp.ln()
751 }
752
753 fn nested_sampling_evidence(
763 &self,
764 log_likelihood: &Array1<f64>,
765 logprior: Option<&Array1<f64>>,
766 ) -> Result<f64> {
767 if log_likelihood.is_empty() {
768 return Err(MetricsError::InvalidInput(
769 "Empty _likelihood array".to_string(),
770 ));
771 }
772
773 let n_samples = log_likelihood.len();
774 let nlive = (n_samples / 10).clamp(10, 100); let (log_evidence, log_evidence_error) =
778 self.nested_sampling_integration(log_likelihood, logprior, nlive)?;
779
780 let corrected_log_evidence = self.apply_nested_sampling_corrections(
782 log_evidence,
783 log_evidence_error,
784 nlive,
785 n_samples,
786 )?;
787
788 Ok(corrected_log_evidence)
789 }
790
791 fn nested_sampling_integration(
793 &self,
794 log_likelihood: &Array1<f64>,
795 _log_prior: Option<&Array1<f64>>,
796 nlive: usize,
797 ) -> Result<(f64, f64)> {
798 let mut indexed_samples: Vec<(usize, f64)> = log_likelihood
800 .iter()
801 .enumerate()
802 .map(|(i, &ll)| (i, ll))
803 .collect();
804 indexed_samples.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
805
806 let live_start = indexed_samples.len().saturating_sub(nlive);
808 let mut live_points: Vec<(usize, f64)> = indexed_samples[live_start..].to_vec();
809
810 let mut log_weights = Vec::new();
812 let mut log_likes = Vec::new();
813 let mut log_prior_volumes = Vec::new();
814
815 let mut log_x = 0.0; let n_iterations = indexed_samples.len().saturating_sub(nlive);
820 for iter in 0..n_iterations {
821 let (min_idx, min_log_like) = live_points
823 .iter()
824 .enumerate()
825 .min_by(|(_, (_, a)), (_, (_, b))| {
826 a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
827 })
828 .map(|(i, (_idx, ll))| (i, *ll))
829 .unwrap_or((0, f64::NEG_INFINITY));
830
831 let shrinkage_factor = self.estimate_shrinkage_factor(nlive, iter)?;
833 let new_log_x = log_x + shrinkage_factor.ln();
834
835 let log_width = self.log_sum_exp(&[log_x, new_log_x]) - (2.0_f64).ln(); log_weights.push(log_width);
839 log_likes.push(min_log_like);
840 log_prior_volumes.push(log_x);
841
842 log_x = new_log_x;
844
845 if iter < n_iterations - 1 {
847 let replacement_idx = indexed_samples[iter].0;
848 let replacement_log_like = indexed_samples[iter].1;
849 live_points[min_idx] = (replacement_idx, replacement_log_like);
850 }
851 }
852
853 let final_log_x = log_x - (nlive as f64).ln();
855 for (_, log_like) in &live_points {
856 log_weights.push(final_log_x);
857 log_likes.push(*log_like);
858 log_prior_volumes.push(final_log_x);
859 }
860
861 let (log_evidence, log_evidence_error) =
863 self.compute_evidence_and_error(&log_weights, &log_likes, &log_prior_volumes)?;
864
865 Ok((log_evidence, log_evidence_error))
866 }
867
868 fn estimate_shrinkage_factor(&self, nlive: usize, iteration: usize) -> Result<f64> {
870 let base_shrinkage = 1.0 / nlive as f64;
874
875 let variation = 0.1 * (iteration as f64 * 0.1).sin(); let shrinkage = base_shrinkage * (1.0 + variation);
879
880 Ok(shrinkage.max(1e-10)) }
882
883 fn compute_evidence_and_error(
885 &self,
886 log_weights: &[f64],
887 log_likes: &[f64],
888 log_prior_volumes: &[f64],
889 ) -> Result<(f64, f64)> {
890 if log_weights.len() != log_likes.len() || log_weights.is_empty() {
891 return Err(MetricsError::InvalidInput(
892 "Mismatched or empty arrays".to_string(),
893 ));
894 }
895
896 let log_terms: Vec<f64> = log_weights
898 .iter()
899 .zip(log_likes.iter())
900 .map(|(&log_w, &log_l)| log_w + log_l)
901 .collect();
902
903 let log_evidence = self.log_sum_exp(&log_terms);
904
905 let log_evidence_error =
907 self.estimate_evidence_error(&log_terms, log_evidence, log_prior_volumes)?;
908
909 Ok((log_evidence, log_evidence_error))
910 }
911
912 fn estimate_evidence_error(
914 &self,
915 log_terms: &[f64],
916 log_evidence: f64,
917 log_prior_volumes: &[f64],
918 ) -> Result<f64> {
919 if log_terms.is_empty() {
920 return Ok(0.0);
921 }
922
923 let mut relative_contributions = Vec::new();
925 for &log_term in log_terms {
926 let contribution = (log_term - log_evidence).exp();
927 relative_contributions.push(contribution);
928 }
929
930 let h_info: f64 = relative_contributions
932 .iter()
933 .filter(|&&x| x > 0.0)
934 .map(|&x| -x * x.ln())
935 .sum();
936
937 let log_volume_range = if log_prior_volumes.len() > 1 {
939 log_prior_volumes
940 .iter()
941 .fold(f64::NEG_INFINITY, |a, &b| a.max(b))
942 - log_prior_volumes
943 .iter()
944 .fold(f64::INFINITY, |a, &b| a.min(b))
945 } else {
946 1.0
947 };
948
949 let log_error = 0.5 * (h_info.ln() + log_volume_range);
950 Ok(log_error)
951 }
952
953 fn apply_nested_sampling_corrections(
955 &self,
956 log_evidence: f64,
957 log_evidence_error: f64,
958 nlive: usize,
959 n_total: usize,
960 ) -> Result<f64> {
961 let live_point_correction = -(nlive as f64).ln() / 2.0;
963
964 let sample_size_correction = if n_total > 100 {
966 -(n_total as f64).ln() / (2.0 * n_total as f64)
967 } else {
968 -0.01 };
970
971 let conservative_correction = -log_evidence_error.abs();
973
974 let corrected_evidence =
975 log_evidence + live_point_correction + sample_size_correction + conservative_correction;
976
977 Ok(corrected_evidence)
978 }
979
980 fn interpret_bayes_factor(bf: f64) -> String {
982 if bf < 1.0 {
983 let inv_bf = 1.0 / bf;
984 if inv_bf < 3.0 {
985 "Barely worth mentioning (favors B)".to_string()
986 } else if inv_bf < 10.0 {
987 "Substantial evidence for B".to_string()
988 } else if inv_bf < 30.0 {
989 "Strong evidence for B".to_string()
990 } else if inv_bf < 100.0 {
991 "Very strong evidence for B".to_string()
992 } else {
993 "Extreme evidence for B".to_string()
994 }
995 } else if bf < 3.0 {
996 "Barely worth mentioning (favors A)".to_string()
997 } else if bf < 10.0 {
998 "Substantial evidence for A".to_string()
999 } else if bf < 30.0 {
1000 "Strong evidence for A".to_string()
1001 } else if bf < 100.0 {
1002 "Very strong evidence for A".to_string()
1003 } else {
1004 "Extreme evidence for A".to_string()
1005 }
1006 }
1007
1008 #[allow(dead_code)]
1010 fn calculate_variance(&self, data: &Array1<f64>) -> Result<f64> {
1011 if data.is_empty() {
1012 return Ok(0.0);
1013 }
1014
1015 let mean = data.mean_or(0.0);
1016 let variance = data.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / data.len() as f64;
1017 Ok(variance)
1018 }
1019}
1020
1021pub struct BayesianInformationCriteria {
1023 num_samples: usize,
1025}
1026
1027impl Default for BayesianInformationCriteria {
1028 fn default() -> Self {
1029 Self::new()
1030 }
1031}
1032
1033impl BayesianInformationCriteria {
1034 pub fn new() -> Self {
1036 Self { num_samples: 1000 }
1037 }
1038
1039 pub fn with_num_samples(mut self, numsamples: usize) -> Self {
1041 self.num_samples = numsamples;
1042 self
1043 }
1044
1045 pub fn evaluate_model(
1047 &self,
1048 log_likelihoodsamples: &Array2<f64>, num_parameters: usize,
1050 num_observations: usize,
1051 ) -> Result<BayesianInformationResults> {
1052 if log_likelihoodsamples.is_empty() {
1053 return Err(MetricsError::InvalidInput(
1054 "Empty likelihood _samples".to_string(),
1055 ));
1056 }
1057
1058 let (waic, p_waic) = self.calculate_waic(log_likelihoodsamples)?;
1060
1061 let loo_cv = self.calculate_loo_cv(log_likelihoodsamples)?;
1063
1064 let mean_log_likelihood: f64 = log_likelihoodsamples.mean_or(0.0);
1066 let bic = -2.0 * mean_log_likelihood * num_observations as f64
1067 + (num_parameters as f64) * (num_observations as f64).ln();
1068
1069 let dic = self.calculate_dic(log_likelihoodsamples)?;
1071
1072 Ok(BayesianInformationResults {
1073 bic,
1074 waic,
1075 loo_cv,
1076 dic,
1077 p_waic,
1078 model_rank: 0, })
1080 }
1081
1082 fn calculate_waic(&self, log_likelihoodsamples: &Array2<f64>) -> Result<(f64, f64)> {
1084 let (n_samples, n_obs) = log_likelihoodsamples.dim();
1085 if n_samples == 0 || n_obs == 0 {
1086 return Err(MetricsError::InvalidInput(
1087 "Empty likelihood _samples".to_string(),
1088 ));
1089 }
1090
1091 let mut lppd = 0.0; let mut p_waic = 0.0; for i in 0..n_obs {
1095 let obs_likelihoods = log_likelihoodsamples.column(i);
1096
1097 let max_ll = obs_likelihoods
1099 .iter()
1100 .fold(f64::NEG_INFINITY, |a, &b| a.max(b));
1101 let sum_exp: f64 = obs_likelihoods.iter().map(|&x| (x - max_ll).exp()).sum();
1102 let log_mean_exp = (sum_exp / n_samples as f64).ln() + max_ll;
1103
1104 lppd += log_mean_exp;
1105
1106 let mean_ll = obs_likelihoods.mean();
1108 let var_ll: f64 = obs_likelihoods
1109 .iter()
1110 .map(|&x| (x - mean_ll).powi(2))
1111 .sum::<f64>()
1112 / n_samples as f64;
1113
1114 p_waic += var_ll;
1115 }
1116
1117 let waic = -2.0 * (lppd - p_waic);
1118 Ok((waic, p_waic))
1119 }
1120
1121 fn calculate_loo_cv(&self, log_likelihoodsamples: &Array2<f64>) -> Result<f64> {
1123 let (n_samples, n_obs) = log_likelihoodsamples.dim();
1124 if n_samples == 0 || n_obs == 0 {
1125 return Err(MetricsError::InvalidInput(
1126 "Empty likelihood _samples".to_string(),
1127 ));
1128 }
1129
1130 let mut loo_sum = 0.0;
1131
1132 for i in 0..n_obs {
1133 let obs_likelihoods = log_likelihoodsamples.column(i);
1134
1135 let weights = self.calculate_psis_weights(&obs_likelihoods.to_owned())?;
1137
1138 let weighted_sum: f64 = obs_likelihoods
1140 .iter()
1141 .zip(weights.iter())
1142 .map(|(&ll, &w)| w * ll.exp())
1143 .sum();
1144
1145 let weight_sum: f64 = weights.sum();
1146
1147 if weight_sum > 1e-10 {
1148 loo_sum += (weighted_sum / weight_sum).ln();
1149 }
1150 }
1151
1152 Ok(-2.0 * loo_sum)
1153 }
1154
1155 fn calculate_dic(&self, log_likelihoodsamples: &Array2<f64>) -> Result<f64> {
1157 let mean_deviance = -2.0 * log_likelihoodsamples.mean_or(0.0);
1158
1159 let posterior_mean_ll = log_likelihoodsamples
1161 .mean_axis(Axis(0))
1162 .expect("Operation failed");
1163 let deviance_at_mean = -2.0 * posterior_mean_ll.sum();
1164
1165 let p_dic = mean_deviance - deviance_at_mean;
1166 let dic = mean_deviance + p_dic;
1167
1168 Ok(dic)
1169 }
1170
1171 fn calculate_psis_weights(&self, logweights: &Array1<f64>) -> Result<Array1<f64>> {
1173 let n = logweights.len();
1174 if n == 0 {
1175 return Ok(Array1::zeros(0));
1176 }
1177
1178 let max_weight = logweights.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
1180 let weights: Array1<f64> = logweights.mapv(|x| (x - max_weight).exp());
1181
1182 let sum_weights = weights.sum();
1184 if sum_weights > 1e-10 {
1185 Ok(weights / sum_weights)
1186 } else {
1187 Ok(Array1::from_elem(n, 1.0 / n as f64))
1188 }
1189 }
1190}
1191
1192pub struct PosteriorPredictiveCheck {
1194 test_statistic: TestStatisticType,
1196 num_samples: usize,
1198}
1199
1200#[derive(Debug, Clone)]
1202pub enum TestStatisticType {
1203 Mean,
1205 Variance,
1207 Minimum,
1209 Maximum,
1211 Custom(String),
1213}
1214
1215impl Default for PosteriorPredictiveCheck {
1216 fn default() -> Self {
1217 Self::new()
1218 }
1219}
1220
1221impl PosteriorPredictiveCheck {
1222 pub fn new() -> Self {
1224 Self {
1225 test_statistic: TestStatisticType::Mean,
1226 num_samples: 1000,
1227 }
1228 }
1229
1230 pub fn with_test_statistic(mut self, teststatistic: TestStatisticType) -> Self {
1232 self.test_statistic = teststatistic;
1233 self
1234 }
1235
1236 pub fn with_num_samples(mut self, numsamples: usize) -> Self {
1238 self.num_samples = numsamples;
1239 self
1240 }
1241
1242 pub fn check_model_adequacy(
1244 &self,
1245 observed_data: &Array1<f64>,
1246 posterior_predictive_samples: &Array2<f64>, ) -> Result<PosteriorPredictiveResults> {
1248 if posterior_predictive_samples.is_empty() {
1249 return Err(MetricsError::InvalidInput(
1250 "Empty posterior predictive _samples".to_string(),
1251 ));
1252 }
1253
1254 let (n_samples, n_obs) = posterior_predictive_samples.dim();
1255 if observed_data.len() != n_obs {
1256 return Err(MetricsError::InvalidInput(
1257 "Observed _data length doesn't match predictive _samples".to_string(),
1258 ));
1259 }
1260
1261 let observed_statistic = self.calculate_test_statistic(observed_data)?;
1263
1264 let mut predicted_statistics = Vec::with_capacity(n_samples);
1266 for i in 0..n_samples {
1267 let sample = posterior_predictive_samples.row(i).to_owned();
1268 let statistic = self.calculate_test_statistic(&sample)?;
1269 predicted_statistics.push(statistic);
1270 }
1271
1272 let predicted_statistics = Array1::from_vec(predicted_statistics);
1273 let predicted_statistic_std = self.calculate_std(&predicted_statistics)?;
1274 let predicted_statistic_mean = predicted_statistics.clone().mean();
1275
1276 let count_extreme = predicted_statistics
1278 .iter()
1279 .filter(|&&x| x >= observed_statistic)
1280 .count();
1281 let bayesian_p_value = count_extreme as f64 / n_samples as f64;
1282
1283 let tail_probability = 2.0 * bayesian_p_value.min(1.0 - bayesian_p_value);
1285
1286 let model_adequate = bayesian_p_value > 0.05 && bayesian_p_value < 0.95;
1288
1289 Ok(PosteriorPredictiveResults {
1290 bayesian_p_value,
1291 observed_statistic,
1292 predicted_statistic_mean,
1293 predicted_statistic_std,
1294 tail_probability,
1295 model_adequate,
1296 })
1297 }
1298
1299 fn calculate_test_statistic(&self, data: &Array1<f64>) -> Result<f64> {
1301 if data.is_empty() {
1302 return Ok(0.0);
1303 }
1304
1305 match &self.test_statistic {
1306 TestStatisticType::Mean => Ok(data.mean_or(0.0)),
1307 TestStatisticType::Variance => {
1308 let mean = data.mean_or(0.0);
1309 let variance =
1310 data.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / data.len() as f64;
1311 Ok(variance)
1312 }
1313 TestStatisticType::Minimum => Ok(data.iter().fold(f64::INFINITY, |a, &b| a.min(b))),
1314 TestStatisticType::Maximum => Ok(data.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b))),
1315 TestStatisticType::Custom(_name) => {
1316 Ok(data.mean_or(0.0))
1319 }
1320 }
1321 }
1322
1323 fn calculate_std(&self, data: &Array1<f64>) -> Result<f64> {
1325 if data.is_empty() {
1326 return Ok(0.0);
1327 }
1328
1329 let mean = data.mean_or(0.0);
1330 let variance = data.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / data.len() as f64;
1331 Ok(variance.sqrt())
1332 }
1333}
1334
1335pub struct CredibleIntervalCalculator {
1337 credible_level: f64,
1339 null_value: Option<f64>,
1341}
1342
1343impl Default for CredibleIntervalCalculator {
1344 fn default() -> Self {
1345 Self::new()
1346 }
1347}
1348
1349impl CredibleIntervalCalculator {
1350 pub fn new() -> Self {
1352 Self {
1353 credible_level: 0.95,
1354 null_value: None,
1355 }
1356 }
1357
1358 pub fn with_credible_level(mut self, level: f64) -> Result<Self> {
1360 if level <= 0.0 || level >= 1.0 {
1361 return Err(MetricsError::InvalidInput(
1362 "Credible level must be between 0 and 1".to_string(),
1363 ));
1364 }
1365 self.credible_level = level;
1366 Ok(self)
1367 }
1368
1369 pub fn with_null_value(mut self, nullvalue: f64) -> Self {
1371 self.null_value = Some(nullvalue);
1372 self
1373 }
1374
1375 pub fn calculate_intervals(
1377 &self,
1378 posterior_samples: &Array1<f64>,
1379 ) -> Result<CredibleIntervalResults> {
1380 if posterior_samples.is_empty() {
1381 return Err(MetricsError::InvalidInput(
1382 "Empty posterior _samples".to_string(),
1383 ));
1384 }
1385
1386 let mut sortedsamples = posterior_samples.to_vec();
1388 sortedsamples.sort_by(|a, b| a.partial_cmp(b).expect("Operation failed"));
1389
1390 let n = sortedsamples.len();
1391 let alpha = 1.0 - self.credible_level;
1392
1393 let lower_idx = ((alpha / 2.0) * n as f64).floor() as usize;
1395 let upper_idx = ((1.0 - alpha / 2.0) * n as f64).ceil() as usize - 1;
1396
1397 let lower_bound = sortedsamples[lower_idx.min(n - 1)];
1398 let upper_bound = sortedsamples[upper_idx.min(n - 1)];
1399
1400 let posterior_mean = posterior_samples.mean_or(0.0);
1402 let posterior_median = if n.is_multiple_of(2) {
1403 (sortedsamples[n / 2 - 1] + sortedsamples[n / 2]) / 2.0
1404 } else {
1405 sortedsamples[n / 2]
1406 };
1407
1408 let contains_null = if let Some(null_val) = self.null_value {
1410 null_val >= lower_bound && null_val <= upper_bound
1411 } else {
1412 false
1413 };
1414
1415 let hpd_interval = self.calculate_hpd_interval(&sortedsamples)?;
1417
1418 Ok(CredibleIntervalResults {
1419 lower_bound,
1420 upper_bound,
1421 credible_level: self.credible_level,
1422 posterior_mean,
1423 posterior_median,
1424 contains_null,
1425 hpd_interval,
1426 })
1427 }
1428
1429 fn calculate_hpd_interval(&self, sortedsamples: &[f64]) -> Result<(f64, f64)> {
1431 let n = sortedsamples.len();
1432 let interval_length = (self.credible_level * n as f64).round() as usize;
1433
1434 if interval_length >= n {
1435 return Ok((sortedsamples[0], sortedsamples[n - 1]));
1436 }
1437
1438 let mut min_width = f64::INFINITY;
1440 let mut best_lower = sortedsamples[0];
1441 let mut best_upper = sortedsamples[n - 1];
1442
1443 for i in 0..=(n - interval_length) {
1444 let lower = sortedsamples[i];
1445 let upper = sortedsamples[i + interval_length - 1];
1446 let width = upper - lower;
1447
1448 if width < min_width {
1449 min_width = width;
1450 best_lower = lower;
1451 best_upper = upper;
1452 }
1453 }
1454
1455 Ok((best_lower, best_upper))
1456 }
1457}
1458
1459pub struct BayesianModelAveraging {
1461 weighting_method: ModelWeightingMethod,
1463}
1464
1465#[derive(Debug, Clone, Copy)]
1467pub enum ModelWeightingMethod {
1468 MarginalLikelihood,
1470 InformationCriteria,
1472 CrossValidation,
1474 Equal,
1476}
1477
1478impl Default for BayesianModelAveraging {
1479 fn default() -> Self {
1480 Self::new()
1481 }
1482}
1483
1484impl BayesianModelAveraging {
1485 pub fn new() -> Self {
1487 Self {
1488 weighting_method: ModelWeightingMethod::InformationCriteria,
1489 }
1490 }
1491
1492 pub fn with_weighting_method(mut self, method: ModelWeightingMethod) -> Self {
1494 self.weighting_method = method;
1495 self
1496 }
1497
1498 pub fn average_models(
1500 &self,
1501 predictions: &Array2<f64>, modelscores: &Array1<f64>, ) -> Result<BayesianModelAveragingResults> {
1504 let (n_models, n_obs) = predictions.dim();
1505 if modelscores.len() != n_models {
1506 return Err(MetricsError::InvalidInput(
1507 "Number of model _scores must match number of models".to_string(),
1508 ));
1509 }
1510
1511 if n_models == 0 || n_obs == 0 {
1512 return Err(MetricsError::InvalidInput(
1513 "Empty predictions array".to_string(),
1514 ));
1515 }
1516
1517 let model_weights = self.calculate_model_weights(modelscores)?;
1519
1520 let mut averaged_prediction = Array1::zeros(n_obs);
1522 for i in 0..n_obs {
1523 let mut weighted_sum = 0.0;
1524 for j in 0..n_models {
1525 weighted_sum += model_weights[j] * predictions[[j, i]];
1526 }
1527 averaged_prediction[i] = weighted_sum;
1528 }
1529
1530 let mut model_uncertainty = Array1::zeros(n_obs);
1532 for i in 0..n_obs {
1533 let mut weighted_variance = 0.0;
1534 for j in 0..n_models {
1535 let diff = predictions[[j, i]] - averaged_prediction[i];
1536 weighted_variance += model_weights[j] * diff * diff;
1537 }
1538 model_uncertainty[i] = weighted_variance;
1539 }
1540
1541 let mut within_model_variance = Array1::<f64>::zeros(n_obs);
1543 for i in 0..n_models {
1544 let prediction_row = predictions.row(i);
1545 let residual_sq = (&prediction_row - &averaged_prediction).mapv(|x| x * x);
1546 within_model_variance = within_model_variance + residual_sq * model_weights[i];
1547 }
1548 let total_variance = &model_uncertainty + &within_model_variance;
1549
1550 Ok(BayesianModelAveragingResults {
1551 averaged_prediction,
1552 model_weights,
1553 individual_predictions: predictions.clone(),
1554 model_uncertainty,
1555 total_variance,
1556 })
1557 }
1558
1559 fn calculate_model_weights(&self, modelscores: &Array1<f64>) -> Result<Array1<f64>> {
1561 match self.weighting_method {
1562 ModelWeightingMethod::MarginalLikelihood => {
1563 self.marginal_likelihood_weights(modelscores)
1564 }
1565 ModelWeightingMethod::InformationCriteria => {
1566 self.information_criteria_weights(modelscores)
1567 }
1568 ModelWeightingMethod::CrossValidation => self.cross_validation_weights(modelscores),
1569 ModelWeightingMethod::Equal => {
1570 let n = modelscores.len();
1571 Ok(Array1::from_elem(n, 1.0 / n as f64))
1572 }
1573 }
1574 }
1575
1576 fn marginal_likelihood_weights(
1578 &self,
1579 log_marginal_likelihoods: &Array1<f64>,
1580 ) -> Result<Array1<f64>> {
1581 let max_log_ml = log_marginal_likelihoods
1583 .iter()
1584 .fold(f64::NEG_INFINITY, |a, &b| a.max(b));
1585
1586 let exp_weights: Array1<f64> = log_marginal_likelihoods.mapv(|x| (x - max_log_ml).exp());
1587 let sum_weights = exp_weights.sum();
1588
1589 if sum_weights > 1e-10 {
1590 Ok(exp_weights / sum_weights)
1591 } else {
1592 let n = log_marginal_likelihoods.len();
1593 Ok(Array1::from_elem(n, 1.0 / n as f64))
1594 }
1595 }
1596
1597 fn information_criteria_weights(
1599 &self,
1600 information_criteria: &Array1<f64>,
1601 ) -> Result<Array1<f64>> {
1602 let min_ic = information_criteria
1604 .iter()
1605 .fold(f64::INFINITY, |a, &b| a.min(b));
1606
1607 let delta_ic: Array1<f64> = information_criteria.mapv(|x| x - min_ic);
1608 let exp_weights: Array1<f64> = delta_ic.mapv(|x| (-0.5 * x).exp());
1609 let sum_weights = exp_weights.sum();
1610
1611 if sum_weights > 1e-10 {
1612 Ok(exp_weights / sum_weights)
1613 } else {
1614 let n = information_criteria.len();
1615 Ok(Array1::from_elem(n, 1.0 / n as f64))
1616 }
1617 }
1618
1619 fn cross_validation_weights(&self, cvscores: &Array1<f64>) -> Result<Array1<f64>> {
1621 let min_score = cvscores.iter().fold(f64::INFINITY, |a, &b| a.min(b));
1623 let shifted_scores: Array1<f64> = cvscores.mapv(|x| x - min_score + 1e-6);
1624 let sum_scores = shifted_scores.sum();
1625
1626 if sum_scores > 1e-10 {
1627 Ok(shifted_scores / sum_scores)
1628 } else {
1629 let n = cvscores.len();
1630 Ok(Array1::from_elem(n, 1.0 / n as f64))
1631 }
1632 }
1633}
1634
1635#[cfg(test)]
1636mod tests {
1637 use super::*;
1638 use scirs2_core::ndarray::Array;
1639
1640 #[test]
1641 fn test_bayesian_model_comparison() {
1642 let comparison = BayesianModelComparison::new();
1643
1644 let log_likelihood_a = Array1::from_vec(vec![-1.0, -1.5, -2.0, -1.2, -1.8]);
1645 let log_likelihood_b = Array1::from_vec(vec![-2.0, -2.5, -3.0, -2.2, -2.8]);
1646
1647 let result = comparison
1648 .compare_models(&log_likelihood_a, &log_likelihood_b, None, None)
1649 .expect("Operation failed");
1650
1651 assert!(result.bayes_factor > 0.0);
1652 assert!(result.evidence_a > result.evidence_b);
1653 assert!(!result.interpretation.is_empty());
1654 }
1655
1656 #[test]
1657 fn test_bayesian_information_criteria() {
1658 let bic_calc = BayesianInformationCriteria::new();
1659
1660 let log_likelihoodsamples =
1662 Array2::from_shape_fn((5, 10), |(i, j)| -1.0 - 0.1 * i as f64 - 0.05 * j as f64);
1663
1664 let result = bic_calc
1665 .evaluate_model(&log_likelihoodsamples, 3, 10)
1666 .expect("Operation failed");
1667
1668 assert!(result.waic > 0.0);
1669 assert!(result.loo_cv > 0.0);
1670 assert!(result.bic > 0.0);
1671 assert!(result.dic.is_finite());
1673 assert!(result.p_waic >= 0.0);
1674 }
1675
1676 #[test]
1677 fn test_posterior_predictive_check() {
1678 let ppc = PosteriorPredictiveCheck::new().with_test_statistic(TestStatisticType::Mean);
1679
1680 let observed_data = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
1681 let posterior_samples =
1682 Array2::from_shape_fn((100, 5), |(i, j)| 1.0 + j as f64 + 0.1 * (i as f64 - 50.0));
1683
1684 let result = ppc
1685 .check_model_adequacy(&observed_data, &posterior_samples)
1686 .expect("Operation failed");
1687
1688 assert!(result.bayesian_p_value >= 0.0 && result.bayesian_p_value <= 1.0);
1689 assert!(result.tail_probability >= 0.0 && result.tail_probability <= 1.0);
1690 assert!(
1691 !result.model_adequate
1692 || (result.bayesian_p_value > 0.05 && result.bayesian_p_value < 0.95)
1693 );
1694 }
1695
1696 #[test]
1697 fn test_credible_interval_calculator() {
1698 let ci_calc = CredibleIntervalCalculator::new()
1699 .with_credible_level(0.95)
1700 .expect("Operation failed")
1701 .with_null_value(0.0);
1702
1703 let posterior_samples =
1704 Array1::from_vec(vec![-0.5, -0.2, 0.1, 0.3, 0.5, 0.7, 0.9, 1.1, 1.3, 1.5]);
1705
1706 let result = ci_calc
1707 .calculate_intervals(&posterior_samples)
1708 .expect("Operation failed");
1709
1710 assert!(result.lower_bound < result.upper_bound);
1711 assert!(result.credible_level == 0.95);
1712 assert!(result.posterior_mean > 0.0);
1713 assert!(result.hpd_interval.0 <= result.hpd_interval.1);
1714 }
1715
1716 #[test]
1717 fn test_bayesian_model_averaging() {
1718 let bma = BayesianModelAveraging::new()
1719 .with_weighting_method(ModelWeightingMethod::InformationCriteria);
1720
1721 let predictions = Array2::from_shape_vec(
1723 (3, 5),
1724 vec![
1725 1.0, 2.0, 3.0, 4.0, 5.0, 1.1, 2.1, 2.9, 4.1, 4.9, 0.9, 1.9, 3.1, 3.9, 5.1, ],
1729 )
1730 .expect("Operation failed");
1731
1732 let modelscores = Array1::from_vec(vec![100.0, 102.0, 105.0]); let result = bma
1735 .average_models(&predictions, &modelscores)
1736 .expect("Operation failed");
1737
1738 assert_eq!(result.averaged_prediction.len(), 5);
1739 assert_eq!(result.model_weights.len(), 3);
1740 assert!((result.model_weights.sum() - 1.0).abs() < 1e-6);
1741 assert_eq!(result.model_uncertainty.len(), 5);
1742 }
1743
1744 #[test]
1745 fn test_bayes_factor_interpretation() {
1746 assert!(BayesianModelComparison::interpret_bayes_factor(0.5).contains("favors B"));
1748 assert!(BayesianModelComparison::interpret_bayes_factor(2.0)
1749 .contains("Barely worth mentioning"));
1750 assert!(
1751 BayesianModelComparison::interpret_bayes_factor(15.0).contains("Strong evidence for A")
1752 );
1753 assert!(BayesianModelComparison::interpret_bayes_factor(150.0)
1754 .contains("Extreme evidence for A"));
1755 }
1756
1757 #[test]
1758 fn test_evidence_methods() {
1759 let _comparison = BayesianModelComparison::new();
1760 let log_likelihood = Array1::from_vec(vec![-1.0, -1.5, -2.0, -1.2, -1.8]);
1761
1762 let methods = vec![
1764 EvidenceMethod::HarmonicMean,
1765 EvidenceMethod::ThermodynamicIntegration,
1766 EvidenceMethod::BridgeSampling,
1767 EvidenceMethod::NestedSampling,
1768 ];
1769
1770 for method in methods {
1771 let comparison_with_method =
1772 BayesianModelComparison::new().with_evidence_method(method);
1773 let evidence = comparison_with_method
1774 .estimate_evidence(&log_likelihood, None)
1775 .expect("Operation failed");
1776 assert!(evidence.is_finite());
1777 }
1778 }
1779}