1use std::f64::consts::PI;
2use std::option::Option;
3use std::cmp::min;
4
5use argmin::{
6 core::{OptimizationResult, CostFunction, Executor, State, Error as ArgminError},
7 solver::brent::BrentRoot,
8};
9
10use rustdct::DctPlanner;
11
12use ndarray::Array1;
13
14use num_traits::{Float, ToPrimitive, FromPrimitive};
15
16struct ZetaGammaLMinusT {
18 k2: Array1<f64>,
20 a2: Array1<f64>,
22 n_datapoints: f64,
24 l: i32
26}
27
28struct Histogram<F: Float> {
29 counts: Vec<u32>,
30 bins: Vec<F>,
31 data_range: F
32}
33
34#[derive(Debug)]
35pub struct Kde1DResult<F: Float + FromPrimitive + ToPrimitive> {
36 pub density: Vec<F>,
37 pub grid: Vec<F>,
38 pub bandwidth: F
39}
40
41fn product_of_first_n_odd_numbers(n: i32) -> f64 {
44 (1..=n)
46 .map(|k| (2*k - 1) as f64)
47 .product()
48}
49
50impl ZetaGammaLMinusT {
51 pub fn new(k2: &Array1<f64>, transformed: &Array1<f64>, n_datapoints: usize, l: i32) -> Self {
52 let a2: Array1<f64> = transformed.powi(2);
61
62 Self {
63 k2: k2.clone(),
64 a2: a2,
65 n_datapoints: (n_datapoints as f64),
66 l: l
67 }
68 }
69}
70
71impl CostFunction for ZetaGammaLMinusT {
72 type Param = f64;
73 type Output = f64;
74
75 fn cost(&self, t: &Self::Param) -> Result<Self::Output, ArgminError> {
76 let mut f: f64 = 2.0 * PI.powi(2 * self.l) *
81 (&self.k2.powi(self.l) *
82 &self.a2 *
83 (- PI.powi(2) * &self.k2 * *t).exp())
84 .sum();
85
86 for s in (2..=(self.l - 1)).rev() {
87 let k = product_of_first_n_odd_numbers(s) / (2.0 * PI).sqrt();
90 let c = (1.0 + 0.5_f64.powf((s as f64) + 0.5)) / 3.0;
91
92 let t_s: f64 = (2.0 * k * c / (self.n_datapoints * f)).powf(2.0 / (3.0 + 2.0 * (s as f64)));
93
94 f = 2.0 * PI.powi(2 * s) *
95 (&self.k2.powi(s) *
96 &self.a2 *
97 (- PI.powi(2) * &self.k2 * t_s).exp())
98 .sum()
99 }
100
101 Ok(t - (2.0 * self.n_datapoints * PI.sqrt() * f).powf(-0.4))
103 }
104}
105
106fn max_min_of_array<F: Float>(x: &Vec<F>) -> Option<(F, F)> {
107 x
108 .iter()
109 .fold(None, |maybe_min_max, x_i| {
110 match maybe_min_max {
111 None => {
112 Some((*x_i, *x_i))
113 },
114
115 Some((x_min, x_max)) => {
116 let new_x_min = if *x_i < x_min { *x_i } else { x_min };
117 let new_x_max = if *x_i > x_max { *x_i } else { x_max };
118
119 Some((new_x_min, new_x_max))
120 }
121 }
122 })
123}
124
125#[inline(never)]
126fn histogram<F>(x: &Vec<F>, grid_size: usize, lim_low: Option<F>, lim_high: Option<F>)
127 -> Histogram<F> where F: Float + ToPrimitive + FromPrimitive {
128 let (x_min0, x_max0) = max_min_of_array(&x).unwrap();
152 let delta0: F = x_max0 - x_min0;
153
154 let margin: F = FromPrimitive::from_f64(0.1).unwrap();
157 let x_min: F = match lim_low {
158 Some(value) => value,
159 None => x_min0 - delta0 * margin
160 };
161
162 let x_max: F = match lim_high {
163 Some(value) => value,
164 None => x_max0 + delta0 * margin
165 };
166
167 let delta: F = x_max - x_min;
168 let f_grid_size: F = FromPrimitive::from_u64(grid_size as u64).unwrap();
169
170 let bins: Vec<F> = (0..grid_size)
171 .map(|i| {
172 let f_i: F = FromPrimitive::from_usize(i).unwrap();
173 (f_i * delta) / f_grid_size + x_min
174 })
175 .collect();
176
177 let mut counts: Vec<u32> = vec![0; grid_size];
179
180 if delta.is_zero() {
182 counts[0] = x.len() as u32;
184 } else {
185 let grid_coefficient: F = f_grid_size / delta;
189
190 for x_i in x.iter() {
192 let f_index: F = (grid_coefficient * (*x_i - x_min)).floor();
195 let index = min(f_index.to_usize().unwrap(), grid_size - 1);
196 counts[index] += 1;
198 }
199 };
200
201 Histogram{
202 counts: counts,
203 bins: bins,
204 data_range: delta
205 }
206}
207
208pub fn kde_1d<F>(x: &Vec<F>, suggested_grid_size: usize, limits: (Option<F>, Option<F>)) ->
213 Result<Kde1DResult<F>, ArgminError> where F: Float + FromPrimitive + ToPrimitive{
214 let grid_size = (2_i32.pow((suggested_grid_size as f64).log2().ceil() as u32)) as usize;
216 let n_datapoints = x.len();
218
219 let (lim_low, lim_high) = limits;
220
221 let k2: Array1<f64> =
223 (0..grid_size)
224 .map(|k| (k as f64).powi(2))
225 .collect::<Vec<f64>>()
226 .into();
227
228 let hist: Histogram<F> = histogram(&x, grid_size, lim_low, lim_high);
230
231 let counts: Vec<u32> = hist.counts;
232 let bins: Vec<F> = hist.bins;
235 let delta_x: f64 = hist.data_range.to_f64().unwrap();
238
239 let mut transformed: Vec<f64> = counts
241 .iter()
242 .map(|count| (*count as f64) / (n_datapoints as f64))
243 .collect();
244
245 let mut planner: DctPlanner<f64> = DctPlanner::new();
247 let dct = planner.plan_dct2(grid_size);
248 dct.process_dct2(&mut transformed);
249
250 transformed[0] /= 2.0;
254 let transformed: Array1<f64> = transformed.into();
256
257 let fixed_point = ZetaGammaLMinusT::new(&k2, &transformed, n_datapoints, 7_i32);
260
261 let init_param: f64 = 0.05;
263 let solver = BrentRoot::new(0.0, 0.1, 2e-12);
265 let result: OptimizationResult<_, _, _> =
266 Executor::new(fixed_point, solver)
267 .configure(|state| state.param(init_param).max_iters(100))
268 .run()?;
271
272 let t_star: f64 = result.state.get_best_param().copied().unwrap_or(init_param);
273
274 let bandwidth = t_star.sqrt() * delta_x;
275
276 let mut smoothed: Array1<f64> = transformed * (- 0.5 * PI.powi(2) * t_star * &k2).exp();
277 smoothed[0] *= 2.0;
279
280 let mut inverse = smoothed.to_vec();
282 let idct = planner.plan_dct3(grid_size);
285 idct.process_dct3(&mut inverse);
286
287 let density: Array1<f64> = 2.0 * Array1::from(inverse) / delta_x;
292
293
294 let density: Vec<F> = density
299 .to_vec()
300 .iter()
301 .map(|x| FromPrimitive::from_f64(*x).unwrap())
302 .collect::<Vec<F>>();
303
304 let kde_1d_result = Kde1DResult{
305 density: density,
306 grid: bins,
307 bandwidth: FromPrimitive::from_f64(bandwidth).unwrap()
308 };
309
310 Ok(kde_1d_result)
311}
312
313#[cfg(test)]
314mod tests {
315 use std::fs::File;
316 use std::f64::consts::PI;
317 use std::io::prelude::*;
318
319 use ndarray::{Array1, Array0};
322 use ndarray_npy::NpzReader;
323 use approx::assert_relative_eq;
325 use plotly::{Plot, Scatter, Layout};
327 use plotly::common::{Anchor, Mode, Font};
328 use plotly::layout::Annotation;
329 use rand::{Rng, SeedableRng};
331 use rand_chacha::ChaCha8Rng;
332 use rand_distr::{Normal, Distribution};
334 use statrs::distribution::{ContinuousCDF, Empirical};
338 use tera::{Tera, Context};
340
341 use super::*;
342
343 const RNG_SEED: u64 = 31415;
345 const TOLERANCE: f64 = 8.0*f64::EPSILON;
347
348 const KOLMOGOROV_SMIRNOV_ALPHA: f64 = 0.05;
350
351 const KOLMOGOROV_SMIRNOV_MAX_ITEMS: usize = 5000;
355
356 struct TwoSampleKolmogorovSmirnovTest {
358 statistic: f64,
359 cutoff: f64
360 }
361
362 impl TwoSampleKolmogorovSmirnovTest {
363 fn run(samples_a: &Vec<f64>, samples_b: &Vec<f64>) -> Self {
364 let n = usize::min(samples_a.len(), KOLMOGOROV_SMIRNOV_MAX_ITEMS);
369 let m = usize::min(samples_b.len(), KOLMOGOROV_SMIRNOV_MAX_ITEMS);
370
371 let samples_a = samples_a.into_iter().map(|x| *x).take(n);
372 let samples_b = samples_b.into_iter().map(|x| *x).take(m);
373
374 let f_a = Empirical::from_iter(samples_a.clone());
375 let f_b = Empirical::from_iter(samples_b.clone());
376
377 let n = n as f64;
378 let m = m as f64;
379
380 let statistic_a = samples_a
381 .clone()
382 .map(|x| (f_a.cdf(x) - f_b.cdf(x)).abs())
383 .reduce(f64::max)
384 .unwrap_or(0.0);
385
386 let statistic_b = samples_b
387 .clone()
388 .map(|x| (f_a.cdf(x) - f_b.cdf(x)).abs())
389 .reduce(f64::max)
390 .unwrap_or(0.0);
391
392 let c_alpha = (- (KOLMOGOROV_SMIRNOV_ALPHA / 2.0).ln() / 2.0).sqrt();
393
394 let cutoff = c_alpha * ((n + m) / (n * m)).sqrt();
395
396 Self {
397 statistic: f64::max(statistic_a, statistic_b),
398 cutoff: cutoff
399 }
400 }
401 }
402
403 #[derive(Clone)]
404 struct PyReferenceData1D {
405 x: Vec<f64>,
406 n_datapoints: usize,
407 grid_size: usize,
408 x_max: f64,
409 x_min: f64,
410 density: Vec<f64>,
411 grid: Vec<f64>,
412 bandwidth: f64
413 }
414
415 impl PyReferenceData1D {
416 fn from_npz(path: &str) -> Self {
417 let file = File::open(path).unwrap();
418 let mut npz = NpzReader::new(file).unwrap();
419
420 let x: Array1<f64> = npz.by_name("x").unwrap();
421 let density: Array1<f64> = npz.by_name("density").unwrap();
422 let grid: Array1<f64> = npz.by_name("grid").unwrap();
423
424 let n_array: Array0<i64> = npz.by_name("N").unwrap();
425 let grid_size_array: Array0<i64> = npz.by_name("n").unwrap();
427 let x_min_array: Array0<f64> = npz.by_name("xmin").unwrap();
428 let x_max_array: Array0<f64> = npz.by_name("xmax").unwrap();
429 let bandwidth_array: Array0<f64> = npz.by_name("bandwidth").unwrap();
430
431 let n_datapoints: usize = *n_array.get(()).unwrap() as usize;
433 let grid_size: usize = *grid_size_array.get(()).unwrap() as usize;
434 let x_min: f64 = *x_min_array.get(()).unwrap() as f64;
435 let x_max: f64 = *x_max_array.get(()).unwrap() as f64;
436 let bandwidth: f64 = *bandwidth_array.get(()).unwrap();
437
438 Self {
439 x: x.to_vec(),
440 n_datapoints: n_datapoints,
441 grid_size: grid_size,
442 x_max: x_max,
443 x_min: x_min,
444 density: density.to_vec(),
445 grid: grid.to_vec(),
446 bandwidth: bandwidth
447 }
448 }
449 }
450
451 #[derive(Clone)]
452 struct MixtureOfNormals {
455 parameters: Vec<(f64, (f64, f64))>,
456 weights: Vec<f64>,
457 distributions: Vec<Normal<f64>>
458 }
459
460 impl MixtureOfNormals {
461 fn new(parameters: Vec<(f64, (f64, f64))>) -> Self {
462 let mut distributions = Vec::with_capacity(parameters.len());
463 let mut weights = Vec::with_capacity(parameters.len());
464
465 for (weight, (mean, std_dev)) in parameters.iter() {
466 weights.push(*weight);
467 distributions.push(Normal::new(*mean, *std_dev).unwrap());
468 }
469
470 Self {
471 parameters: parameters,
472 weights: weights,
473 distributions: distributions
474 }
475 }
476
477 fn pdf(&self, x: f64) -> f64 {
478 self.parameters
483 .iter()
484 .map(|(w, (mean, std_dev))|
485 w * (- (x - mean).powi(2) / (2.0 * std_dev.powi(2))).exp() /
486 (std_dev * (2.0 * PI).sqrt())
487 )
488 .sum()
489 }
490 }
491
492 impl Distribution<f64> for MixtureOfNormals {
493 fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> f64 {
494 let r: f64 = rng.random();
495
496 let mut weight: f64 = self.weights[0];
497 let mut i: usize = 0;
498 while r > weight {
499 weight += self.weights[i + 1];
500 i += 1;
501 }
502
503 let normal = self.distributions[i];
504 normal.sample(rng)
505 }
506 }
507
508 #[test]
509 fn test_against_reference() {
510 let file = File::open("test/reference_densities/reference1d.npz").unwrap();
523 let mut npz = NpzReader::new(file).unwrap();
524
525 let x: Array1<f64> = npz.by_name("x").unwrap();
530 let reference_density: Array1<f64> = npz.by_name("density").unwrap();
531 let reference_grid: Array1<f64> = npz.by_name("grid").unwrap();
532
533 let n_array: Array0<u16> = npz.by_name("N").unwrap();
540 let grid_size_array: Array0<u16> = npz.by_name("n").unwrap();
542 let x_min_array: Array0<i16> = npz.by_name("xmin").unwrap();
543 let x_max_array: Array0<u8> = npz.by_name("xmax").unwrap();
544 let reference_bandwidth_array: Array0<f64> = npz.by_name("bandwidth").unwrap();
545
546 let n: usize = *n_array.get(()).unwrap() as usize;
548 let grid_size: usize = *grid_size_array.get(()).unwrap() as usize;
549 let x_min: f64 = *x_min_array.get(()).unwrap() as f64;
550 let x_max: f64 = *x_max_array.get(()).unwrap() as f64;
551 let reference_bandwidth: f64 = *reference_bandwidth_array.get(()).unwrap();
552
553 let limits = (Some(x_min), Some(x_max));
554 let kde_result: Kde1DResult<f64> = kde_1d(&x.to_vec(), grid_size, limits).unwrap();
555
556 assert_eq!(n, x.len());
557 assert_relative_eq!(reference_bandwidth, kde_result.bandwidth);
558
559 for i in 0..grid_size {
560 assert_relative_eq!(reference_density[i], kde_result.density[i]);
561 assert_relative_eq!(reference_grid[i], kde_result.grid[i])
562 }
563
564 let calculated_t_star = (kde_result.bandwidth / (x_max - x_min)).powi(2);
567 let reference_t_star = (reference_bandwidth / (x_max - x_min)).powi(2);
568
569 let mut plot = Plot::new();
573
574 let calculated_density_trace =
575 Scatter::new(kde_result.grid, kde_result.density)
576 .mode(Mode::LinesMarkers)
577 .name("Calculated density (.rs)");
578
579 let reference_density_trace =
580 Scatter::new(reference_grid.to_vec(), reference_density.to_vec())
581 .mode(Mode::LinesMarkers)
582 .name("Reference density (.py)");
583
584 plot.add_trace(calculated_density_trace);
585 plot.add_trace(reference_density_trace);
586
587 let layout = Layout::new().annotations(vec![
588 Annotation::new()
589 .text(format!("
590 Reference BW = {:.6}<br>
591 Calculated BW = {:.6}<br><br>
592 Reference t⋆ = {:.6}<br>
593 Calculated t⋆ = {:.6}",
594 reference_t_star,
595 calculated_t_star,
596 &reference_bandwidth,
597 &kde_result.bandwidth))
598 .x(0.95)
599 .y(0.95)
600 .x_ref("paper")
601 .y_ref("paper")
602 .x_anchor(Anchor::Right)
603 .y_anchor(Anchor::Top)
604 .show_arrow(false)
605 .font(Font::new().size(8))
606 ]);
607
608 plot.set_layout(layout);
609
610 plot.write_html("test/plots/kde1py_reference.html");
611 }
612
613 fn plot_density_against_reference(
614 plot_id: &str,
615 title: &str,
616 dist: &MixtureOfNormals,
617 kde_result: Kde1DResult<f64>,
618 py_ref: PyReferenceData1D
619 ) -> String {
620 let mut plot = Plot::new();
621
622 let actual_density: Vec<f64> = kde_result.grid
623 .clone()
624 .iter()
625 .map(|x| dist.pdf(*x))
626 .collect();
627
628 let estimated_density_trace =
629 Scatter::new(kde_result.grid.clone(), kde_result.density)
630 .mode(Mode::Lines)
631 .name(format!("{} - estimated density (.rs)", title));
632
633 let reference_density_trace =
634 Scatter::new(py_ref.grid, py_ref.density)
635 .mode(Mode::Lines)
636 .name(format!("{} - reference density (.py)", title));
637
638 let actual_density_trace =
639 Scatter::new(kde_result.grid, actual_density)
640 .mode(Mode::Lines)
641 .name(format!("{} - actual density", title));
642
643 plot.add_trace(actual_density_trace);
644 plot.add_trace(reference_density_trace);
645 plot.add_trace(estimated_density_trace);
646
647 plot.to_inline_html(Some(plot_id))
648 }
649
650 macro_rules! assert_equal_to_reference_within_tolerance {
652 ($kde_result:expr, $py_ref:expr) => {
653 {
654 assert_eq!($py_ref.n_datapoints, $py_ref.x.len());
662 assert_eq!($py_ref.grid_size, $py_ref.grid.len());
664
665 assert_relative_eq!($py_ref.bandwidth, $kde_result.bandwidth, epsilon=TOLERANCE);
667
668 for i in 0..$py_ref.grid_size {
669 assert_relative_eq!($py_ref.density[i], $kde_result.density[i], epsilon=TOLERANCE);
671 assert_relative_eq!($py_ref.grid[i], $kde_result.grid[i], epsilon=TOLERANCE)
673 }
674 }
675 }
676 }
677
678 macro_rules! assert_kolmogorov_smirnov_does_not_reject_the_null {
679 ($dist:expr, $py_ref:expr) => {
680 let mut rng = ChaCha8Rng::seed_from_u64(RNG_SEED);
682 let rust_x: Vec<f64> = $dist.clone().sample_iter(&mut rng).take($py_ref.n_datapoints).collect();
691 let ks_test = TwoSampleKolmogorovSmirnovTest::run(&rust_x, &$py_ref.x);
693 assert!(ks_test.statistic < ks_test.cutoff);
695 }
696 }
697
698 fn kde_1d_from_data_in_reference(py_ref: &PyReferenceData1D) -> Kde1DResult<f64> {
700 let limits = (Some(py_ref.x_min), Some(py_ref.x_max));
701 let grid_size = py_ref.grid_size;
702 kde_1d(&py_ref.x.clone(), grid_size, limits).unwrap()
704 }
705
706 fn botev_01_claw_distribution() -> MixtureOfNormals {
709 MixtureOfNormals::new(vec![
710 (0.5, (0.0, 1.0)),
711 (0.1, (-1.0, 0.1)),
712 (0.1, (-0.5, 0.1)),
713 (0.1, (0.0, 0.1)),
714 (0.1, (0.5, 0.1)),
715 (0.1, (1.0, 0.1))
716 ])
717 }
718
719 fn botev_02_strongly_skewed_distribution() -> MixtureOfNormals {
720 MixtureOfNormals::new(
721 (0..=7)
722 .map(|k| (1./8., (3. * ((2./3. as f64).powi(k) - 1.), (2./3. as f64).powi(k))))
723 .collect()
724 )
725 }
726
727 fn botev_03_kurtotic_unimodal_distribution() -> MixtureOfNormals {
728 MixtureOfNormals::new(vec![
729 (2./3., (0.0, 1.0)),
730 (1./3., (0.0, 0.1))
731 ])
732 }
733
734 fn botev_04_double_claw_distribution() -> MixtureOfNormals {
735 let mut parameters = vec![
736 (49./100., (-1.0, 2./3.)),
737 (49./100., (1.0, 2./3.))
738 ];
739
740 for k in 0..=6 {
741 parameters.push((1./350., ((k as f64 - 3.0) / 2.0, 1./100.)))
742 }
743
744 MixtureOfNormals::new(parameters)
745 }
746
747 fn botev_05_discrete_comb_distribution() -> MixtureOfNormals {
748 let mut parameters = vec![];
749
750 for k in 0..=2 {
751 parameters.push((2./7., ((12.*(k as f64) - 15.)/7., 2./7.)))
752 }
753
754 for k in 8..=10 {
755 parameters.push((1./21., (2.*(k as f64)/7., 1./21.)))
756 }
757
758 MixtureOfNormals::new(parameters)
759 }
760
761 fn botev_06_asymmetric_double_claw_distribution() -> MixtureOfNormals {
762 let mut parameters = vec![];
763
764 for k in 0..=1 {
765 parameters.push((46./100., (2.*(k as f64) - 1., 2./3.)))
766 }
767
768 for k in 1..=3 {
769 parameters.push((1./300., (-(k as f64)/2., 1./100.)))
770 }
771
772 for k in 1..=3 {
773 parameters.push((7./300., ((k as f64)/2., 7./100.)))
774 }
775
776 MixtureOfNormals::new(parameters)
777 }
778
779 fn botev_07_outlier_distribution() -> MixtureOfNormals {
780 MixtureOfNormals::new(vec![
781 (1./10., (0., 1.)),
782 (9./10., (0., 0.1))
783 ])
784 }
785
786 fn botev_08_separated_bimodal_distribution() -> MixtureOfNormals {
787 MixtureOfNormals::new(vec![
788 (1./2., (-12., 1./2.)),
789 (1./2., (12., 1./2.))
790 ])
791 }
792
793 fn botev_09_skewed_bimodal_distribution() -> MixtureOfNormals {
794 MixtureOfNormals::new(vec![
795 (3./4., (0., 1.)),
796 (1./4., (3./2., 1./3.))
797 ])
798 }
799
800 fn botev_10_bimodal_distribution() -> MixtureOfNormals {
801 MixtureOfNormals::new(vec![
802 (1./2., (0., 0.1)),
803 (1./2., (5., 1.))
804 ])
805 }
806
807 fn botev_13_trimodal_distribution() -> MixtureOfNormals {
808 let mut parameters = vec![];
809 for k in 0..=2 {
810 parameters.push((
811 1./3.,
812 (80.* (k as f64), (k as f64 + 1.).powi(2))
813 ))
814 }
815
816 MixtureOfNormals::new(parameters)
817 }
818
819 fn botev_14_five_modes_distribution() -> MixtureOfNormals {
820 let mut parameters = vec![];
821 for k in 0..=4 {
822 parameters.push((1./5., (80.* (k as f64), k as f64 + 1.)))
823 }
824
825 MixtureOfNormals::new(parameters)
826 }
827
828 fn botev_15_ten_modes_distribution() -> MixtureOfNormals {
829 let mut parameters = vec![];
830 for k in 0..=9 {
831 parameters.push((1./10., (100. * (k as f64), (k as f64 + 1.))))
832 }
833
834 MixtureOfNormals::new(parameters)
835 }
836
837 fn botev_16_smooth_comb_distribution() -> MixtureOfNormals {
838 let mut parameters = vec![];
839 for k in 0..=5 {
840 parameters.push(
841 (2.0_f64.powi(5 - k)/63.,
842 ((65. - 96./(2.0_f64.powi(k)))/21., (32./63.) / 2.0_f64.powi(k)))
843 )
844 }
845
846 MixtureOfNormals::new(parameters)
847 }
848
849 #[test]
850 fn test_botev_01_claw() {
851 let claw = botev_01_claw_distribution();
852 let py_ref = PyReferenceData1D::from_npz("test/reference_densities/botev_01_claw.npz");
853 let kde_result = kde_1d_from_data_in_reference(&py_ref);
854
855 assert_equal_to_reference_within_tolerance!(kde_result, py_ref);
857 assert_kolmogorov_smirnov_does_not_reject_the_null!(claw, py_ref);
859 }
860
861 #[test]
862 fn test_botev_02_strongly_skewed() {
863 let strongly_skewed = botev_02_strongly_skewed_distribution();
864 let py_ref = PyReferenceData1D::from_npz("test/reference_densities/botev_02_strongly_skewed.npz");
865 let kde_result = kde_1d_from_data_in_reference(&py_ref);
866
867 assert_equal_to_reference_within_tolerance!(kde_result, py_ref);
869 assert_kolmogorov_smirnov_does_not_reject_the_null!(strongly_skewed, py_ref);
871 }
872
873 #[test]
874 fn test_botev_03_kurtotic_unimodal() {
875 let kurtotic_unimodal = botev_03_kurtotic_unimodal_distribution();
876
877 let py_ref = PyReferenceData1D::from_npz("test/reference_densities/botev_03_kurtotic_unimodal.npz");
878 let kde_result = kde_1d_from_data_in_reference(&py_ref);
879
880 assert_equal_to_reference_within_tolerance!(kde_result, py_ref);
882 assert_kolmogorov_smirnov_does_not_reject_the_null!(kurtotic_unimodal, py_ref);
884 }
885
886 #[test]
887 fn test_botev_04_double_claw() {
888 let double_claw = botev_04_double_claw_distribution();
889 let py_ref = PyReferenceData1D::from_npz("test/reference_densities/botev_04_double_claw.npz");
890 let kde_result = kde_1d_from_data_in_reference(&py_ref);
891
892 assert_equal_to_reference_within_tolerance!(kde_result, py_ref);
894 assert_kolmogorov_smirnov_does_not_reject_the_null!(double_claw, py_ref);
896 }
897
898 #[test]
899 fn test_botev_05_discrete_comb() {
900 let discrete_comb = botev_05_discrete_comb_distribution();
901
902 let py_ref = PyReferenceData1D::from_npz("test/reference_densities/botev_05_discrete_comb.npz");
903 let kde_result = kde_1d_from_data_in_reference(&py_ref);
904
905 assert_equal_to_reference_within_tolerance!(kde_result, py_ref);
907 assert_kolmogorov_smirnov_does_not_reject_the_null!(discrete_comb, py_ref);
909 }
910
911 #[test]
912 fn test_botev_06_asymmetric_double_claw() {
913 let asymmetric_double_claw = botev_06_asymmetric_double_claw_distribution();
914 let py_ref = PyReferenceData1D::from_npz("test/reference_densities/botev_06_asymmetric_double_claw.npz");
915 let kde_result = kde_1d_from_data_in_reference(&py_ref);
916
917 assert_equal_to_reference_within_tolerance!(kde_result, py_ref);
919 assert_kolmogorov_smirnov_does_not_reject_the_null!(asymmetric_double_claw, py_ref);
921 }
922
923 #[test]
924 fn test_botev_07_outlier() {
925 let outlier = botev_07_outlier_distribution();
926 let py_ref = PyReferenceData1D::from_npz("test/reference_densities/botev_07_outlier.npz");
927 let kde_result = kde_1d_from_data_in_reference(&py_ref);
928
929 assert_equal_to_reference_within_tolerance!(kde_result, py_ref);
931 assert_kolmogorov_smirnov_does_not_reject_the_null!(outlier, py_ref);
933 }
934
935 #[test]
936 fn test_botev_08_separated_bimodal() {
937 let separated_bimodal = botev_08_separated_bimodal_distribution();
938 let py_ref = PyReferenceData1D::from_npz("test/reference_densities/botev_08_separated_bimodal.npz");
939 let kde_result = kde_1d_from_data_in_reference(&py_ref);
940
941 assert_equal_to_reference_within_tolerance!(kde_result, py_ref);
943 assert_kolmogorov_smirnov_does_not_reject_the_null!(separated_bimodal, py_ref);
945 }
946
947 #[test]
948 fn test_botev_09_skewed_bimodal() {
949 let skewed_bimodal = botev_09_skewed_bimodal_distribution();
950 let py_ref = PyReferenceData1D::from_npz("test/reference_densities/botev_09_skewed_bimodal.npz");
951 let kde_result = kde_1d_from_data_in_reference(&py_ref);
952
953 assert_equal_to_reference_within_tolerance!(kde_result, py_ref);
955 assert_kolmogorov_smirnov_does_not_reject_the_null!(skewed_bimodal, py_ref);
957 }
958
959 #[test]
960 fn test_botev_10_bimodal() {
961 let bimodal = botev_10_bimodal_distribution();
962 let py_ref = PyReferenceData1D::from_npz("test/reference_densities/botev_10_bimodal.npz");
963 let kde_result = kde_1d_from_data_in_reference(&py_ref);
964
965 assert_equal_to_reference_within_tolerance!(kde_result, py_ref);
967 assert_kolmogorov_smirnov_does_not_reject_the_null!(bimodal, py_ref);
969 }
970
971 #[test]
972 fn test_botev_13_trimodal() {
973 let trimodal = botev_13_trimodal_distribution();
974 let py_ref = PyReferenceData1D::from_npz("test/reference_densities/botev_13_trimodal.npz");
975 let kde_result = kde_1d_from_data_in_reference(&py_ref);
976
977 assert_equal_to_reference_within_tolerance!(kde_result, py_ref);
979 assert_kolmogorov_smirnov_does_not_reject_the_null!(trimodal, py_ref);
981 }
982
983 #[test]
984 fn test_botev_14_five_modes() {
985 let _five_modes = botev_14_five_modes_distribution();
986 let py_ref = PyReferenceData1D::from_npz("test/reference_densities/botev_14_five_modes.npz");
987 let kde_result = kde_1d_from_data_in_reference(&py_ref);
988
989 assert_equal_to_reference_within_tolerance!(kde_result, py_ref);
991 }
995
996 #[test]
997 fn test_botev_15_ten_modes() {
998 let ten_modal = botev_15_ten_modes_distribution();
999 let py_ref = PyReferenceData1D::from_npz("test/reference_densities/botev_15_ten_modes.npz");
1000 let kde_result = kde_1d_from_data_in_reference(&py_ref);
1001
1002 assert_equal_to_reference_within_tolerance!(kde_result, py_ref);
1004 assert_kolmogorov_smirnov_does_not_reject_the_null!(ten_modal, py_ref);
1006 }
1007
1008 #[test]
1009 fn test_botev_16_smooth_comb() {
1010 let smooth_comb = botev_16_smooth_comb_distribution();
1011 let py_ref = PyReferenceData1D::from_npz("test/reference_densities/botev_16_smooth_comb.npz");
1012 let kde_result = kde_1d_from_data_in_reference(&py_ref);
1013
1014 assert_equal_to_reference_within_tolerance!(kde_result, py_ref);
1016 assert_kolmogorov_smirnov_does_not_reject_the_null!(smooth_comb, py_ref);
1018 }
1019
1020 use std::path::Path;
1021 use std::{io, fs};
1022
1023 fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
1024 fs::create_dir_all(&dst)?;
1025 for entry in fs::read_dir(src)? {
1026 let entry = entry?;
1027 let ty = entry.file_type()?;
1028 if ty.is_dir() {
1029 copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?;
1030 } else {
1031 fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
1032 }
1033 }
1034 Ok(())
1035 }
1036
1037 #[test]
1038 fn build_demo_page() {
1039 let mut tera = Tera::default();
1040 tera.add_template_file("test/templates/demo.html", None).unwrap();
1041
1042 let claw = botev_01_claw_distribution();
1043 let py_ref = PyReferenceData1D::from_npz("test/reference_densities/botev_01_claw.npz");
1044 let kde_result = kde_1d_from_data_in_reference(&py_ref);
1045 let botev_01_claw_plot = plot_density_against_reference(
1046 "botev_01_claw_plot", "Claw", &claw, kde_result, py_ref
1047 );
1048
1049 let strongly_skewed = botev_02_strongly_skewed_distribution();
1050 let py_ref = PyReferenceData1D::from_npz("test/reference_densities/botev_02_strongly_skewed.npz");
1051 let kde_result = kde_1d_from_data_in_reference(&py_ref);
1052 let botev_02_strongly_skewed_plot = plot_density_against_reference(
1053 "botev_02_strongly_skewed_plot", "Strongly skewed", &strongly_skewed, kde_result, py_ref
1054 );
1055
1056 let kurtotic_unimodal = botev_03_kurtotic_unimodal_distribution();
1057 let py_ref = PyReferenceData1D::from_npz("test/reference_densities/botev_03_kurtotic_unimodal.npz");
1058 let kde_result = kde_1d_from_data_in_reference(&py_ref);
1059 let botev_03_kurtotic_unimodal_plot = plot_density_against_reference(
1060 "botev_03_kurtotic_unimodal_plot", "Kurtotic unimodal", &kurtotic_unimodal, kde_result, py_ref
1061 );
1062
1063 let double_claw = botev_04_double_claw_distribution();
1064 let py_ref = PyReferenceData1D::from_npz("test/reference_densities/botev_04_double_claw.npz");
1065 let kde_result = kde_1d_from_data_in_reference(&py_ref);
1066 let botev_04_double_claw_plot = plot_density_against_reference(
1067 "botev_04_double_claw_plot", "Double claw", &double_claw, kde_result, py_ref
1068 );
1069
1070 let discrete_comb = botev_05_discrete_comb_distribution();
1071 let py_ref = PyReferenceData1D::from_npz("test/reference_densities/botev_05_discrete_comb.npz");
1072 let kde_result = kde_1d_from_data_in_reference(&py_ref);
1073 let botev_05_discrete_comb_plot = plot_density_against_reference(
1074 "botev_05_discrete_comb_plot", "Discrete comb", &discrete_comb, kde_result, py_ref
1075 );
1076
1077 let asymmetric_double_claw = botev_06_asymmetric_double_claw_distribution();
1078 let py_ref = PyReferenceData1D::from_npz("test/reference_densities/botev_06_asymmetric_double_claw.npz");
1079 let kde_result = kde_1d_from_data_in_reference(&py_ref);
1080 let botev_06_asymmetric_double_claw_plot = plot_density_against_reference(
1081 "botev_06_asymmetric_double_claw_plot",
1082 "Asymmetric double claw",
1083 &asymmetric_double_claw,
1084 kde_result,
1085 py_ref
1086 );
1087
1088 let outlier = botev_07_outlier_distribution();
1089 let py_ref = PyReferenceData1D::from_npz("test/reference_densities/botev_07_outlier.npz");
1090 let kde_result = kde_1d_from_data_in_reference(&py_ref);
1091 let botev_07_outlier_plot = plot_density_against_reference(
1092 "botev_07_outlier_plot", "Outlier", &outlier, kde_result, py_ref
1093 );
1094
1095 let separated_bimodal = botev_08_separated_bimodal_distribution();
1096 let py_ref = PyReferenceData1D::from_npz("test/reference_densities/botev_08_separated_bimodal.npz");
1097 let kde_result = kde_1d_from_data_in_reference(&py_ref);
1098 let botev_08_separated_bimodal_plot = plot_density_against_reference(
1099 "botev_08_separated_bimodal_plot", "Separated bimodal", &separated_bimodal, kde_result, py_ref
1100 );
1101
1102 let skewed_bimodal = botev_09_skewed_bimodal_distribution();
1103 let py_ref = PyReferenceData1D::from_npz("test/reference_densities/botev_09_skewed_bimodal.npz");
1104 let kde_result = kde_1d_from_data_in_reference(&py_ref);
1105 let botev_09_skewed_bimodal_plot = plot_density_against_reference(
1106 "botev_09_skewed_bimodal_plot", "Skewed bimodal", &skewed_bimodal, kde_result, py_ref
1107 );
1108
1109 let bimodal = botev_10_bimodal_distribution();
1110 let py_ref = PyReferenceData1D::from_npz("test/reference_densities/botev_10_bimodal.npz");
1111 let kde_result = kde_1d_from_data_in_reference(&py_ref);
1112 let botev_10_bimodal_plot = plot_density_against_reference(
1113 "botev_10_bimodal_plot", "Skewed bimodal", &bimodal, kde_result, py_ref
1114 );
1115
1116 let trimodal = botev_13_trimodal_distribution();
1117 let py_ref = PyReferenceData1D::from_npz("test/reference_densities/botev_13_trimodal.npz");
1118 let kde_result = kde_1d_from_data_in_reference(&py_ref);
1119 let botev_13_trimodal_plot = plot_density_against_reference(
1120 "botev_13_trimodal_plot", "Trimodal", &trimodal, kde_result, py_ref
1121 );
1122
1123 let five_modes = botev_14_five_modes_distribution();
1124 let py_ref = PyReferenceData1D::from_npz("test/reference_densities/botev_14_five_modes.npz");
1125 let kde_result = kde_1d_from_data_in_reference(&py_ref);
1126 let botev_14_five_modes_plot = plot_density_against_reference(
1127 "botev_14_five_modes_plot", "Five modes", &five_modes, kde_result, py_ref
1128 );
1129
1130 let ten_modes = botev_15_ten_modes_distribution();
1131 let py_ref = PyReferenceData1D::from_npz("test/reference_densities/botev_15_ten_modes.npz");
1132 let kde_result = kde_1d_from_data_in_reference(&py_ref);
1133 let botev_15_ten_modes_plot = plot_density_against_reference(
1134 "botev_15_ten_modes_plot", "Ten modes", &ten_modes, kde_result, py_ref
1135 );
1136
1137 let smooth_comb = botev_16_smooth_comb_distribution();
1138 let py_ref = PyReferenceData1D::from_npz("test/reference_densities/botev_16_smooth_comb.npz");
1139 let kde_result = kde_1d_from_data_in_reference(&py_ref);
1140 let botev_16_smooth_comb_plot = plot_density_against_reference(
1141 "botev_16_smooth_comb_plot", "Smooth comb", &smooth_comb, kde_result, py_ref
1142 );
1143
1144 let mut context = Context::new();
1145 context.insert("botev_01_claw_plot", &botev_01_claw_plot);
1146 context.insert("botev_02_strongly_skewed_plot", &botev_02_strongly_skewed_plot);
1147 context.insert("botev_03_kurtotic_unimodal_plot", &botev_03_kurtotic_unimodal_plot);
1148 context.insert("botev_04_double_claw_plot", &botev_04_double_claw_plot);
1149 context.insert("botev_05_discrete_comb_plot", &botev_05_discrete_comb_plot);
1150 context.insert("botev_06_asymmetric_double_claw_plot", &botev_06_asymmetric_double_claw_plot);
1151 context.insert("botev_07_outlier_plot", &botev_07_outlier_plot);
1152 context.insert("botev_08_separated_bimodal_plot", &botev_08_separated_bimodal_plot);
1153 context.insert("botev_09_skewed_bimodal_plot", &botev_09_skewed_bimodal_plot);
1154 context.insert("botev_10_bimodal_plot", &botev_10_bimodal_plot);
1155 context.insert("botev_13_trimodal_plot", &botev_13_trimodal_plot);
1156 context.insert("botev_14_five_modes_plot", &botev_14_five_modes_plot);
1157 context.insert("botev_15_ten_modes_plot", &botev_15_ten_modes_plot);
1158 context.insert("botev_16_smooth_comb_plot", &botev_16_smooth_comb_plot);
1159
1160 let output = tera.render("test/templates/demo.html", &context).unwrap();
1161
1162 let mut file = File::create("webpage/index.html").unwrap();
1163 let _ = file.write_all(&output.as_bytes());
1164
1165 copy_dir_all(
1167 Path::new("target/criterion/Double claw"),
1168 Path::new("webpage/benchmarks/double-claw")
1169 ).unwrap();
1170 }
1171
1172 }