ferray_random/distributions/
misc_continuous.rs1use ferray_core::{Array, FerrayError, Ix1};
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, vec_to_array1};
12
13impl<B: BitGenerator> Generator<B> {
14 pub fn laplace(
26 &mut self,
27 loc: f64,
28 scale: f64,
29 size: usize,
30 ) -> Result<Array<f64, Ix1>, FerrayError> {
31 if size == 0 {
32 return Err(FerrayError::invalid_value("size must be > 0"));
33 }
34 if scale <= 0.0 {
35 return Err(FerrayError::invalid_value(format!(
36 "scale must be positive, got {scale}"
37 )));
38 }
39 let data = generate_vec(self, size, |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 loc - scale * u.signum() * (1.0 - 2.0 * u.abs()).ln()
46 });
47 vec_to_array1(data)
48 }
49
50 pub fn logistic(
62 &mut self,
63 loc: f64,
64 scale: f64,
65 size: usize,
66 ) -> Result<Array<f64, Ix1>, FerrayError> {
67 if size == 0 {
68 return Err(FerrayError::invalid_value("size must be > 0"));
69 }
70 if scale <= 0.0 {
71 return Err(FerrayError::invalid_value(format!(
72 "scale must be positive, got {scale}"
73 )));
74 }
75 let data = generate_vec(self, size, |bg| {
76 loop {
77 let u = bg.next_f64();
78 if u > f64::EPSILON && u < 1.0 - f64::EPSILON {
79 return loc + scale * (u / (1.0 - u)).ln();
80 }
81 }
82 });
83 vec_to_array1(data)
84 }
85
86 pub fn rayleigh(&mut self, scale: f64, size: usize) -> Result<Array<f64, Ix1>, FerrayError> {
97 if size == 0 {
98 return Err(FerrayError::invalid_value("size must be > 0"));
99 }
100 if scale <= 0.0 {
101 return Err(FerrayError::invalid_value(format!(
102 "scale must be positive, got {scale}"
103 )));
104 }
105 let data = generate_vec(self, size, |bg| {
106 scale * (2.0 * standard_exponential_single(bg)).sqrt()
107 });
108 vec_to_array1(data)
109 }
110
111 pub fn weibull(&mut self, a: f64, size: usize) -> Result<Array<f64, Ix1>, FerrayError> {
122 if size == 0 {
123 return Err(FerrayError::invalid_value("size must be > 0"));
124 }
125 if a <= 0.0 {
126 return Err(FerrayError::invalid_value(format!(
127 "a must be positive, got {a}"
128 )));
129 }
130 let data = generate_vec(self, size, |bg| {
131 standard_exponential_single(bg).powf(1.0 / a)
132 });
133 vec_to_array1(data)
134 }
135
136 pub fn pareto(&mut self, a: f64, size: usize) -> Result<Array<f64, Ix1>, FerrayError> {
148 if size == 0 {
149 return Err(FerrayError::invalid_value("size must be > 0"));
150 }
151 if a <= 0.0 {
152 return Err(FerrayError::invalid_value(format!(
153 "a must be positive, got {a}"
154 )));
155 }
156 let data = generate_vec(self, size, |bg| {
157 let e = standard_exponential_single(bg);
158 (e / a).exp() - 1.0
159 });
160 vec_to_array1(data)
161 }
162
163 pub fn gumbel(
175 &mut self,
176 loc: f64,
177 scale: f64,
178 size: usize,
179 ) -> Result<Array<f64, Ix1>, FerrayError> {
180 if size == 0 {
181 return Err(FerrayError::invalid_value("size must be > 0"));
182 }
183 if scale <= 0.0 {
184 return Err(FerrayError::invalid_value(format!(
185 "scale must be positive, got {scale}"
186 )));
187 }
188 let data = generate_vec(self, size, |bg| {
189 loop {
190 let u = bg.next_f64();
191 if u > f64::EPSILON && u < 1.0 - f64::EPSILON {
192 return loc - scale * (-u.ln()).ln();
193 }
194 }
195 });
196 vec_to_array1(data)
197 }
198
199 pub fn power(&mut self, a: f64, size: usize) -> Result<Array<f64, Ix1>, FerrayError> {
211 if size == 0 {
212 return Err(FerrayError::invalid_value("size must be > 0"));
213 }
214 if a <= 0.0 {
215 return Err(FerrayError::invalid_value(format!(
216 "a must be positive, got {a}"
217 )));
218 }
219 let data = generate_vec(self, size, |bg| {
220 let e = standard_exponential_single(bg);
221 (1.0 - (-e).exp()).powf(1.0 / a)
225 });
226 vec_to_array1(data)
227 }
228
229 pub fn triangular(
240 &mut self,
241 left: f64,
242 mode: f64,
243 right: f64,
244 size: usize,
245 ) -> Result<Array<f64, Ix1>, FerrayError> {
246 if size == 0 {
247 return Err(FerrayError::invalid_value("size must be > 0"));
248 }
249 if left >= right {
250 return Err(FerrayError::invalid_value(format!(
251 "left ({left}) must be less than right ({right})"
252 )));
253 }
254 if mode < left || mode > right {
255 return Err(FerrayError::invalid_value(format!(
256 "mode ({mode}) must be in [{left}, {right}]"
257 )));
258 }
259 let fc = (mode - left) / (right - left);
260 let data = generate_vec(self, size, |bg| {
261 let u = bg.next_f64();
262 if u < fc {
263 left + ((right - left) * (mode - left) * u).sqrt()
264 } else {
265 right - ((right - left) * (right - mode) * (1.0 - u)).sqrt()
266 }
267 });
268 vec_to_array1(data)
269 }
270
271 pub fn vonmises(
284 &mut self,
285 mu: f64,
286 kappa: f64,
287 size: usize,
288 ) -> Result<Array<f64, Ix1>, FerrayError> {
289 if size == 0 {
290 return Err(FerrayError::invalid_value("size must be > 0"));
291 }
292 if kappa < 0.0 {
293 return Err(FerrayError::invalid_value(format!(
294 "kappa must be non-negative, got {kappa}"
295 )));
296 }
297 if kappa < 1e-6 {
298 let data = generate_vec(self, size, |bg| {
300 mu + (bg.next_f64() * std::f64::consts::TAU - std::f64::consts::PI)
301 });
302 return vec_to_array1(data);
303 }
304
305 let tau = 1.0 + (1.0 + 4.0 * kappa * kappa).sqrt();
307 let rho = (tau - (2.0 * tau).sqrt()) / (2.0 * kappa);
308 let r = (1.0 + rho * rho) / (2.0 * rho);
309
310 let data = generate_vec(self, size, |bg| {
311 loop {
312 let u1 = bg.next_f64();
313 let z = (std::f64::consts::TAU * u1 - std::f64::consts::PI).cos();
314 let f_val = (1.0 + r * z) / (r + z);
315 let c = kappa * (r - f_val);
316 let u2 = bg.next_f64();
317 if u2 < c * (2.0 - c) || u2 <= c * (-c).exp() {
318 let u3 = bg.next_f64();
319 let theta = if u3 > 0.5 {
320 mu + f_val.acos()
321 } else {
322 mu - f_val.acos()
323 };
324 return theta;
325 }
326 }
327 });
328 vec_to_array1(data)
329 }
330
331 pub fn wald(
341 &mut self,
342 mean: f64,
343 scale: f64,
344 size: usize,
345 ) -> Result<Array<f64, Ix1>, FerrayError> {
346 if size == 0 {
347 return Err(FerrayError::invalid_value("size must be > 0"));
348 }
349 if mean <= 0.0 {
350 return Err(FerrayError::invalid_value(format!(
351 "mean must be positive, got {mean}"
352 )));
353 }
354 if scale <= 0.0 {
355 return Err(FerrayError::invalid_value(format!(
356 "scale must be positive, got {scale}"
357 )));
358 }
359 let data = generate_vec(self, size, |bg| {
361 let z = standard_normal_single(bg);
362 let v = z * z;
363 let mu = mean;
364 let lam = scale;
365 let x = mu + (mu * mu * v) / (2.0 * lam)
366 - (mu / (2.0 * lam)) * (4.0 * mu * lam * v + mu * mu * v * v).sqrt();
367 let u = bg.next_f64();
368 if u <= mu / (mu + x) { x } else { mu * mu / x }
369 });
370 vec_to_array1(data)
371 }
372
373 pub fn standard_cauchy(&mut self, size: usize) -> Result<Array<f64, Ix1>, FerrayError> {
383 if size == 0 {
384 return Err(FerrayError::invalid_value("size must be > 0"));
385 }
386 let data = generate_vec(self, size, |bg| {
387 loop {
388 let u = bg.next_f64();
389 if (u - 0.5).abs() > f64::EPSILON {
391 return (std::f64::consts::PI * (u - 0.5)).tan();
392 }
393 }
394 });
395 vec_to_array1(data)
396 }
397}
398
399#[cfg(test)]
400mod tests {
401 use crate::default_rng_seeded;
402
403 #[test]
404 fn laplace_mean_variance() {
405 let mut rng = default_rng_seeded(42);
406 let n = 100_000;
407 let loc = 2.0;
408 let scale = 3.0;
409 let arr = rng.laplace(loc, scale, n).unwrap();
410 let slice = arr.as_slice().unwrap();
411 let mean: f64 = slice.iter().sum::<f64>() / n as f64;
412 let var: f64 = slice.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / n as f64;
413 let expected_var = 2.0 * scale * scale;
415 let se = (expected_var / n as f64).sqrt();
416 assert!(
417 (mean - loc).abs() < 3.0 * se,
418 "laplace mean {mean} too far from {loc}"
419 );
420 assert!(
421 (var - expected_var).abs() / expected_var < 0.05,
422 "laplace variance {var} too far from {expected_var}"
423 );
424 }
425
426 #[test]
427 fn logistic_mean() {
428 let mut rng = default_rng_seeded(42);
429 let n = 100_000;
430 let loc = 1.0;
431 let scale = 2.0;
432 let arr = rng.logistic(loc, scale, n).unwrap();
433 let slice = arr.as_slice().unwrap();
434 let mean: f64 = slice.iter().sum::<f64>() / n as f64;
435 let expected_var = (std::f64::consts::PI * scale).powi(2) / 3.0;
436 let se = (expected_var / n as f64).sqrt();
437 assert!(
438 (mean - loc).abs() < 3.0 * se,
439 "logistic mean {mean} too far from {loc}"
440 );
441 }
442
443 #[test]
444 fn rayleigh_positive() {
445 let mut rng = default_rng_seeded(42);
446 let arr = rng.rayleigh(2.0, 10_000).unwrap();
447 for &v in arr.as_slice().unwrap() {
448 assert!(v > 0.0);
449 }
450 }
451
452 #[test]
453 fn rayleigh_mean() {
454 let mut rng = default_rng_seeded(42);
455 let n = 100_000;
456 let scale = 2.0;
457 let arr = rng.rayleigh(scale, n).unwrap();
458 let slice = arr.as_slice().unwrap();
459 let mean: f64 = slice.iter().sum::<f64>() / n as f64;
460 let expected_mean = scale * (std::f64::consts::FRAC_PI_2).sqrt();
462 let expected_var = (4.0 - std::f64::consts::PI) / 2.0 * scale * scale;
463 let se = (expected_var / n as f64).sqrt();
464 assert!(
465 (mean - expected_mean).abs() < 3.0 * se,
466 "rayleigh mean {mean} too far from {expected_mean}"
467 );
468 }
469
470 #[test]
471 fn weibull_positive() {
472 let mut rng = default_rng_seeded(42);
473 let arr = rng.weibull(1.5, 10_000).unwrap();
474 for &v in arr.as_slice().unwrap() {
475 assert!(v > 0.0);
476 }
477 }
478
479 #[test]
480 fn pareto_ge_zero() {
481 let mut rng = default_rng_seeded(42);
482 let arr = rng.pareto(3.0, 10_000).unwrap();
483 for &v in arr.as_slice().unwrap() {
484 assert!(v >= 0.0, "pareto value {v} < 0");
485 }
486 }
487
488 #[test]
489 fn gumbel_mean() {
490 let mut rng = default_rng_seeded(42);
491 let n = 100_000;
492 let loc = 1.0;
493 let scale = 2.0;
494 let arr = rng.gumbel(loc, scale, n).unwrap();
495 let slice = arr.as_slice().unwrap();
496 let mean: f64 = slice.iter().sum::<f64>() / n as f64;
497 let euler = 0.5772156649015329;
499 let expected_mean = loc + scale * euler;
500 let expected_var = std::f64::consts::PI * std::f64::consts::PI * scale * scale / 6.0;
501 let se = (expected_var / n as f64).sqrt();
502 assert!(
503 (mean - expected_mean).abs() < 3.0 * se,
504 "gumbel mean {mean} too far from {expected_mean}"
505 );
506 }
507
508 #[test]
509 fn power_in_range() {
510 let mut rng = default_rng_seeded(42);
511 let arr = rng.power(2.0, 10_000).unwrap();
512 for &v in arr.as_slice().unwrap() {
513 assert!(v >= 0.0 && v <= 1.0, "power value {v} out of [0,1]");
514 }
515 }
516
517 #[test]
518 fn triangular_in_range() {
519 let mut rng = default_rng_seeded(42);
520 let arr = rng.triangular(1.0, 3.0, 5.0, 10_000).unwrap();
521 for &v in arr.as_slice().unwrap() {
522 assert!(v >= 1.0 && v <= 5.0, "triangular value {v} out of [1,5]");
523 }
524 }
525
526 #[test]
527 fn triangular_mean() {
528 let mut rng = default_rng_seeded(42);
529 let n = 100_000;
530 let (left, mode, right) = (1.0, 3.0, 5.0);
531 let arr = rng.triangular(left, mode, right, n).unwrap();
532 let slice = arr.as_slice().unwrap();
533 let mean: f64 = slice.iter().sum::<f64>() / n as f64;
534 let expected_mean = (left + mode + right) / 3.0;
535 assert!(
536 (mean - expected_mean).abs() < 0.05,
537 "triangular mean {mean} too far from {expected_mean}"
538 );
539 }
540
541 #[test]
542 fn vonmises_basic() {
543 let mut rng = default_rng_seeded(42);
544 let arr = rng.vonmises(0.0, 1.0, 10_000).unwrap();
545 assert_eq!(arr.shape(), &[10_000]);
546 }
547
548 #[test]
549 fn wald_positive() {
550 let mut rng = default_rng_seeded(42);
551 let arr = rng.wald(1.0, 1.0, 10_000).unwrap();
552 for &v in arr.as_slice().unwrap() {
553 assert!(v > 0.0, "wald value {v} must be positive");
554 }
555 }
556
557 #[test]
558 fn standard_cauchy_basic() {
559 let mut rng = default_rng_seeded(42);
560 let arr = rng.standard_cauchy(10_000).unwrap();
561 assert_eq!(arr.shape(), &[10_000]);
562 }
564
565 #[test]
566 fn bad_params() {
567 let mut rng = default_rng_seeded(42);
568 assert!(rng.laplace(0.0, 0.0, 10).is_err());
569 assert!(rng.logistic(0.0, -1.0, 10).is_err());
570 assert!(rng.rayleigh(0.0, 10).is_err());
571 assert!(rng.weibull(0.0, 10).is_err());
572 assert!(rng.pareto(0.0, 10).is_err());
573 assert!(rng.gumbel(0.0, 0.0, 10).is_err());
574 assert!(rng.power(0.0, 10).is_err());
575 assert!(rng.triangular(5.0, 3.0, 1.0, 10).is_err());
576 assert!(rng.vonmises(0.0, -1.0, 10).is_err());
577 assert!(rng.wald(0.0, 1.0, 10).is_err());
578 assert!(rng.wald(1.0, 0.0, 10).is_err());
579 }
580}