1use ferray_core::{Array, FerrayError, IxDyn};
7
8use crate::bitgen::BitGenerator;
9use crate::distributions::exponential::standard_exponential_single;
10use crate::distributions::normal::standard_normal_single;
11use crate::generator::{Generator, generate_vec, shape_size, vec_to_array_f64};
12use crate::shape::IntoShape;
13
14impl<B: BitGenerator> Generator<B> {
15 pub fn laplace(
27 &mut self,
28 loc: f64,
29 scale: f64,
30 size: impl IntoShape,
31 ) -> Result<Array<f64, IxDyn>, FerrayError> {
32 if scale <= 0.0 {
33 return Err(FerrayError::invalid_value(format!(
34 "scale must be positive, got {scale}"
35 )));
36 }
37 let shape_vec = size.into_shape()?;
38 let n = shape_size(&shape_vec);
39 let data = generate_vec(self, n, |bg| {
40 let mut u = bg.next_f64() - 0.5;
41 while u.abs() >= 0.5 {
43 u = bg.next_f64() - 0.5;
44 }
45 (scale * u.signum()).mul_add(-2.0f64.mul_add(-u.abs(), 1.0).ln(), loc)
46 });
47 vec_to_array_f64(data, &shape_vec)
48 }
49
50 pub fn logistic(
62 &mut self,
63 loc: f64,
64 scale: f64,
65 size: impl IntoShape,
66 ) -> Result<Array<f64, IxDyn>, FerrayError> {
67 if scale <= 0.0 {
68 return Err(FerrayError::invalid_value(format!(
69 "scale must be positive, got {scale}"
70 )));
71 }
72 let shape_vec = size.into_shape()?;
73 let n = shape_size(&shape_vec);
74 let data = generate_vec(self, n, |bg| {
75 loop {
76 let u = bg.next_f64();
77 if u > f64::EPSILON && u < 1.0 - f64::EPSILON {
78 return scale.mul_add((u / (1.0 - u)).ln(), loc);
79 }
80 }
81 });
82 vec_to_array_f64(data, &shape_vec)
83 }
84
85 pub fn rayleigh(
96 &mut self,
97 scale: f64,
98 size: impl IntoShape,
99 ) -> Result<Array<f64, IxDyn>, FerrayError> {
100 if scale <= 0.0 {
101 return Err(FerrayError::invalid_value(format!(
102 "scale must be positive, got {scale}"
103 )));
104 }
105 let shape_vec = size.into_shape()?;
106 let n = shape_size(&shape_vec);
107 let data = generate_vec(self, n, |bg| {
108 scale * (2.0 * standard_exponential_single(bg)).sqrt()
109 });
110 vec_to_array_f64(data, &shape_vec)
111 }
112
113 pub fn weibull(
124 &mut self,
125 a: f64,
126 size: impl IntoShape,
127 ) -> Result<Array<f64, IxDyn>, FerrayError> {
128 if a <= 0.0 {
129 return Err(FerrayError::invalid_value(format!(
130 "a must be positive, got {a}"
131 )));
132 }
133 let shape_vec = size.into_shape()?;
134 let n = shape_size(&shape_vec);
135 let data = generate_vec(self, n, |bg| standard_exponential_single(bg).powf(1.0 / a));
136 vec_to_array_f64(data, &shape_vec)
137 }
138
139 pub fn pareto(
151 &mut self,
152 a: f64,
153 size: impl IntoShape,
154 ) -> Result<Array<f64, IxDyn>, FerrayError> {
155 if a <= 0.0 {
156 return Err(FerrayError::invalid_value(format!(
157 "a must be positive, got {a}"
158 )));
159 }
160 let shape_vec = size.into_shape()?;
161 let n = shape_size(&shape_vec);
162 let data = generate_vec(self, n, |bg| {
163 let e = standard_exponential_single(bg);
164 (e / a).exp_m1()
165 });
166 vec_to_array_f64(data, &shape_vec)
167 }
168
169 pub fn gumbel(
181 &mut self,
182 loc: f64,
183 scale: f64,
184 size: impl IntoShape,
185 ) -> Result<Array<f64, IxDyn>, FerrayError> {
186 if scale <= 0.0 {
187 return Err(FerrayError::invalid_value(format!(
188 "scale must be positive, got {scale}"
189 )));
190 }
191 let shape_vec = size.into_shape()?;
192 let n = shape_size(&shape_vec);
193 let data = generate_vec(self, n, |bg| {
194 loop {
195 let u = bg.next_f64();
196 if u > f64::EPSILON && u < 1.0 - f64::EPSILON {
197 return scale.mul_add(-(-u.ln()).ln(), loc);
198 }
199 }
200 });
201 vec_to_array_f64(data, &shape_vec)
202 }
203
204 pub fn power(
216 &mut self,
217 a: f64,
218 size: impl IntoShape,
219 ) -> Result<Array<f64, IxDyn>, FerrayError> {
220 if a <= 0.0 {
221 return Err(FerrayError::invalid_value(format!(
222 "a must be positive, got {a}"
223 )));
224 }
225 let shape_vec = size.into_shape()?;
226 let n = shape_size(&shape_vec);
227 let data = generate_vec(self, n, |bg| {
228 let e = standard_exponential_single(bg);
229 (1.0 - (-e).exp()).powf(1.0 / a)
233 });
234 vec_to_array_f64(data, &shape_vec)
235 }
236
237 pub fn triangular(
248 &mut self,
249 left: f64,
250 mode: f64,
251 right: f64,
252 size: impl IntoShape,
253 ) -> Result<Array<f64, IxDyn>, FerrayError> {
254 if left >= right {
255 return Err(FerrayError::invalid_value(format!(
256 "left ({left}) must be less than right ({right})"
257 )));
258 }
259 if mode < left || mode > right {
260 return Err(FerrayError::invalid_value(format!(
261 "mode ({mode}) must be in [{left}, {right}]"
262 )));
263 }
264 let fc = (mode - left) / (right - left);
265 let shape_vec = size.into_shape()?;
266 let n = shape_size(&shape_vec);
267 let data = generate_vec(self, n, |bg| {
268 let u = bg.next_f64();
269 if u < fc {
270 left + ((right - left) * (mode - left) * u).sqrt()
271 } else {
272 right - ((right - left) * (right - mode) * (1.0 - u)).sqrt()
273 }
274 });
275 vec_to_array_f64(data, &shape_vec)
276 }
277
278 pub fn vonmises(
291 &mut self,
292 mu: f64,
293 kappa: f64,
294 size: impl IntoShape,
295 ) -> Result<Array<f64, IxDyn>, FerrayError> {
296 if kappa < 0.0 {
297 return Err(FerrayError::invalid_value(format!(
298 "kappa must be non-negative, got {kappa}"
299 )));
300 }
301 let shape_vec = size.into_shape()?;
302 let n = shape_size(&shape_vec);
303
304 if kappa < 1e-6 {
305 let data = generate_vec(self, n, |bg| {
307 mu + bg
308 .next_f64()
309 .mul_add(std::f64::consts::TAU, -std::f64::consts::PI)
310 });
311 return vec_to_array_f64(data, &shape_vec);
312 }
313
314 let tau = 1.0 + (4.0 * kappa).mul_add(kappa, 1.0).sqrt();
316 let rho = (tau - (2.0 * tau).sqrt()) / (2.0 * kappa);
317 let r = (1.0 + rho * rho) / (2.0 * rho);
318
319 let data = generate_vec(self, n, |bg| {
320 loop {
321 let u1 = bg.next_f64();
322 let z = std::f64::consts::TAU
323 .mul_add(u1, -std::f64::consts::PI)
324 .cos();
325 let f_val = (1.0 + r * z) / (r + z);
326 let c = kappa * (r - f_val);
327 let u2 = bg.next_f64();
328 if u2 < c * (2.0 - c) || u2 <= c * (-c).exp() {
329 let u3 = bg.next_f64();
330 let theta = if u3 > 0.5 {
331 mu + f_val.acos()
332 } else {
333 mu - f_val.acos()
334 };
335 return theta;
336 }
337 }
338 });
339 vec_to_array_f64(data, &shape_vec)
340 }
341
342 pub fn wald(
352 &mut self,
353 mean: f64,
354 scale: f64,
355 size: impl IntoShape,
356 ) -> Result<Array<f64, IxDyn>, FerrayError> {
357 if mean <= 0.0 {
358 return Err(FerrayError::invalid_value(format!(
359 "mean must be positive, got {mean}"
360 )));
361 }
362 if scale <= 0.0 {
363 return Err(FerrayError::invalid_value(format!(
364 "scale must be positive, got {scale}"
365 )));
366 }
367 let shape_vec = size.into_shape()?;
369 let n = shape_size(&shape_vec);
370 let data = generate_vec(self, n, |bg| {
371 let z = standard_normal_single(bg);
372 let v = z * z;
373 let mu = mean;
374 let lam = scale;
375 let x = (mu / (2.0 * lam)).mul_add(
376 -(4.0 * mu * lam).mul_add(v, mu * mu * v * v).sqrt(),
377 mu + (mu * mu * v) / (2.0 * lam),
378 );
379 let u = bg.next_f64();
380 if u <= mu / (mu + x) { x } else { mu * mu / x }
381 });
382 vec_to_array_f64(data, &shape_vec)
383 }
384
385 pub fn standard_cauchy(
395 &mut self,
396 size: impl IntoShape,
397 ) -> Result<Array<f64, IxDyn>, FerrayError> {
398 let shape_vec = size.into_shape()?;
399 let n = shape_size(&shape_vec);
400 let data = generate_vec(self, n, |bg| {
401 loop {
402 let u = bg.next_f64();
403 if (u - 0.5).abs() > f64::EPSILON {
405 return (std::f64::consts::PI * (u - 0.5)).tan();
406 }
407 }
408 });
409 vec_to_array_f64(data, &shape_vec)
410 }
411}
412
413#[cfg(test)]
414#[allow(clippy::unreadable_literal)] mod tests {
416 use crate::default_rng_seeded;
417
418 #[test]
419 fn laplace_mean_variance() {
420 let mut rng = default_rng_seeded(42);
421 let n = 100_000;
422 let loc = 2.0;
423 let scale = 3.0;
424 let arr = rng.laplace(loc, scale, n).unwrap();
425 let slice = arr.as_slice().unwrap();
426 let mean: f64 = slice.iter().sum::<f64>() / n as f64;
427 let var: f64 = slice.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / n as f64;
428 let expected_var = 2.0 * scale * scale;
430 let se = (expected_var / n as f64).sqrt();
431 assert!(
432 (mean - loc).abs() < 3.0 * se,
433 "laplace mean {mean} too far from {loc}"
434 );
435 assert!(
436 (var - expected_var).abs() / expected_var < 0.05,
437 "laplace variance {var} too far from {expected_var}"
438 );
439 }
440
441 #[test]
442 fn logistic_mean() {
443 let mut rng = default_rng_seeded(42);
444 let n = 100_000;
445 let loc = 1.0;
446 let scale = 2.0;
447 let arr = rng.logistic(loc, scale, n).unwrap();
448 let slice = arr.as_slice().unwrap();
449 let mean: f64 = slice.iter().sum::<f64>() / n as f64;
450 let expected_var = (std::f64::consts::PI * scale).powi(2) / 3.0;
451 let se = (expected_var / n as f64).sqrt();
452 assert!(
453 (mean - loc).abs() < 3.0 * se,
454 "logistic mean {mean} too far from {loc}"
455 );
456 }
457
458 #[test]
459 fn rayleigh_positive() {
460 let mut rng = default_rng_seeded(42);
461 let arr = rng.rayleigh(2.0, 10_000).unwrap();
462 for &v in arr.as_slice().unwrap() {
463 assert!(v > 0.0);
464 }
465 }
466
467 #[test]
468 fn rayleigh_mean() {
469 let mut rng = default_rng_seeded(42);
470 let n = 100_000;
471 let scale = 2.0;
472 let arr = rng.rayleigh(scale, n).unwrap();
473 let slice = arr.as_slice().unwrap();
474 let mean: f64 = slice.iter().sum::<f64>() / n as f64;
475 let expected_mean = scale * (std::f64::consts::FRAC_PI_2).sqrt();
477 let expected_var = (4.0 - std::f64::consts::PI) / 2.0 * scale * scale;
478 let se = (expected_var / n as f64).sqrt();
479 assert!(
480 (mean - expected_mean).abs() < 3.0 * se,
481 "rayleigh mean {mean} too far from {expected_mean}"
482 );
483 }
484
485 #[test]
486 fn weibull_positive() {
487 let mut rng = default_rng_seeded(42);
488 let arr = rng.weibull(1.5, 10_000).unwrap();
489 for &v in arr.as_slice().unwrap() {
490 assert!(v > 0.0);
491 }
492 }
493
494 #[test]
495 fn pareto_ge_zero() {
496 let mut rng = default_rng_seeded(42);
497 let arr = rng.pareto(3.0, 10_000).unwrap();
498 for &v in arr.as_slice().unwrap() {
499 assert!(v >= 0.0, "pareto value {v} < 0");
500 }
501 }
502
503 #[test]
504 fn gumbel_mean() {
505 let mut rng = default_rng_seeded(42);
506 let n = 100_000;
507 let loc = 1.0;
508 let scale = 2.0;
509 let arr = rng.gumbel(loc, scale, n).unwrap();
510 let slice = arr.as_slice().unwrap();
511 let mean: f64 = slice.iter().sum::<f64>() / n as f64;
512 let euler = 0.5772156649015329;
514 let expected_mean = loc + scale * euler;
515 let expected_var = std::f64::consts::PI * std::f64::consts::PI * scale * scale / 6.0;
516 let se = (expected_var / n as f64).sqrt();
517 assert!(
518 (mean - expected_mean).abs() < 3.0 * se,
519 "gumbel mean {mean} too far from {expected_mean}"
520 );
521 }
522
523 #[test]
524 fn power_in_range() {
525 let mut rng = default_rng_seeded(42);
526 let arr = rng.power(2.0, 10_000).unwrap();
527 for &v in arr.as_slice().unwrap() {
528 assert!((0.0..=1.0).contains(&v), "power value {v} out of [0,1]");
529 }
530 }
531
532 #[test]
533 fn triangular_in_range() {
534 let mut rng = default_rng_seeded(42);
535 let arr = rng.triangular(1.0, 3.0, 5.0, 10_000).unwrap();
536 for &v in arr.as_slice().unwrap() {
537 assert!(
538 (1.0..=5.0).contains(&v),
539 "triangular value {v} out of [1,5]"
540 );
541 }
542 }
543
544 #[test]
545 fn triangular_mean() {
546 let mut rng = default_rng_seeded(42);
547 let n = 100_000;
548 let (left, mode, right) = (1.0, 3.0, 5.0);
549 let arr = rng.triangular(left, mode, right, n).unwrap();
550 let slice = arr.as_slice().unwrap();
551 let mean: f64 = slice.iter().sum::<f64>() / n as f64;
552 let expected_mean = (left + mode + right) / 3.0;
553 assert!(
554 (mean - expected_mean).abs() < 0.05,
555 "triangular mean {mean} too far from {expected_mean}"
556 );
557 }
558
559 #[test]
560 fn vonmises_basic() {
561 let mut rng = default_rng_seeded(42);
562 let arr = rng.vonmises(0.0, 1.0, 10_000).unwrap();
563 assert_eq!(arr.shape(), &[10_000]);
564 }
565
566 #[test]
567 fn wald_positive() {
568 let mut rng = default_rng_seeded(42);
569 let arr = rng.wald(1.0, 1.0, 10_000).unwrap();
570 for &v in arr.as_slice().unwrap() {
571 assert!(v > 0.0, "wald value {v} must be positive");
572 }
573 }
574
575 #[test]
576 fn standard_cauchy_basic() {
577 let mut rng = default_rng_seeded(42);
578 let arr = rng.standard_cauchy(10_000).unwrap();
579 assert_eq!(arr.shape(), &[10_000]);
580 }
582
583 #[test]
584 fn bad_params() {
585 let mut rng = default_rng_seeded(42);
586 assert!(rng.laplace(0.0, 0.0, 10).is_err());
587 assert!(rng.logistic(0.0, -1.0, 10).is_err());
588 assert!(rng.rayleigh(0.0, 10).is_err());
589 assert!(rng.weibull(0.0, 10).is_err());
590 assert!(rng.pareto(0.0, 10).is_err());
591 assert!(rng.gumbel(0.0, 0.0, 10).is_err());
592 assert!(rng.power(0.0, 10).is_err());
593 assert!(rng.triangular(5.0, 3.0, 1.0, 10).is_err());
594 assert!(rng.vonmises(0.0, -1.0, 10).is_err());
595 assert!(rng.wald(0.0, 1.0, 10).is_err());
596 assert!(rng.wald(1.0, 0.0, 10).is_err());
597 }
598}