1#![allow(non_snake_case)]
7#![allow(clippy::type_complexity)]
8#![allow(clippy::too_many_arguments)]
9
10use rand::Rng;
11pub use stochastic_rs_core::simd_rng;
12use wide::f32x8;
13use wide::f64x8;
14
15#[macro_use]
16mod macros;
17
18pub mod float_impls;
19pub mod special;
20pub mod traits;
21
22#[cfg(feature = "python")]
23pub use crate::traits::CallableDist;
24pub use crate::traits::DistributionExt;
25pub use crate::traits::DistributionSampler;
26pub use crate::traits::FloatExt;
27pub use crate::traits::Fn1D;
28pub use crate::traits::Fn2D;
29pub use crate::traits::SimdFloatExt;
30
31pub mod alpha_stable;
32pub mod beta;
33pub mod binomial;
34pub mod cauchy;
35pub mod chi_square;
36pub mod complex;
37pub mod exp;
38pub mod gamma;
39pub mod geometric;
40pub mod hypergeometric;
41pub mod inverse_gauss;
42pub mod lognormal;
43pub mod non_central_chi_squared;
44pub mod normal;
45pub mod normal_inverse_gauss;
46pub mod pareto;
47pub mod poisson;
48pub mod studentt;
49pub mod uniform;
50pub mod weibull;
51
52macro_rules! impl_distribution_sampler_float {
53 ($($dist:ty),+ $(,)?) => {
54 $(
55 impl<T: SimdFloatExt> DistributionSampler<T> for $dist {
56 #[inline]
57 fn fill_slice<R: Rng + ?Sized>(&self, rng: &mut R, out: &mut [T]) {
58 self.fill_slice(rng, out);
59 }
60 }
61 )+
62 };
63}
64
65macro_rules! impl_distribution_sampler_int {
66 ($($dist:ty),+ $(,)?) => {
67 $(
68 impl<T: num_traits::PrimInt> DistributionSampler<T> for $dist {
69 #[inline]
70 fn fill_slice<R: Rng + ?Sized>(&self, rng: &mut R, out: &mut [T]) {
71 self.fill_slice(rng, out);
72 }
73 }
74 )+
75 };
76}
77
78macro_rules! impl_distribution_sampler_float_const_n {
79 ($($dist:ty),+ $(,)?) => {
80 $(
81 impl<T: SimdFloatExt, const N: usize> DistributionSampler<T> for $dist {
82 #[inline]
83 fn fill_slice<R: Rng + ?Sized>(&self, rng: &mut R, out: &mut [T]) {
84 self.fill_slice(rng, out);
85 }
86 }
87 )+
88 };
89}
90
91impl_distribution_sampler_float!(
92 alpha_stable::SimdAlphaStable<T>,
93 beta::SimdBeta<T>,
94 cauchy::SimdCauchy<T>,
95 chi_square::SimdChiSquared<T>,
96 exp::SimdExp<T>,
97 gamma::SimdGamma<T>,
98 inverse_gauss::SimdInverseGauss<T>,
99 lognormal::SimdLogNormal<T>,
100 normal_inverse_gauss::SimdNormalInverseGauss<T>,
101 pareto::SimdPareto<T>,
102 studentt::SimdStudentT<T>,
103 uniform::SimdUniform<T>,
104 weibull::SimdWeibull<T>,
105);
106
107impl_distribution_sampler_int!(
108 binomial::SimdBinomial<T>,
109 geometric::SimdGeometric<T>,
110 hypergeometric::SimdHypergeometric<T>,
111 poisson::SimdPoisson<T>,
112);
113
114impl_distribution_sampler_float_const_n!(normal::SimdNormal<T, N>, exp::SimdExpZig<T, N>,);
115
116#[cfg(test)]
117mod distribution_sampler_tests {
118 use super::DistributionSampler;
119 use super::normal::SimdNormal;
120 use super::poisson::SimdPoisson;
121
122 #[test]
123 fn sample_n_returns_requested_length() {
124 let dist = SimdNormal::<f64>::new(0.0, 1.0);
125 let out = dist.sample_n(1024);
126 assert_eq!(out.len(), 1024);
127 }
128
129 #[test]
130 fn sample_matrix_float_has_expected_shape() {
131 let dist = SimdNormal::<f32>::new(0.0, 1.0);
132 let out = dist.sample_matrix(32, 64);
133 assert_eq!(out.shape(), &[32, 64]);
134 }
135
136 #[test]
137 fn sample_matrix_int_has_expected_shape() {
138 let dist = SimdPoisson::<i64>::new(1.5);
139 let out = dist.sample_matrix(16, 8);
140 assert_eq!(out.shape(), &[16, 8]);
141 }
142}
143
144fn fill_f32_zero_one<R: Rng + ?Sized>(rng: &mut R, out: &mut [f32]) {
145 for x in out.iter_mut() {
146 *x = rng.random();
147 }
148}
149
150fn fill_f64_zero_one<R: Rng + ?Sized>(rng: &mut R, out: &mut [f64]) {
151 for x in out.iter_mut() {
152 *x = rng.random();
153 }
154}
155
156impl SimdFloatExt for f32 {
157 type Simd = f32x8;
158
159 fn splat(val: f32) -> f32x8 {
160 f32x8::splat(val)
161 }
162
163 fn simd_from_array(arr: [f32; 8]) -> f32x8 {
164 f32x8::from(arr)
165 }
166
167 fn simd_to_array(v: f32x8) -> [f32; 8] {
168 v.to_array()
169 }
170
171 fn simd_ln(v: f32x8) -> f32x8 {
172 v.ln()
173 }
174
175 fn simd_sqrt(v: f32x8) -> f32x8 {
176 v.sqrt()
177 }
178
179 fn simd_cos(v: f32x8) -> f32x8 {
180 v.cos()
181 }
182
183 fn simd_sin(v: f32x8) -> f32x8 {
184 v.sin()
185 }
186
187 fn simd_exp(v: f32x8) -> f32x8 {
188 v.exp()
189 }
190
191 fn simd_tan(v: f32x8) -> f32x8 {
192 v.tan()
193 }
194
195 fn simd_max(a: f32x8, b: f32x8) -> f32x8 {
196 a.max(b)
197 }
198
199 fn simd_powf(v: f32x8, exp: f32) -> f32x8 {
200 v.powf(exp)
201 }
202
203 fn simd_floor(v: f32x8) -> f32x8 {
204 v.floor()
205 }
206
207 fn fill_uniform<R: Rng + ?Sized>(rng: &mut R, out: &mut [f32]) {
208 fill_f32_zero_one(rng, out)
209 }
210
211 fn fill_uniform_simd(rng: &mut crate::simd_rng::SimdRng, out: &mut [f32]) {
212 let mut chunks = out.chunks_exact_mut(8);
213 for chunk in &mut chunks {
214 chunk.copy_from_slice(&rng.next_f32_array());
215 }
216 let rem = chunks.into_remainder();
217 if !rem.is_empty() {
218 let arr = rng.next_f32_array();
219 rem.copy_from_slice(&arr[..rem.len()]);
220 }
221 }
222
223 fn sample_uniform<R: Rng + ?Sized>(rng: &mut R) -> f32 {
224 rng.random()
225 }
226
227 #[inline(always)]
228 fn sample_uniform_simd(rng: &mut crate::simd_rng::SimdRng) -> f32 {
229 rng.next_f32()
230 }
231
232 fn simd_from_i32x8(v: wide::i32x8) -> f32x8 {
233 v.round_float()
234 }
235
236 const PREFERS_F32_WN: bool = true;
237
238 #[inline(always)]
239 fn from_f64_fast(v: f64) -> f32 {
240 v as f32
241 }
242
243 #[inline(always)]
244 fn from_f32_fast(v: f32) -> f32 {
245 v
246 }
247
248 fn pi() -> f32 {
249 std::f32::consts::PI
250 }
251
252 fn two_pi() -> f32 {
253 2.0 * std::f32::consts::PI
254 }
255
256 fn min_positive_val() -> f32 {
257 f32::MIN_POSITIVE
258 }
259}
260
261impl SimdFloatExt for f64 {
262 type Simd = f64x8;
263
264 fn splat(val: f64) -> f64x8 {
265 f64x8::splat(val)
266 }
267
268 fn simd_from_array(arr: [f64; 8]) -> f64x8 {
269 f64x8::from(arr)
270 }
271
272 fn simd_to_array(v: f64x8) -> [f64; 8] {
273 v.to_array()
274 }
275
276 fn simd_ln(v: f64x8) -> f64x8 {
277 v.ln()
278 }
279
280 fn simd_sqrt(v: f64x8) -> f64x8 {
281 v.sqrt()
282 }
283
284 fn simd_cos(v: f64x8) -> f64x8 {
285 v.cos()
286 }
287
288 fn simd_sin(v: f64x8) -> f64x8 {
289 v.sin()
290 }
291
292 fn simd_exp(v: f64x8) -> f64x8 {
293 v.exp()
294 }
295
296 fn simd_tan(v: f64x8) -> f64x8 {
297 v.tan()
298 }
299
300 fn simd_max(a: f64x8, b: f64x8) -> f64x8 {
301 a.max(b)
302 }
303
304 fn simd_powf(v: f64x8, exp: f64) -> f64x8 {
305 v.powf(exp)
306 }
307
308 fn simd_floor(v: f64x8) -> f64x8 {
309 v.floor()
310 }
311
312 fn fill_uniform<R: Rng + ?Sized>(rng: &mut R, out: &mut [f64]) {
313 fill_f64_zero_one(rng, out)
314 }
315
316 fn fill_uniform_simd(rng: &mut crate::simd_rng::SimdRng, out: &mut [f64]) {
317 let mut chunks = out.chunks_exact_mut(8);
318 for chunk in &mut chunks {
319 chunk.copy_from_slice(&rng.next_f64_array());
320 }
321 let rem = chunks.into_remainder();
322 if !rem.is_empty() {
323 let arr = rng.next_f64_array();
324 rem.copy_from_slice(&arr[..rem.len()]);
325 }
326 }
327
328 fn sample_uniform<R: Rng + ?Sized>(rng: &mut R) -> f64 {
329 rng.random()
330 }
331
332 #[inline(always)]
333 fn sample_uniform_simd(rng: &mut crate::simd_rng::SimdRng) -> f64 {
334 rng.next_f64()
335 }
336
337 fn simd_from_i32x8(v: wide::i32x8) -> f64x8 {
338 f64x8::from_i32x8(v)
339 }
340
341 const PREFERS_F32_WN: bool = false;
342
343 #[inline(always)]
344 fn from_f64_fast(v: f64) -> f64 {
345 v
346 }
347
348 fn pi() -> f64 {
349 std::f64::consts::PI
350 }
351
352 fn two_pi() -> f64 {
353 2.0 * std::f64::consts::PI
354 }
355
356 fn min_positive_val() -> f64 {
357 f64::MIN_POSITIVE
358 }
359}
360
361#[cfg(test)]
362mod tests {
363 use plotly::Layout;
364 use plotly::Plot;
365 use plotly::Scatter;
366 use plotly::common::Line;
367 use plotly::common::LineShape;
368 use plotly::common::Mode;
369 use plotly::layout::GridPattern;
370 use plotly::layout::LayoutGrid;
371 use rand::rng;
372 use rand_distr::Distribution;
373
374 use crate::beta::SimdBeta;
375 use crate::binomial::SimdBinomial;
376 use crate::cauchy::SimdCauchy;
377 use crate::chi_square::SimdChiSquared;
378 use crate::exp::SimdExp;
379 use crate::exp::SimdExpZig;
380 use crate::gamma::SimdGamma;
381 use crate::geometric::SimdGeometric;
382 use crate::hypergeometric::SimdHypergeometric;
383 use crate::inverse_gauss::SimdInverseGauss;
384 use crate::lognormal::SimdLogNormal;
385 use crate::normal::SimdNormal;
386 use crate::normal_inverse_gauss::SimdNormalInverseGauss;
387 use crate::pareto::SimdPareto;
388 use crate::poisson::SimdPoisson;
389 use crate::studentt::SimdStudentT;
390 use crate::uniform::SimdUniform;
391 use crate::weibull::SimdWeibull;
392
393 fn make_histogram(
395 samples: &[f32],
396 bins_count: usize,
397 min_x: f32,
398 max_x: f32,
399 ) -> (Vec<f32>, Vec<f32>) {
400 let bin_width = (max_x - min_x) / bins_count as f32;
401 let mut bins = vec![0.0; bins_count + 1];
402 for &val in samples {
403 if val >= min_x && val < max_x {
404 let idx = ((val - min_x) / bin_width) as usize;
405 bins[idx] += 1.0;
406 }
407 }
408 let total = samples.len() as f32;
409 for b in bins.iter_mut() {
411 *b /= total * bin_width;
412 }
413 let xs: Vec<f32> = (0..bins_count)
414 .map(|i| min_x + (i as f32 + 0.5) * bin_width)
415 .collect();
416 (xs, bins)
417 }
418
419 fn make_discrete_pmf(samples: &[u32], max_range: u32) -> (Vec<f32>, Vec<f32>) {
421 let mut bins = vec![0.0; (max_range + 1) as usize];
422 for &val in samples {
423 if val <= max_range {
424 bins[val as usize] += 1.0;
425 }
426 }
427 let n = samples.len() as f32;
428 for b in bins.iter_mut() {
429 *b /= n;
430 }
431 let xs: Vec<f32> = (0..=max_range).map(|i| i as f32).collect();
432 (xs, bins)
433 }
434
435 fn subplot_axes(row: usize, col: usize) -> (String, String) {
439 let index = (row - 1) * 4 + col;
441 let xaxis = format!("x{}", index);
442 let yaxis = format!("y{}", index);
443 (xaxis, yaxis)
444 }
445
446 #[test]
447 #[ignore = "interactive: opens a browser via plot.show(); run with --ignored when generating combined plots"]
448 fn combined_all_distributions() {
449 let mut plot = Plot::new();
451 plot.set_layout(
452 Layout::new().grid(
453 LayoutGrid::new()
454 .rows(5)
455 .columns(4)
456 .pattern(GridPattern::Independent),
457 ),
458 );
459
460 let sample_size = 50_000;
463
464 {
466 let (xa, ya) = subplot_axes(1, 1);
467 let mut rng = rand::rng();
468 let dist: SimdNormal<f32> = SimdNormal::new(0.0, 1.0);
469 let samples: Vec<f32> = (0..sample_size).map(|_| dist.sample(&mut rng)).collect();
470 let (xs, bins) = make_histogram(&samples, 100, -4.0, 4.0);
471 let trace = Scatter::new(xs.clone(), bins)
472 .name("Normal(0,1) - SIMD")
473 .mode(Mode::Lines)
474 .line(Line::new().shape(LineShape::Linear))
475 .x_axis(&xa)
476 .y_axis(&ya);
477 plot.add_trace(trace);
478
479 let mut rng = rand::rng();
481 let rd = rand_distr::Normal::<f32>::new(0.0, 1.0).unwrap();
482 let samples_rd: Vec<f32> = (0..sample_size).map(|_| rd.sample(&mut rng)).collect();
483 let (_, bins_rd) = make_histogram(&samples_rd, 100, -4.0, 4.0);
484 let trace_rd = Scatter::new(xs, bins_rd)
485 .name("Normal(0,1) - rand_distr")
486 .mode(Mode::Lines)
487 .line(
488 Line::new()
489 .shape(LineShape::Linear)
490 .dash(plotly::common::DashType::Dash),
491 )
492 .x_axis(&xa)
493 .y_axis(&ya);
494 plot.add_trace(trace_rd);
495 }
496
497 {
499 let (xa, ya) = subplot_axes(1, 2);
500 let mut rng = rand::rng();
501 let dist = SimdCauchy::new(0.0, 1.0);
502 let samples: Vec<f32> = (0..sample_size).map(|_| dist.sample(&mut rng)).collect();
503 let (xs, bins) = make_histogram(&samples, 100, -10.0, 10.0);
504 let trace = Scatter::new(xs.clone(), bins)
505 .name("Cauchy(0,1) - SIMD")
506 .mode(Mode::Lines)
507 .line(Line::new().shape(LineShape::Linear))
508 .x_axis(&xa)
509 .y_axis(&ya);
510 plot.add_trace(trace);
511
512 let mut rng = rand::rng();
514 let rd = rand_distr::Cauchy::<f32>::new(0.0, 1.0).unwrap();
515 let samples_rd: Vec<f32> = (0..sample_size).map(|_| rd.sample(&mut rng)).collect();
516 let (_, bins_rd) = make_histogram(&samples_rd, 100, -10.0, 10.0);
517 let trace_rd = Scatter::new(xs, bins_rd)
518 .name("Cauchy(0,1) - rand_distr")
519 .mode(Mode::Lines)
520 .line(
521 Line::new()
522 .shape(LineShape::Linear)
523 .dash(plotly::common::DashType::Dash),
524 )
525 .x_axis(&xa)
526 .y_axis(&ya);
527 plot.add_trace(trace_rd);
528 }
529
530 {
532 let (xa, ya) = subplot_axes(1, 3);
533 let mut rng = rand::rng();
534 let dist = SimdLogNormal::new(0.0, 1.0);
535 let samples: Vec<f32> = (0..sample_size).map(|_| dist.sample(&mut rng)).collect();
536 let (xs, bins) = make_histogram(&samples, 100, 0.0, 8.0);
537 let trace = Scatter::new(xs.clone(), bins)
538 .name("LogNormal(0,1) - SIMD")
539 .mode(Mode::Lines)
540 .line(Line::new().shape(LineShape::Linear))
541 .x_axis(&xa)
542 .y_axis(&ya);
543 plot.add_trace(trace);
544
545 let mut rng = rand::rng();
547 let rd = rand_distr::LogNormal::<f32>::new(0.0, 1.0).unwrap();
548 let samples_rd: Vec<f32> = (0..sample_size).map(|_| rd.sample(&mut rng)).collect();
549 let (_, bins_rd) = make_histogram(&samples_rd, 100, 0.0, 8.0);
550 let trace_rd = Scatter::new(xs, bins_rd)
551 .name("LogNormal(0,1) - rand_distr")
552 .mode(Mode::Lines)
553 .line(
554 Line::new()
555 .shape(LineShape::Linear)
556 .dash(plotly::common::DashType::Dash),
557 )
558 .x_axis(&xa)
559 .y_axis(&ya);
560 plot.add_trace(trace_rd);
561 }
562
563 {
565 let (xa, ya) = subplot_axes(1, 4);
566 let mut rng = rand::rng();
567 let dist = SimdPareto::new(1.0, 1.5);
568 let samples: Vec<f32> = (0..sample_size).map(|_| dist.sample(&mut rng)).collect();
569 let (xs, bins) = make_histogram(&samples, 100, 0.0, 10.0);
570 let trace = Scatter::new(xs.clone(), bins)
571 .name("Pareto(1,1.5) - SIMD")
572 .mode(Mode::Lines)
573 .line(Line::new().shape(LineShape::Linear))
574 .x_axis(&xa)
575 .y_axis(&ya);
576 plot.add_trace(trace);
577
578 let mut rng = rand::rng();
580 let rd = rand_distr::Pareto::<f32>::new(1.0, 1.5).unwrap();
581 let samples_rd: Vec<f32> = (0..sample_size).map(|_| rd.sample(&mut rng)).collect();
582 let (_, bins_rd) = make_histogram(&samples_rd, 100, 0.0, 10.0);
583 let trace_rd = Scatter::new(xs, bins_rd)
584 .name("Pareto(1,1.5) - rand_distr")
585 .mode(Mode::Lines)
586 .line(
587 Line::new()
588 .shape(LineShape::Linear)
589 .dash(plotly::common::DashType::Dash),
590 )
591 .x_axis(&xa)
592 .y_axis(&ya);
593 plot.add_trace(trace_rd);
594 }
595
596 {
598 let (xa, ya) = subplot_axes(2, 1);
599 let mut rng = rand::rng();
600 let dist = SimdWeibull::new(1.0, 1.5);
601 let samples: Vec<f32> = (0..sample_size).map(|_| dist.sample(&mut rng)).collect();
602 let (xs, bins) = make_histogram(&samples, 100, 0.0, 3.0);
603 let trace = Scatter::new(xs.clone(), bins)
604 .name("Weibull(1,1.5) - SIMD")
605 .mode(Mode::Lines)
606 .line(Line::new().shape(LineShape::Linear))
607 .x_axis(&xa)
608 .y_axis(&ya);
609 plot.add_trace(trace);
610
611 let mut rng = rand::rng();
613 let rd = rand_distr::Weibull::<f32>::new(1.0, 1.5).unwrap();
614 let samples_rd: Vec<f32> = (0..sample_size).map(|_| rd.sample(&mut rng)).collect();
615 let (_, bins_rd) = make_histogram(&samples_rd, 100, 0.0, 3.0);
616 let trace_rd = Scatter::new(xs, bins_rd)
617 .name("Weibull(1,1.5) - rand_distr")
618 .mode(Mode::Lines)
619 .line(
620 Line::new()
621 .shape(LineShape::Linear)
622 .dash(plotly::common::DashType::Dash),
623 )
624 .x_axis(&xa)
625 .y_axis(&ya);
626 plot.add_trace(trace_rd);
627 }
628
629 {
631 let (xa, ya) = subplot_axes(2, 2);
632 let mut rng = rand::rng();
633 let dist = SimdGamma::new(2.0, 2.0);
634 let samples: Vec<f32> = (0..sample_size).map(|_| dist.sample(&mut rng)).collect();
635 let (xs, bins) = make_histogram(&samples, 120, 0.0, 20.0);
636 let trace = Scatter::new(xs.clone(), bins)
637 .name("Gamma(2,2) - SIMD")
638 .mode(Mode::Lines)
639 .line(Line::new().shape(LineShape::Linear))
640 .x_axis(&xa)
641 .y_axis(&ya);
642 plot.add_trace(trace);
643
644 let mut rng = rand::rng();
646 let rd = rand_distr::Gamma::<f32>::new(2.0, 2.0).unwrap();
647 let samples_rd: Vec<f32> = (0..sample_size).map(|_| rd.sample(&mut rng)).collect();
648 let (_, bins_rd) = make_histogram(&samples_rd, 120, 0.0, 20.0);
649 let trace_rd = Scatter::new(xs, bins_rd)
650 .name("Gamma(2,2) - rand_distr")
651 .mode(Mode::Lines)
652 .line(
653 Line::new()
654 .shape(LineShape::Linear)
655 .dash(plotly::common::DashType::Dash),
656 )
657 .x_axis(&xa)
658 .y_axis(&ya);
659 plot.add_trace(trace_rd);
660 }
661
662 {
664 let (xa, ya) = subplot_axes(2, 3);
665 let mut rng = rand::rng();
666 let dist = SimdBeta::new(2.0, 2.0);
667 let samples: Vec<f32> = (0..sample_size).map(|_| dist.sample(&mut rng)).collect();
668 let (xs, bins) = make_histogram(&samples, 100, 0.0, 1.0);
669 let trace = Scatter::new(xs.clone(), bins)
670 .name("Beta(2,2) - SIMD")
671 .mode(Mode::Lines)
672 .line(Line::new().shape(LineShape::Linear))
673 .x_axis(&xa)
674 .y_axis(&ya);
675 plot.add_trace(trace);
676
677 let mut rng = rand::rng();
679 let rd = rand_distr::Beta::<f32>::new(2.0, 2.0).unwrap();
680 let samples_rd: Vec<f32> = (0..sample_size).map(|_| rd.sample(&mut rng)).collect();
681 let (_, bins_rd) = make_histogram(&samples_rd, 100, 0.0, 1.0);
682 let trace_rd = Scatter::new(xs, bins_rd)
683 .name("Beta(2,2) - rand_distr")
684 .mode(Mode::Lines)
685 .line(
686 Line::new()
687 .shape(LineShape::Linear)
688 .dash(plotly::common::DashType::Dash),
689 )
690 .x_axis(&xa)
691 .y_axis(&ya);
692 plot.add_trace(trace_rd);
693 }
694
695 {
697 let (xa, ya) = subplot_axes(2, 4);
698 let mut rng = rng();
699 let dist = SimdInverseGauss::new(1.0, 2.0);
700 let samples: Vec<f32> = (0..sample_size).map(|_| dist.sample(&mut rng)).collect();
701 let (xs, bins) = make_histogram(&samples, 100, 0.0, 3.0);
702 let trace = Scatter::new(xs.clone(), bins)
703 .name("InverseGauss(1,2) - SIMD")
704 .mode(Mode::Lines)
705 .line(Line::new().shape(LineShape::Linear))
706 .x_axis(&xa)
707 .y_axis(&ya);
708 plot.add_trace(trace);
709
710 let mut rng = rand::rng();
712 let rd = rand_distr::InverseGaussian::<f32>::new(1.0, 2.0).unwrap();
713 let samples_rd: Vec<f32> = (0..sample_size).map(|_| rd.sample(&mut rng)).collect();
714 let (_, bins_rd) = make_histogram(&samples_rd, 100, 0.0, 3.0);
715 let trace_rd = Scatter::new(xs, bins_rd)
716 .name("InverseGauss(1,2) - rand_distr")
717 .mode(Mode::Lines)
718 .line(
719 Line::new()
720 .shape(LineShape::Linear)
721 .dash(plotly::common::DashType::Dash),
722 )
723 .x_axis(&xa)
724 .y_axis(&ya);
725 plot.add_trace(trace_rd);
726 }
727
728 {
730 let (xa, ya) = subplot_axes(3, 1);
731 let mut rng = rand::rng();
732 let dist = SimdNormalInverseGauss::new(2.0, 0.0, 1.0, 0.0);
733 let samples: Vec<f32> = (0..sample_size).map(|_| dist.sample(&mut rng)).collect();
734 let (xs, bins) = make_histogram(&samples, 100, -3.0, 3.0);
735 let trace = Scatter::new(xs.clone(), bins)
736 .name("Nig(2,0) - SIMD")
737 .mode(Mode::Lines)
738 .line(Line::new().shape(LineShape::Linear))
739 .x_axis(&xa)
740 .y_axis(&ya);
741 plot.add_trace(trace);
742
743 let mut rng = rand::rng();
745 let rd = rand_distr::NormalInverseGaussian::<f32>::new(2.0, 0.0).unwrap();
746 let samples_rd: Vec<f32> = (0..sample_size).map(|_| rd.sample(&mut rng)).collect();
747 let (_, bins_rd) = make_histogram(&samples_rd, 100, -3.0, 3.0);
748 let trace_rd = Scatter::new(xs, bins_rd)
749 .name("Nig(2,0) - rand_distr")
750 .mode(Mode::Lines)
751 .line(
752 Line::new()
753 .shape(LineShape::Linear)
754 .dash(plotly::common::DashType::Dash),
755 )
756 .x_axis(&xa)
757 .y_axis(&ya);
758 plot.add_trace(trace_rd);
759 }
760
761 {
763 let (xa, ya) = subplot_axes(3, 2);
764 let mut rng = rng();
765 let dist = SimdStudentT::new(5.0);
766 let samples: Vec<f32> = (0..sample_size).map(|_| dist.sample(&mut rng)).collect();
767 let (xs, bins) = make_histogram(&samples, 120, -5.0, 5.0);
768 let trace = Scatter::new(xs.clone(), bins)
769 .name("StudentT(5) - SIMD")
770 .mode(Mode::Lines)
771 .line(Line::new().shape(LineShape::Linear))
772 .x_axis(&xa)
773 .y_axis(&ya);
774 plot.add_trace(trace);
775
776 let mut rng = rand::rng();
778 let rd = rand_distr::StudentT::<f32>::new(5.0).unwrap();
779 let samples_rd: Vec<f32> = (0..sample_size).map(|_| rd.sample(&mut rng)).collect();
780 let (_, bins_rd) = make_histogram(&samples_rd, 120, -5.0, 5.0);
781 let trace_rd = Scatter::new(xs, bins_rd)
782 .name("StudentT(5) - rand_distr")
783 .mode(Mode::Lines)
784 .line(
785 Line::new()
786 .shape(LineShape::Linear)
787 .dash(plotly::common::DashType::Dash),
788 )
789 .x_axis(&xa)
790 .y_axis(&ya);
791 plot.add_trace(trace_rd);
792 }
793
794 {
796 let (xa, ya) = subplot_axes(3, 3);
797 let mut rng = rand::rng();
798 let dist = SimdBinomial::new(10, 0.3);
799 let samples: Vec<u32> = (0..sample_size).map(|_| dist.sample(&mut rng)).collect();
800 let (xs, bins) = make_discrete_pmf(&samples, 10);
801
802 let trace = Scatter::new(xs.clone(), bins)
803 .name("Binomial(10,0.3) - SIMD")
804 .mode(Mode::Lines)
805 .line(Line::new().shape(LineShape::Linear))
806 .x_axis(&xa)
807 .y_axis(&ya);
808 plot.add_trace(trace);
809
810 let mut rng = rand::rng();
812 let rd = rand_distr::Binomial::new(10, 0.3).unwrap();
813 let samples_rd: Vec<u32> = (0..sample_size)
814 .map(|_| rd.sample(&mut rng) as u32)
815 .collect();
816 let (_, bins_rd) = make_discrete_pmf(&samples_rd, 10);
817 let trace_rd = Scatter::new(xs, bins_rd)
818 .name("Binomial(10,0.3) - rand_distr")
819 .mode(Mode::Lines)
820 .line(
821 Line::new()
822 .shape(LineShape::Linear)
823 .dash(plotly::common::DashType::Dash),
824 )
825 .x_axis(&xa)
826 .y_axis(&ya);
827 plot.add_trace(trace_rd);
828 }
829
830 {
832 let (xa, ya) = subplot_axes(3, 4);
833 let mut rng = rng();
834 let dist = SimdGeometric::new(0.25);
835 let samples: Vec<u32> = (0..sample_size).map(|_| dist.sample(&mut rng)).collect();
836 let (xs, bins) = make_discrete_pmf(&samples, 20);
837 let trace = Scatter::new(xs.clone(), bins)
838 .name("Geometric(0.25) - SIMD")
839 .mode(Mode::Lines)
840 .line(Line::new().shape(LineShape::Linear))
841 .x_axis(&xa)
842 .y_axis(&ya);
843 plot.add_trace(trace);
844
845 let mut rng = rand::rng();
847 let rd = rand_distr::Geometric::new(0.25).unwrap();
848 let samples_rd: Vec<u32> = (0..sample_size)
849 .map(|_| rd.sample(&mut rng) as u32)
850 .collect();
851 let (_, bins_rd) = make_discrete_pmf(&samples_rd, 20);
852 let trace_rd = Scatter::new(xs, bins_rd)
853 .name("Geometric(0.25) - rand_distr")
854 .mode(Mode::Lines)
855 .line(
856 Line::new()
857 .shape(LineShape::Linear)
858 .dash(plotly::common::DashType::Dash),
859 )
860 .x_axis(&xa)
861 .y_axis(&ya);
862 plot.add_trace(trace_rd);
863 }
864
865 {
867 let (xa, ya) = subplot_axes(4, 1);
868 let mut rng = rand::rng();
869 let dist = SimdHypergeometric::new(20, 5, 6);
871 let samples: Vec<u32> = (0..sample_size).map(|_| dist.sample(&mut rng)).collect();
872 let (xs, bins) = make_discrete_pmf(&samples, 6);
873
874 let trace = Scatter::new(xs.clone(), bins)
875 .name("HyperGeo(20,5,6) - SIMD")
876 .mode(Mode::Lines)
877 .line(Line::new().shape(LineShape::Linear))
878 .x_axis(&xa)
879 .y_axis(&ya);
880 plot.add_trace(trace);
881
882 let mut rng = rand::rng();
884 let rd = rand_distr::Hypergeometric::new(20, 5, 6).unwrap();
885 let samples_rd: Vec<u32> = (0..sample_size)
886 .map(|_| rd.sample(&mut rng) as u32)
887 .collect();
888 let (_, bins_rd) = make_discrete_pmf(&samples_rd, 6);
889 let trace_rd = Scatter::new(xs, bins_rd)
890 .name("HyperGeo(20,5,6) - rand_distr")
891 .mode(Mode::Lines)
892 .line(
893 Line::new()
894 .shape(LineShape::Linear)
895 .dash(plotly::common::DashType::Dash),
896 )
897 .x_axis(&xa)
898 .y_axis(&ya);
899 plot.add_trace(trace_rd);
900 }
901
902 {
904 let (xa, ya) = subplot_axes(4, 2);
905 let mut rng = rand::rng();
906 let dist = SimdPoisson::new(4.0);
907 let samples: Vec<u32> = (0..sample_size).map(|_| dist.sample(&mut rng)).collect();
908 let (xs, bins) = make_discrete_pmf(&samples, 15);
909
910 let trace = Scatter::new(xs.clone(), bins)
911 .name("Poisson(4) - SIMD")
912 .mode(Mode::Lines)
913 .line(Line::new().shape(LineShape::Linear))
914 .x_axis(&xa)
915 .y_axis(&ya);
916 plot.add_trace(trace);
917
918 let mut rng = rand::rng();
920 let rd = rand_distr::Poisson::<f64>::new(4.0).unwrap();
921 let samples_rd: Vec<u32> = (0..sample_size)
922 .map(|_| rd.sample(&mut rng) as u32)
923 .collect();
924 let (_, bins_rd) = make_discrete_pmf(&samples_rd, 15);
925 let trace_rd = Scatter::new(xs, bins_rd)
926 .name("Poisson(4) - rand_distr")
927 .mode(Mode::Lines)
928 .line(
929 Line::new()
930 .shape(LineShape::Linear)
931 .dash(plotly::common::DashType::Dash),
932 )
933 .x_axis(&xa)
934 .y_axis(&ya);
935 plot.add_trace(trace_rd);
936 }
937
938 {
940 let (xa, ya) = subplot_axes(4, 3);
941 let mut rng = rand::rng();
942 let dist = SimdUniform::new(0.0, 1.0);
943 let samples: Vec<f32> = (0..sample_size).map(|_| dist.sample(&mut rng)).collect();
944 let (xs, bins) = make_histogram(&samples, 100, 0.0, 1.0);
945 let trace = Scatter::new(xs.clone(), bins)
946 .name("Uniform(0,1) - SIMD")
947 .mode(Mode::Lines)
948 .line(Line::new().shape(LineShape::Linear))
949 .x_axis(&xa)
950 .y_axis(&ya);
951 plot.add_trace(trace);
952
953 let mut rng = rand::rng();
955 let rd = rand_distr::Uniform::<f32>::new(0.0, 1.0).unwrap();
956 let samples_rd: Vec<f32> = (0..sample_size).map(|_| rd.sample(&mut rng)).collect();
957 let (_, bins_rd) = make_histogram(&samples_rd, 100, 0.0, 1.0);
958 let trace_rd = Scatter::new(xs, bins_rd)
959 .name("Uniform(0,1) - rand_distr")
960 .mode(Mode::Lines)
961 .line(
962 Line::new()
963 .shape(LineShape::Linear)
964 .dash(plotly::common::DashType::Dash),
965 )
966 .x_axis(&xa)
967 .y_axis(&ya);
968 plot.add_trace(trace_rd);
969 }
970
971 {
973 let (xa, ya) = subplot_axes(4, 4);
974 let mut rng = rand::rng();
975 let dist = SimdExp::new(1.5);
976 let samples: Vec<f32> = (0..sample_size).map(|_| dist.sample(&mut rng)).collect();
977 let (xs, bins) = make_histogram(&samples, 100, 0.0, 4.0);
978 let trace = Scatter::new(xs.clone(), bins)
979 .name("Exp(1.5) - SIMD")
980 .mode(Mode::Lines)
981 .line(Line::new().shape(LineShape::Linear))
982 .x_axis(&xa)
983 .y_axis(&ya);
984 plot.add_trace(trace);
985
986 let mut rng = rand::rng();
988 let rd = rand_distr::Exp::<f32>::new(1.5).unwrap();
989 let samples_rd: Vec<f32> = (0..sample_size).map(|_| rd.sample(&mut rng)).collect();
990 let (_, bins_rd) = make_histogram(&samples_rd, 100, 0.0, 4.0);
991 let trace_rd = Scatter::new(xs, bins_rd)
992 .name("Exp(1.5) - rand_distr")
993 .mode(Mode::Lines)
994 .line(
995 Line::new()
996 .shape(LineShape::Linear)
997 .dash(plotly::common::DashType::Dash),
998 )
999 .x_axis(&xa)
1000 .y_axis(&ya);
1001 plot.add_trace(trace_rd);
1002 }
1003
1004 {
1006 let (xa, ya) = subplot_axes(5, 1);
1007 let mut rng = rand::rng();
1008 let dist = SimdChiSquared::new(5.0);
1009 let samples: Vec<f32> = (0..sample_size).map(|_| dist.sample(&mut rng)).collect();
1010 let (xs, bins) = make_histogram(&samples, 100, 0.0, 20.0);
1011 let trace = Scatter::new(xs.clone(), bins)
1012 .name("ChiSquared(5) - SIMD")
1013 .mode(Mode::Lines)
1014 .line(Line::new().shape(LineShape::Linear))
1015 .x_axis(&xa)
1016 .y_axis(&ya);
1017 plot.add_trace(trace);
1018
1019 let mut rng = rand::rng();
1021 let rd = rand_distr::ChiSquared::<f32>::new(5.0).unwrap();
1022 let samples_rd: Vec<f32> = (0..sample_size).map(|_| rd.sample(&mut rng)).collect();
1023 let (_, bins_rd) = make_histogram(&samples_rd, 100, 0.0, 20.0);
1024 let trace_rd = Scatter::new(xs, bins_rd)
1025 .name("ChiSquared(5) - rand_distr")
1026 .mode(Mode::Lines)
1027 .line(
1028 Line::new()
1029 .shape(LineShape::Linear)
1030 .dash(plotly::common::DashType::Dash),
1031 )
1032 .x_axis(&xa)
1033 .y_axis(&ya);
1034 plot.add_trace(trace_rd);
1035 }
1036
1037 plot.show();
1038 }
1039
1040 use std::time::Instant;
1041
1042 #[test]
1043 #[ignore = "perf benchmark (5-10M sample loop): run with --ignored or via cargo bench"]
1044 fn bench_normal_simd_vs_rand() {
1045 let n = 10_000_000usize;
1046 let warmup = 1_000_000usize;
1047
1048 {
1049 let mut rng = rand::rng();
1050 let d: SimdNormal<f32> = SimdNormal::new(0.0, 1.0);
1051 let rd = rand_distr::Normal::<f32>::new(0.0, 1.0).unwrap();
1052 let mut s = 0.0f32;
1053 for _ in 0..warmup {
1054 s += d.sample(&mut rng);
1055 s += rd.sample(&mut rng);
1056 }
1057 std::hint::black_box(s);
1058 }
1059
1060 let mut rng = rand::rng();
1061 let simd: SimdNormal<f32> = SimdNormal::new(0.0, 1.0);
1062 let mut s_sum = 0.0f32;
1063 let t0 = Instant::now();
1064 for _ in 0..n {
1065 s_sum += simd.sample(&mut rng);
1066 }
1067 let dt_s = t0.elapsed();
1068
1069 let mut rng = rand::rng();
1070 let rd = rand_distr::Normal::<f32>::new(0.0, 1.0).unwrap();
1071 let mut r_sum = 0.0f32;
1072 let t1 = Instant::now();
1073 for _ in 0..n {
1074 r_sum += rd.sample(&mut rng);
1075 }
1076 let dt_r = t1.elapsed();
1077
1078 println!(
1079 "Normal single: simd {:?}, sum={:.3} | rand_distr {:?}, sum={:.3}",
1080 dt_s, s_sum, dt_r, r_sum
1081 );
1082 assert!(!s_sum.is_nan() && !r_sum.is_nan());
1083 }
1084
1085 #[test]
1086 #[ignore = "perf benchmark (5-10M sample loop): run with --ignored or via cargo bench"]
1087 fn bench_lognormal_simd_vs_rand() {
1088 let n = 10_000_000usize;
1089 let warmup = 1_000_000usize;
1090
1091 {
1092 let mut rng = rand::rng();
1093 let d = SimdLogNormal::new(0.2f32, 0.8);
1094 let rd = rand_distr::LogNormal::<f32>::new(0.2, 0.8).unwrap();
1095 let mut s = 0.0f32;
1096 for _ in 0..warmup {
1097 s += d.sample(&mut rng);
1098 s += rd.sample(&mut rng);
1099 }
1100 std::hint::black_box(s);
1101 }
1102
1103 let mut rng = rand::rng();
1104 let simd = SimdLogNormal::new(0.2, 0.8);
1105 let mut s_sum = 0.0f32;
1106 let t0 = Instant::now();
1107 for _ in 0..n {
1108 s_sum += simd.sample(&mut rng);
1109 }
1110 let dt_s = t0.elapsed();
1111
1112 let mut rng = rand::rng();
1113 let rd = rand_distr::LogNormal::<f32>::new(0.2, 0.8).unwrap();
1114 let mut r_sum = 0.0f32;
1115 let t1 = Instant::now();
1116 for _ in 0..n {
1117 r_sum += rd.sample(&mut rng);
1118 }
1119 let dt_r = t1.elapsed();
1120
1121 println!(
1122 "LogNormal single: simd {:?}, sum={:.3} | rand_distr {:?}, sum={:.3}",
1123 dt_s, s_sum, dt_r, r_sum
1124 );
1125 assert!(!s_sum.is_nan() && !r_sum.is_nan());
1126 }
1127
1128 #[test]
1129 #[ignore = "perf benchmark (5-10M sample loop): run with --ignored or via cargo bench"]
1130 fn bench_exp_simd_vs_rand() {
1131 let n = 10_000_000usize;
1132 let lambda = 1.5f32;
1133 let warmup = 1_000_000usize;
1134
1135 {
1136 let mut rng = rand::rng();
1137 let d = SimdExp::new(lambda);
1138 let rd = rand_distr::Exp::<f32>::new(lambda).unwrap();
1139 let mut s = 0.0f32;
1140 for _ in 0..warmup {
1141 s += d.sample(&mut rng);
1142 s += rd.sample(&mut rng);
1143 }
1144 std::hint::black_box(s);
1145 }
1146
1147 let mut rng = rand::rng();
1148 let simd = SimdExp::new(lambda);
1149 let mut s_sum = 0.0f32;
1150 let t0 = Instant::now();
1151 for _ in 0..n {
1152 s_sum += simd.sample(&mut rng);
1153 }
1154 let dt_s = t0.elapsed();
1155
1156 let mut rng = rand::rng();
1157 let rd = rand_distr::Exp::<f32>::new(lambda).unwrap();
1158 let mut r_sum = 0.0f32;
1159 let t1 = Instant::now();
1160 for _ in 0..n {
1161 r_sum += rd.sample(&mut rng);
1162 }
1163 let dt_r = t1.elapsed();
1164
1165 println!(
1166 "Exp single: simd {:?}, sum={:.3} | rand_distr {:?}, sum={:.3}",
1167 dt_s, s_sum, dt_r, r_sum
1168 );
1169 assert!(!s_sum.is_nan() && !r_sum.is_nan());
1170 }
1171
1172 #[test]
1173 #[ignore = "perf benchmark (5-10M sample loop): run with --ignored or via cargo bench"]
1174 fn bench_exp_zig_simd_vs_rand() {
1175 let n = 10_000_000usize;
1176 let lambda = 1.5f32;
1177 let warmup = 1_000_000usize;
1178
1179 {
1180 let mut rng = rand::rng();
1181 let d: SimdExpZig<f32> = SimdExpZig::new(lambda);
1182 let d2 = SimdExp::new(lambda);
1183 let rd = rand_distr::Exp::<f32>::new(lambda).unwrap();
1184 let mut s = 0.0f32;
1185 for _ in 0..warmup {
1186 s += d.sample(&mut rng);
1187 s += d2.sample(&mut rng);
1188 s += rd.sample(&mut rng);
1189 }
1190 std::hint::black_box(s);
1191 }
1192
1193 let mut rng = rand::rng();
1194 let zig: SimdExpZig<f32> = SimdExpZig::new(lambda);
1195 let mut z_sum = 0.0f32;
1196 let t0 = Instant::now();
1197 for _ in 0..n {
1198 z_sum += zig.sample(&mut rng);
1199 }
1200 let dt_z = t0.elapsed();
1201
1202 let mut rng = rand::rng();
1203 let old = SimdExp::new(lambda);
1204 let mut o_sum = 0.0f32;
1205 let t1 = Instant::now();
1206 for _ in 0..n {
1207 o_sum += old.sample(&mut rng);
1208 }
1209 let dt_o = t1.elapsed();
1210
1211 let mut rng = rand::rng();
1212 let rd = rand_distr::Exp::<f32>::new(lambda).unwrap();
1213 let mut r_sum = 0.0f32;
1214 let t2 = Instant::now();
1215 for _ in 0..n {
1216 r_sum += rd.sample(&mut rng);
1217 }
1218 let dt_r = t2.elapsed();
1219
1220 println!(
1221 "Exp Ziggurat: {:?}, sum={:.3} | Exp ICDF: {:?}, sum={:.3} | rand_distr: {:?}, sum={:.3}",
1222 dt_z, z_sum, dt_o, o_sum, dt_r, r_sum
1223 );
1224 assert!(!z_sum.is_nan() && !o_sum.is_nan() && !r_sum.is_nan());
1225 }
1226
1227 #[test]
1228 #[ignore = "perf benchmark (5-10M sample loop): run with --ignored or via cargo bench"]
1229 fn bench_cauchy_simd_vs_rand() {
1230 let n = 10_000_000usize;
1231 let warmup = 1_000_000usize;
1232
1233 {
1234 let mut rng = rand::rng();
1235 let d = SimdCauchy::new(0.0f32, 1.0);
1236 let rd = rand_distr::Cauchy::<f32>::new(0.0, 1.0).unwrap();
1237 let mut s = 0.0f32;
1238 for _ in 0..warmup {
1239 s += d.sample(&mut rng);
1240 s += rd.sample(&mut rng);
1241 }
1242 std::hint::black_box(s);
1243 }
1244
1245 let mut rng = rand::rng();
1246 let simd = SimdCauchy::new(0.0, 1.0);
1247 let mut s_sum = 0.0f32;
1248 let t0 = Instant::now();
1249 for _ in 0..n {
1250 s_sum += simd.sample(&mut rng);
1251 }
1252 let dt_s = t0.elapsed();
1253
1254 let mut rng = rand::rng();
1255 let rd = rand_distr::Cauchy::<f32>::new(0.0, 1.0).unwrap();
1256 let mut r_sum = 0.0f32;
1257 let t1 = Instant::now();
1258 for _ in 0..n {
1259 r_sum += rd.sample(&mut rng);
1260 }
1261 let dt_r = t1.elapsed();
1262
1263 println!(
1264 "Cauchy single: simd {:?}, sum={:.3} | rand_distr {:?}, sum={:.3}",
1265 dt_s, s_sum, dt_r, r_sum
1266 );
1267 assert!(!s_sum.is_nan() && !r_sum.is_nan());
1268 }
1269
1270 #[test]
1271 #[ignore = "perf benchmark (5-10M sample loop): run with --ignored or via cargo bench"]
1272 fn bench_gamma_simd_vs_rand() {
1273 let n = 10_000_000usize;
1274 let warmup = 1_000_000usize;
1275
1276 {
1277 let mut rng = rand::rng();
1278 let d = SimdGamma::new(2.0f32, 2.0);
1279 let rd = rand_distr::Gamma::<f32>::new(2.0, 2.0).unwrap();
1280 let mut s = 0.0f32;
1281 for _ in 0..warmup {
1282 s += d.sample(&mut rng);
1283 s += rd.sample(&mut rng);
1284 }
1285 std::hint::black_box(s);
1286 }
1287
1288 let mut rng = rand::rng();
1289 let simd = SimdGamma::new(2.0, 2.0);
1290 let mut s_sum = 0.0f32;
1291 let t0 = Instant::now();
1292 for _ in 0..n {
1293 s_sum += simd.sample(&mut rng);
1294 }
1295 let dt_s = t0.elapsed();
1296
1297 let mut rng = rand::rng();
1298 let rd = rand_distr::Gamma::<f32>::new(2.0, 2.0).unwrap();
1299 let mut r_sum = 0.0f32;
1300 let t1 = Instant::now();
1301 for _ in 0..n {
1302 r_sum += rd.sample(&mut rng);
1303 }
1304 let dt_r = t1.elapsed();
1305
1306 println!(
1307 "Gamma single: simd {:?}, sum={:.3} | rand_distr {:?}, sum={:.3}",
1308 dt_s, s_sum, dt_r, r_sum
1309 );
1310 assert!(!s_sum.is_nan() && !r_sum.is_nan());
1311 }
1312
1313 #[test]
1314 #[ignore = "perf benchmark (5-10M sample loop): run with --ignored or via cargo bench"]
1315 fn bench_weibull_simd_vs_rand() {
1316 let n = 10_000_000usize;
1317 let warmup = 1_000_000usize;
1318
1319 {
1320 let mut rng = rand::rng();
1321 let d = SimdWeibull::new(1.0f32, 1.5);
1322 let rd = rand_distr::Weibull::<f32>::new(1.0, 1.5).unwrap();
1323 let mut s = 0.0f32;
1324 for _ in 0..warmup {
1325 s += d.sample(&mut rng);
1326 s += rd.sample(&mut rng);
1327 }
1328 std::hint::black_box(s);
1329 }
1330
1331 let mut rng = rand::rng();
1332 let simd = SimdWeibull::new(1.0, 1.5);
1333 let mut s_sum = 0.0f32;
1334 let t0 = Instant::now();
1335 for _ in 0..n {
1336 s_sum += simd.sample(&mut rng);
1337 }
1338 let dt_s = t0.elapsed();
1339
1340 let mut rng = rand::rng();
1341 let rd = rand_distr::Weibull::<f32>::new(1.0, 1.5).unwrap();
1342 let mut r_sum = 0.0f32;
1343 let t1 = Instant::now();
1344 for _ in 0..n {
1345 r_sum += rd.sample(&mut rng);
1346 }
1347 let dt_r = t1.elapsed();
1348
1349 println!(
1350 "Weibull single: simd {:?}, sum={:.3} | rand_distr {:?}, sum={:.3}",
1351 dt_s, s_sum, dt_r, r_sum
1352 );
1353 assert!(!s_sum.is_nan() && !r_sum.is_nan());
1354 }
1355
1356 #[test]
1357 #[ignore = "perf benchmark (5-10M sample loop): run with --ignored or via cargo bench"]
1358 fn bench_beta_simd_vs_rand() {
1359 let n = 10_000_000usize;
1360 let warmup = 1_000_000usize;
1361
1362 {
1363 let mut rng = rand::rng();
1364 let d = SimdBeta::new(2.0f32, 2.0);
1365 let rd = rand_distr::Beta::<f32>::new(2.0, 2.0).unwrap();
1366 let mut s = 0.0f32;
1367 for _ in 0..warmup {
1368 s += d.sample(&mut rng);
1369 s += rd.sample(&mut rng);
1370 }
1371 std::hint::black_box(s);
1372 }
1373
1374 let mut rng = rand::rng();
1375 let simd = SimdBeta::new(2.0, 2.0);
1376 let mut s_sum = 0.0f32;
1377 let t0 = Instant::now();
1378 for _ in 0..n {
1379 s_sum += simd.sample(&mut rng);
1380 }
1381 let dt_s = t0.elapsed();
1382
1383 let mut rng = rand::rng();
1384 let rd = rand_distr::Beta::<f32>::new(2.0, 2.0).unwrap();
1385 let mut r_sum = 0.0f32;
1386 let t1 = Instant::now();
1387 for _ in 0..n {
1388 r_sum += rd.sample(&mut rng);
1389 }
1390 let dt_r = t1.elapsed();
1391
1392 println!(
1393 "Beta single: simd {:?}, sum={:.3} | rand_distr {:?}, sum={:.3}",
1394 dt_s, s_sum, dt_r, r_sum
1395 );
1396 assert!(!s_sum.is_nan() && !r_sum.is_nan());
1397 }
1398
1399 #[test]
1400 #[ignore = "perf benchmark (5-10M sample loop): run with --ignored or via cargo bench"]
1401 fn bench_chisq_simd_vs_rand() {
1402 let n = 10_000_000usize;
1403 let warmup = 1_000_000usize;
1404
1405 {
1406 let mut rng = rand::rng();
1407 let d = SimdChiSquared::new(5.0f32);
1408 let rd = rand_distr::ChiSquared::<f32>::new(5.0).unwrap();
1409 let mut s = 0.0f32;
1410 for _ in 0..warmup {
1411 s += d.sample(&mut rng);
1412 s += rd.sample(&mut rng);
1413 }
1414 std::hint::black_box(s);
1415 }
1416
1417 let mut rng = rand::rng();
1418 let simd = SimdChiSquared::new(5.0);
1419 let mut s_sum = 0.0f32;
1420 let t0 = Instant::now();
1421 for _ in 0..n {
1422 s_sum += simd.sample(&mut rng);
1423 }
1424 let dt_s = t0.elapsed();
1425
1426 let mut rng = rand::rng();
1427 let rd = rand_distr::ChiSquared::<f32>::new(5.0).unwrap();
1428 let mut r_sum = 0.0f32;
1429 let t1 = Instant::now();
1430 for _ in 0..n {
1431 r_sum += rd.sample(&mut rng);
1432 }
1433 let dt_r = t1.elapsed();
1434
1435 println!(
1436 "ChiSq single: simd {:?}, sum={:.3} | rand_distr {:?}, sum={:.3}",
1437 dt_s, s_sum, dt_r, r_sum
1438 );
1439 assert!(!s_sum.is_nan() && !r_sum.is_nan());
1440 }
1441
1442 #[test]
1443 #[ignore = "perf benchmark (5-10M sample loop): run with --ignored or via cargo bench"]
1444 fn bench_studentt_simd_vs_rand() {
1445 let n = 10_000_000usize;
1446 let warmup = 1_000_000usize;
1447
1448 {
1449 let mut rng = rand::rng();
1450 let d = SimdStudentT::new(5.0f32);
1451 let rd = rand_distr::StudentT::<f32>::new(5.0).unwrap();
1452 let mut s = 0.0f32;
1453 for _ in 0..warmup {
1454 s += d.sample(&mut rng);
1455 s += rd.sample(&mut rng);
1456 }
1457 std::hint::black_box(s);
1458 }
1459
1460 let mut rng = rand::rng();
1461 let simd = SimdStudentT::new(5.0);
1462 let mut s_sum = 0.0f32;
1463 let t0 = Instant::now();
1464 for _ in 0..n {
1465 s_sum += simd.sample(&mut rng);
1466 }
1467 let dt_s = t0.elapsed();
1468
1469 let mut rng = rand::rng();
1470 let rd = rand_distr::StudentT::<f32>::new(5.0).unwrap();
1471 let mut r_sum = 0.0f32;
1472 let t1 = Instant::now();
1473 for _ in 0..n {
1474 r_sum += rd.sample(&mut rng);
1475 }
1476 let dt_r = t1.elapsed();
1477
1478 println!(
1479 "StudentT single: simd {:?}, sum={:.3} | rand_distr {:?}, sum={:.3}",
1480 dt_s, s_sum, dt_r, r_sum
1481 );
1482 assert!(!s_sum.is_nan() && !r_sum.is_nan());
1483 }
1484
1485 #[test]
1486 #[ignore = "perf benchmark (5-10M sample loop): run with --ignored or via cargo bench"]
1487 fn bench_poisson_simd_vs_rand() {
1488 let n = 5_000_000usize;
1489 let warmup = 500_000usize;
1490
1491 {
1492 let mut rng = rand::rng();
1493 let d = SimdPoisson::<u32>::new(4.0);
1494 let rd = rand_distr::Poisson::<f64>::new(4.0).unwrap();
1495 let mut s: u64 = 0;
1496 for _ in 0..warmup {
1497 s += d.sample(&mut rng) as u64;
1498 s += rd.sample(&mut rng) as u64;
1499 }
1500 std::hint::black_box(s);
1501 }
1502
1503 let mut rng = rand::rng();
1504 let simd = SimdPoisson::<u32>::new(4.0);
1505 let mut s_sum: u64 = 0;
1506 let t0 = Instant::now();
1507 for _ in 0..n {
1508 s_sum += simd.sample(&mut rng) as u64;
1509 }
1510 let dt_s = t0.elapsed();
1511
1512 let mut rng = rand::rng();
1513 let rd = rand_distr::Poisson::<f64>::new(4.0).unwrap();
1514 let mut r_sum: u64 = 0;
1515 let t1 = Instant::now();
1516 for _ in 0..n {
1517 r_sum += rd.sample(&mut rng) as u64;
1518 }
1519 let dt_r = t1.elapsed();
1520
1521 println!(
1522 "Poisson single: simd {:?}, sum={} | rand_distr {:?}, sum={}",
1523 dt_s, s_sum, dt_r, r_sum
1524 );
1525 assert!(s_sum > 0 && r_sum > 0);
1526 }
1527
1528 struct Row {
1530 name: &'static str,
1531 simd_ms: f64,
1532 rand_ms: f64,
1533 }
1534 fn time_f32<F1, F2>(
1535 rows: &mut Vec<Row>,
1536 n: usize,
1537 name: &'static str,
1538 mut simd_fn: F1,
1539 mut rand_fn: F2,
1540 ) where
1541 F1: FnMut() -> f32,
1542 F2: FnMut() -> f32,
1543 {
1544 use std::hint::black_box;
1545 let warmup = n / 5;
1546 let mut w = 0.0f32;
1547 for _ in 0..warmup {
1548 w += simd_fn();
1549 w += rand_fn();
1550 }
1551 black_box(w);
1552
1553 let t0 = Instant::now();
1554 let mut s_sum = 0.0f32;
1555 for _ in 0..n {
1556 s_sum += simd_fn();
1557 }
1558 let dt_simd = t0.elapsed().as_secs_f64() * 1000.0;
1559
1560 let t1 = Instant::now();
1561 let mut r_sum = 0.0f32;
1562 for _ in 0..n {
1563 r_sum += rand_fn();
1564 }
1565 let dt_rand = t1.elapsed().as_secs_f64() * 1000.0;
1566
1567 black_box(s_sum);
1568 black_box(r_sum);
1569 rows.push(Row {
1570 name,
1571 simd_ms: dt_simd,
1572 rand_ms: dt_rand,
1573 });
1574 }
1575 fn time_u32<F1, F2>(
1576 rows: &mut Vec<Row>,
1577 n: usize,
1578 name: &'static str,
1579 mut simd_fn: F1,
1580 mut rand_fn: F2,
1581 ) where
1582 F1: FnMut() -> u32,
1583 F2: FnMut() -> u32,
1584 {
1585 use std::hint::black_box;
1586 let warmup = n / 5;
1587 let mut w: u64 = 0;
1588 for _ in 0..warmup {
1589 w += simd_fn() as u64;
1590 w += rand_fn() as u64;
1591 }
1592 black_box(w);
1593
1594 let t0 = Instant::now();
1595 let mut s_sum: u64 = 0;
1596 for _ in 0..n {
1597 s_sum += simd_fn() as u64;
1598 }
1599 let dt_simd = t0.elapsed().as_secs_f64() * 1000.0;
1600
1601 let t1 = Instant::now();
1602 let mut r_sum: u64 = 0;
1603 for _ in 0..n {
1604 r_sum += rand_fn() as u64;
1605 }
1606 let dt_rand = t1.elapsed().as_secs_f64() * 1000.0;
1607
1608 black_box(s_sum);
1609 black_box(r_sum);
1610 rows.push(Row {
1611 name,
1612 simd_ms: dt_simd,
1613 rand_ms: dt_rand,
1614 });
1615 }
1616
1617 #[test]
1618 #[ignore = "perf benchmark summary (~119s): run with --ignored or via cargo bench"]
1619 fn bench_summary_table() {
1620 let n_f = 5_000_000usize; let n_i = 5_000_000usize; let mut rows: Vec<Row> = Vec::new();
1624
1625 {
1627 let mut rng = rand::rng();
1628 let simd: SimdNormal<f32> = SimdNormal::new(0.0, 1.0);
1629 let mut rng2 = rand::rng();
1630 let rd = rand_distr::Normal::<f32>::new(0.0, 1.0).unwrap();
1631 time_f32(
1632 &mut rows,
1633 n_f,
1634 "Normal",
1635 || simd.sample(&mut rng),
1636 || rd.sample(&mut rng2),
1637 );
1638 }
1639
1640 {
1642 let mut rng = rand::rng();
1643 let simd = SimdLogNormal::new(0.2, 0.8);
1644 let mut rng2 = rand::rng();
1645 let rd = rand_distr::LogNormal::<f32>::new(0.2, 0.8).unwrap();
1646 time_f32(
1647 &mut rows,
1648 n_f,
1649 "LogNormal",
1650 || simd.sample(&mut rng),
1651 || rd.sample(&mut rng2),
1652 );
1653 }
1654
1655 {
1657 let mut rng = rand::rng();
1658 let simd = SimdExp::new(1.5);
1659 let mut rng2 = rand::rng();
1660 let rd = rand_distr::Exp::<f32>::new(1.5).unwrap();
1661 time_f32(
1662 &mut rows,
1663 n_f,
1664 "Exp",
1665 || simd.sample(&mut rng),
1666 || rd.sample(&mut rng2),
1667 );
1668 }
1669
1670 {
1672 let mut rng = rand::rng();
1673 let simd = SimdCauchy::new(0.0, 1.0);
1674 let mut rng2 = rand::rng();
1675 let rd = rand_distr::Cauchy::<f32>::new(0.0, 1.0).unwrap();
1676 time_f32(
1677 &mut rows,
1678 n_f,
1679 "Cauchy",
1680 || simd.sample(&mut rng),
1681 || rd.sample(&mut rng2),
1682 );
1683 }
1684
1685 {
1687 let mut rng = rand::rng();
1688 let simd = SimdGamma::new(2.0, 2.0);
1689 let mut rng2 = rand::rng();
1690 let rd = rand_distr::Gamma::<f32>::new(2.0, 2.0).unwrap();
1691 time_f32(
1692 &mut rows,
1693 n_f,
1694 "Gamma",
1695 || simd.sample(&mut rng),
1696 || rd.sample(&mut rng2),
1697 );
1698 }
1699
1700 {
1702 let mut rng = rand::rng();
1703 let simd = SimdWeibull::new(1.0, 1.5);
1704 let mut rng2 = rand::rng();
1705 let rd = rand_distr::Weibull::<f32>::new(1.0, 1.5).unwrap();
1706 time_f32(
1707 &mut rows,
1708 n_f,
1709 "Weibull",
1710 || simd.sample(&mut rng),
1711 || rd.sample(&mut rng2),
1712 );
1713 }
1714
1715 {
1717 let mut rng = rand::rng();
1718 let simd = SimdBeta::new(2.0, 2.0);
1719 let mut rng2 = rand::rng();
1720 let rd = rand_distr::Beta::<f32>::new(2.0, 2.0).unwrap();
1721 time_f32(
1722 &mut rows,
1723 n_f,
1724 "Beta",
1725 || simd.sample(&mut rng),
1726 || rd.sample(&mut rng2),
1727 );
1728 }
1729
1730 {
1732 let mut rng = rand::rng();
1733 let simd = SimdChiSquared::new(5.0);
1734 let mut rng2 = rand::rng();
1735 let rd = rand_distr::ChiSquared::<f32>::new(5.0).unwrap();
1736 time_f32(
1737 &mut rows,
1738 n_f,
1739 "ChiSquared",
1740 || simd.sample(&mut rng),
1741 || rd.sample(&mut rng2),
1742 );
1743 }
1744
1745 {
1747 let mut rng = rand::rng();
1748 let simd = SimdStudentT::new(5.0);
1749 let mut rng2 = rand::rng();
1750 let rd = rand_distr::StudentT::<f32>::new(5.0).unwrap();
1751 time_f32(
1752 &mut rows,
1753 n_f,
1754 "StudentT",
1755 || simd.sample(&mut rng),
1756 || rd.sample(&mut rng2),
1757 );
1758 }
1759
1760 {
1762 let mut rng = rand::rng();
1763 let simd = SimdPoisson::new(4.0);
1764 let mut rng2 = rand::rng();
1765 let rd = rand_distr::Poisson::<f64>::new(4.0).unwrap();
1766 time_u32(
1767 &mut rows,
1768 n_i,
1769 "Poisson",
1770 || simd.sample(&mut rng),
1771 || rd.sample(&mut rng2) as u32,
1772 );
1773 }
1774
1775 #[allow(unused)]
1777 {
1778 let _ = SimdPareto::new(1.0, 1.5);
1780 }
1781
1782 println!(
1784 "{:<14} {:>12} {:>14}",
1785 "Distribution", "simd (ms)", "rand_distr (ms)"
1786 );
1787 println!("{:-<14} {:-<12} {:-<14}", "", "", "");
1788 for r in &rows {
1789 println!("{:<14} {:>12.2} {:>14.2}", r.name, r.simd_ms, r.rand_ms);
1790 }
1791
1792 println!();
1794 println!(
1795 "{:<24} {:>12} {:>14} {:>8}",
1796 "Normal fill_slice", "simd (ms)", "rand_distr (ms)", "speedup"
1797 );
1798 println!("{:-<24} {:-<12} {:-<14} {:-<8}", "", "", "", "");
1799 let total = 5_000_000usize;
1800 for &size in &[8, 16, 64, 256, 1024, 10_000, 100_000] {
1801 let iters = total / size;
1802 let simd = SimdNormal::<f32>::new(0.0, 1.0);
1803 let rd = rand_distr::Normal::<f32>::new(0.0, 1.0).unwrap();
1804 let mut buf = vec![0.0f32; size];
1805
1806 let mut rng = rand::rng();
1807 let t0 = Instant::now();
1808 for _ in 0..iters {
1809 simd.fill_slice(&mut rng, &mut buf);
1810 std::hint::black_box(&buf);
1811 }
1812 let dt_simd = t0.elapsed().as_secs_f64() * 1000.0;
1813
1814 let mut rng2 = rand::rng();
1815 let t1 = Instant::now();
1816 for _ in 0..iters {
1817 for x in buf.iter_mut() {
1818 *x = rd.sample(&mut rng2);
1819 }
1820 std::hint::black_box(&buf);
1821 }
1822 let dt_rand = t1.elapsed().as_secs_f64() * 1000.0;
1823
1824 let speedup = dt_rand / dt_simd;
1825 println!(
1826 " n={:<20} {:>10.2} {:>14.2} {:>7.2}x",
1827 size, dt_simd, dt_rand, speedup
1828 );
1829 }
1830 }
1831}