1use scirs2_core::ndarray::{s, Array1, Array2, ArrayView1};
16use scirs2_core::numeric::{Float, FromPrimitive};
17use scirs2_core::random::random;
18use std::fmt;
19
20use crate::error::{IntegrateError, IntegrateResult};
21
22#[derive(Clone, Debug)]
24pub struct QMCQuadResult<T> {
25 pub integral: T,
27 pub standard_error: T,
29}
30
31impl<T: fmt::Display> fmt::Display for QMCQuadResult<T> {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 write!(
34 f,
35 "QMCQuadResult(integral={}, standard_error={})",
36 self.integral, self.standard_error
37 )
38 }
39}
40
41pub trait QRNGEngine: Send + Sync {
43 fn random(&mut self, n: usize) -> Array2<f64>;
45
46 fn dim(&self) -> usize;
48
49 fn new_from_seed(&self, seed: u64) -> Box<dyn QRNGEngine>;
51}
52
53pub struct RandomGenerator {
55 dim: usize,
56 _seed: u64,
58}
59
60impl RandomGenerator {
61 pub fn new(dim: usize, seed: Option<u64>) -> Self {
63 let _seed = seed.unwrap_or_else(random::<u64>);
64
65 Self { dim, _seed }
66 }
67}
68
69impl QRNGEngine for RandomGenerator {
70 fn random(&mut self, n: usize) -> Array2<f64> {
71 let mut result = Array2::zeros((n, self.dim));
72
73 for i in 0..n {
74 for j in 0..self.dim {
75 result[[i, j]] = random::<f64>();
76 }
77 }
78
79 result
80 }
81
82 fn dim(&self) -> usize {
83 self.dim
84 }
85
86 fn new_from_seed(&self, seed: u64) -> Box<dyn QRNGEngine> {
87 Box::new(Self::new(self.dim, Some(seed)))
88 }
89}
90
91pub struct Sobol {
93 dim: usize,
94 seed: u64,
95 curr_index: u64,
96 direction_numbers: Vec<Vec<u64>>,
97 last_point: Vec<u64>,
98}
99
100impl Sobol {
101 pub fn new(dim: usize, seed: Option<u64>) -> Self {
103 let seed = seed.unwrap_or_else(random::<u64>);
104
105 let mut sobol = Self {
106 dim,
107 seed,
108 curr_index: 0,
109 direction_numbers: Vec::new(),
110 last_point: vec![0; dim],
111 };
112
113 sobol.initialize_direction_numbers();
114 sobol
115 }
116
117 fn initialize_direction_numbers(&mut self) {
119 self.direction_numbers = vec![Vec::new(); self.dim];
120
121 self.direction_numbers[0] = (0..64).map(|i| 1u64 << (63 - i)).collect();
123
124 let primitive_polynomials = [
127 0, 0, 3, 7, 11, 13, 19, 25, 37, 41, 55, ];
139
140 let initial_numbers = vec![
141 vec![], vec![], vec![1],
144 vec![1, 1],
145 vec![1, 3, 1],
146 vec![1, 1, 3],
147 vec![1, 3, 3, 9],
148 vec![1, 1, 5, 5],
149 vec![1, 3, 1, 13],
150 vec![1, 1, 5, 5, 17],
151 vec![1, 3, 5, 5, 5],
152 ];
153
154 for d in 1..std::cmp::min(self.dim, primitive_polynomials.len()) {
155 let poly = primitive_polynomials[d];
156 let init_nums = &initial_numbers[d];
157
158 self.direction_numbers[d] = vec![0; 64];
159
160 for (i, &num) in init_nums.iter().enumerate() {
162 self.direction_numbers[d][i] = (num as u64) << (63 - i);
163 }
164
165 let degree = self.bit_length(poly) - 1;
167 for i in degree..64 {
168 let mut value = self.direction_numbers[d][i - degree];
169
170 let mut poly_temp = poly;
172 for j in 1..degree {
173 if poly_temp & 1 == 1 {
174 value ^= self.direction_numbers[d][i - j];
175 }
176 poly_temp >>= 1;
177 }
178
179 self.direction_numbers[d][i] = value;
180 }
181 }
182
183 for d in primitive_polynomials.len()..self.dim {
185 self.direction_numbers[d] = (0..64)
186 .map(|i| {
187 let base = 2 + (d - primitive_polynomials.len()) as u64;
188 self.van_der_corput_direction_number(i, base)
189 })
190 .collect();
191 }
192 }
193
194 fn bit_length(&self, mut n: u64) -> usize {
196 let mut length = 0;
197 while n > 0 {
198 length += 1;
199 n >>= 1;
200 }
201 length
202 }
203
204 fn van_der_corput_direction_number(&self, i: usize, base: u64) -> u64 {
206 if i == 0 {
207 1u64 << 63
208 } else {
209 let mut value = 0u64;
210 let mut n = i + 1;
211 let mut denom = base;
212
213 while n > 0 && denom <= (1u64 << 63) {
214 value |= ((n % base as usize) as u64) << (64 - self.bit_length(denom));
215 n /= base as usize;
216 denom *= base;
217 }
218
219 value
220 }
221 }
222
223 fn generate_point(&mut self) -> Vec<f64> {
225 if self.curr_index == 0 {
226 self.curr_index = 1;
227 return vec![0.0; self.dim];
228 }
229
230 let gray_code_index = self.curr_index ^ (self.curr_index >> 1);
232 let rightmost_zero = (!gray_code_index).trailing_zeros() as usize;
233
234 for d in 0..self.dim {
236 if rightmost_zero < self.direction_numbers[d].len() {
237 self.last_point[d] ^= self.direction_numbers[d][rightmost_zero];
238 }
239 }
240
241 self.curr_index += 1;
242
243 self.last_point
245 .iter()
246 .map(|&x| (x as f64) / u64::MAX as f64)
247 .collect()
248 }
249}
250
251impl QRNGEngine for Sobol {
252 fn random(&mut self, n: usize) -> Array2<f64> {
253 let mut result = Array2::zeros((n, self.dim));
254
255 for i in 0..n {
256 let point = self.generate_point();
257 for j in 0..self.dim {
258 result[[i, j]] = point[j];
259 }
260 }
261
262 result
263 }
264
265 fn dim(&self) -> usize {
266 self.dim
267 }
268
269 fn new_from_seed(&self, seed: u64) -> Box<dyn QRNGEngine> {
270 Box::new(Self::new(self.dim, Some(seed)))
271 }
272}
273
274pub struct Halton {
276 dim: usize,
277 seed: u64,
278 curr_index: usize,
279}
280
281impl Halton {
282 pub fn new(dim: usize, seed: Option<u64>) -> Self {
284 let seed = seed.unwrap_or_else(random::<u64>);
285
286 Self {
287 dim,
288 seed,
289 curr_index: 0,
290 }
291 }
292
293 fn generate_point(&mut self) -> Vec<f64> {
295 let first_primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53];
296 let mut result = vec![0.0; self.dim];
297
298 let offset = (self.seed % 1000) as usize;
300
301 for d in 0..self.dim {
302 let base = if d < first_primes.len() {
303 first_primes[d]
304 } else {
305 first_primes[d % first_primes.len()] + d
307 };
308
309 let mut f = 1.0;
310 let mut r = 0.0;
311 let mut n = self.curr_index + offset;
312
313 while n > 0 {
314 f /= base as f64;
315 r += f * (n % base) as f64;
316 n /= base;
317 }
318
319 result[d] = r;
320 }
321
322 self.curr_index += 1;
323 result
324 }
325}
326
327impl QRNGEngine for Halton {
328 fn random(&mut self, n: usize) -> Array2<f64> {
329 let mut result = Array2::zeros((n, self.dim));
330
331 for i in 0..n {
332 let point = self.generate_point();
333 for j in 0..self.dim {
334 result[[i, j]] = point[j];
335 }
336 }
337
338 result
339 }
340
341 fn dim(&self) -> usize {
342 self.dim
343 }
344
345 fn new_from_seed(&self, seed: u64) -> Box<dyn QRNGEngine> {
346 Box::new(Self::new(self.dim, Some(seed)))
347 }
348}
349
350pub struct Faure {
352 dim: usize,
353 seed: u64,
354 curr_index: usize,
355}
356
357impl Faure {
358 pub fn new(dim: usize, seed: Option<u64>) -> Self {
360 let seed = seed.unwrap_or_else(random::<u64>);
361
362 Self {
363 dim,
364 seed,
365 curr_index: 0,
366 }
367 }
368
369 fn generate_point(&mut self) -> Vec<f64> {
374 let base = self.find_prime_base(self.dim);
375 let mut result = vec![0.0; self.dim];
376
377 let offset = (self.seed % 1000) as usize;
379 let scrambled_index = self.curr_index + offset;
380
381 for (d, result_elem) in result.iter_mut().enumerate().take(self.dim) {
383 *result_elem = self.faure_coordinate(scrambled_index, d, base);
384 }
385
386 self.curr_index += 1;
387 result
388 }
389
390 fn find_prime_base(&self, n: usize) -> usize {
392 if n <= 2 {
393 return 2;
394 }
395
396 let mut candidate = n;
397 while !self.is_prime(candidate) {
398 candidate += 1;
399 }
400 candidate
401 }
402
403 fn is_prime(&self, n: usize) -> bool {
405 if n < 2 {
406 return false;
407 }
408 if n == 2 {
409 return true;
410 }
411 if n.is_multiple_of(2) {
412 return false;
413 }
414
415 let sqrt_n = (n as f64).sqrt() as usize;
416 for i in (3..=sqrt_n).step_by(2) {
417 if n.is_multiple_of(i) {
418 return false;
419 }
420 }
421 true
422 }
423
424 fn faure_coordinate(&self, index: usize, dimension: usize, base: usize) -> f64 {
426 if index == 0 {
427 return 0.0;
428 }
429
430 let digits = self.to_base_digits(index, base);
432 let mut result = 0.0;
433 let mut base_power = base as f64;
434
435 for (i, &digit) in digits.iter().enumerate() {
437 let transformed_digit = self.faure_matrix_element(i, dimension, base) * digit;
438 result += (transformed_digit % base) as f64 / base_power;
439 base_power *= base as f64;
440 }
441
442 result.fract() }
444
445 fn to_base_digits(&self, mut n: usize, base: usize) -> Vec<usize> {
447 if n == 0 {
448 return vec![0];
449 }
450
451 let mut digits = Vec::new();
452 while n > 0 {
453 digits.push(n % base);
454 n /= base;
455 }
456 digits
457 }
458
459 fn faure_matrix_element(&self, i: usize, dimension: usize, base: usize) -> usize {
462 if dimension == 0 {
465 if i == 0 {
466 1
467 } else {
468 0
469 }
470 } else {
471 let offset = dimension * 7 + self.seed as usize % 13; ((i + offset + 1) % base + 1) % base
474 }
475 }
476}
477
478impl QRNGEngine for Faure {
479 fn random(&mut self, n: usize) -> Array2<f64> {
480 let mut result = Array2::zeros((n, self.dim));
481
482 for i in 0..n {
483 let point = self.generate_point();
484 for j in 0..self.dim {
485 result[[i, j]] = point[j];
486 }
487 }
488
489 result
490 }
491
492 fn dim(&self) -> usize {
493 self.dim
494 }
495
496 fn new_from_seed(&self, seed: u64) -> Box<dyn QRNGEngine> {
497 Box::new(Self::new(self.dim, Some(seed)))
498 }
499}
500
501#[allow(dead_code)]
503pub fn scale<T: Float + FromPrimitive>(
504 sample: &Array2<T>,
505 a: &Array1<T>,
506 b: &Array1<T>,
507) -> Array2<T> {
508 let mut scaled = Array2::<T>::zeros(sample.raw_dim());
509 let dim = sample.shape()[1];
510
511 for i in 0..sample.shape()[0] {
512 for j in 0..dim {
513 scaled[[i, j]] = a[j] + (b[j] - a[j]) * sample[[i, j]];
514 }
515 }
516
517 scaled
518}
519
520#[allow(dead_code)]
553pub fn qmc_quad<F>(
554 func: F,
555 a: &Array1<f64>,
556 b: &Array1<f64>,
557 n_estimates: Option<usize>,
558 n_points: Option<usize>,
559 qrng: Option<Box<dyn QRNGEngine>>,
560 log: bool,
561) -> IntegrateResult<QMCQuadResult<f64>>
562where
563 F: Fn(ArrayView1<f64>) -> f64,
564{
565 let n_estimates = n_estimates.unwrap_or(8);
566 let n_points = n_points.unwrap_or(1024);
567
568 if a.len() != b.len() {
570 return Err(IntegrateError::ValueError(
571 "Dimension mismatch: 'a' and 'b' must have the same length".to_string(),
572 ));
573 }
574
575 let dim = a.len();
576
577 let mut qrng = qrng.unwrap_or_else(|| Box::new(Halton::new(dim, None)));
579
580 if qrng.dim() != dim {
581 return Err(IntegrateError::ValueError(format!(
582 "QRNG dimension ({}) does not match integration dimension ({})",
583 qrng.dim(),
584 dim
585 )));
586 }
587
588 for i in 0..dim {
590 if (a[i] - b[i]).abs() < f64::EPSILON {
591 return Ok(QMCQuadResult {
592 integral: if log { f64::NEG_INFINITY } else { 0.0 },
593 standard_error: 0.0,
594 });
595 }
596 }
597
598 let mut a_mod = a.clone();
600 let mut b_mod = b.clone();
601 let mut sign = 1.0;
602
603 for i in 0..dim {
604 if a[i] > b[i] {
605 a_mod[i] = b[i];
606 b_mod[i] = a[i];
607 sign *= -1.0;
608 }
609 }
610
611 let volume = (0..dim).map(|i| b_mod[i] - a_mod[i]).product::<f64>();
613 let delta = volume / (n_points as f64);
614
615 let mut estimates = Array1::<f64>::zeros(n_estimates);
617
618 for i in 0..n_estimates {
620 let sample = qrng.random(n_points);
622
623 let x = scale(&sample, &a_mod, &b_mod);
625
626 let mut sum = 0.0;
628
629 if log {
630 let mut max_val = f64::NEG_INFINITY;
631 let mut log_values = Vec::with_capacity(n_points);
632
633 for j in 0..n_points {
634 let val = func(x.slice(s![j, ..]));
635 log_values.push(val);
636 if val > max_val {
637 max_val = val;
638 }
639 }
640
641 let mut sum_exp = 0.0;
643 for val in log_values {
644 sum_exp += (val - max_val).exp();
645 }
646
647 estimates[i] = max_val + sum_exp.ln() + delta.ln();
648 } else {
649 for j in 0..n_points {
650 sum += func(x.slice(s![j, ..]));
651 }
652
653 estimates[i] = sum * delta;
654 }
655
656 let seed = i as u64 + 1;
658 qrng = qrng.new_from_seed(seed);
659 }
660
661 let integral = if log {
663 let mut max_val = f64::NEG_INFINITY;
664 for i in 0..n_estimates {
665 if estimates[i] > max_val {
666 max_val = estimates[i];
667 }
668 }
669
670 let mut sum_exp = 0.0;
671 for i in 0..n_estimates {
672 sum_exp += (estimates[i] - max_val).exp();
673 }
674
675 max_val + (sum_exp / (n_estimates as f64)).ln()
676 } else {
677 estimates.sum() / (n_estimates as f64)
678 };
679
680 let standard_error = if n_estimates > 1 {
682 if log {
683 let mean = integral;
685 let mut variance = 0.0;
686
687 for i in 0..n_estimates {
688 let diff = (estimates[i] - mean).exp();
689 variance += (diff - 1.0).powi(2);
690 }
691
692 variance /= (n_estimates - 1) as f64;
693 (variance / (n_estimates as f64)).sqrt().ln()
694 } else {
695 let mean = integral;
696 let mut variance = 0.0;
697
698 for estimate in estimates.iter().take(n_estimates) {
699 variance += (estimate - mean).powi(2);
700 }
701
702 variance /= (n_estimates - 1) as f64;
703 (variance / (n_estimates as f64)).sqrt()
704 }
705 } else if log {
706 f64::NEG_INFINITY
707 } else {
708 0.0
709 };
710
711 let final_integral = if log && sign < 0.0 {
713 -integral
717 } else {
718 integral * sign
719 };
720
721 Ok(QMCQuadResult {
722 integral: final_integral,
723 standard_error,
724 })
725}
726
727#[allow(dead_code)]
765pub fn qmc_quad_parallel<F>(
766 func: F,
767 a: &Array1<f64>,
768 b: &Array1<f64>,
769 n_estimates: Option<usize>,
770 n_points: Option<usize>,
771 qrng: Option<Box<dyn QRNGEngine>>,
772 log: bool,
773 workers: Option<usize>,
774) -> IntegrateResult<QMCQuadResult<f64>>
775where
776 F: Fn(ArrayView1<f64>) -> f64 + Send + Sync,
777{
778 let n_estimates = n_estimates.unwrap_or(8);
779 let n_points = n_points.unwrap_or(1024);
780
781 if a.len() != b.len() {
783 return Err(IntegrateError::ValueError(
784 "Dimension mismatch: 'a' and 'b' must have the same length".to_string(),
785 ));
786 }
787
788 let dim = a.len();
789
790 let qrng = qrng.unwrap_or_else(|| Box::new(Halton::new(dim, None)));
792
793 if qrng.dim() != dim {
794 return Err(IntegrateError::ValueError(format!(
795 "QRNG dimension ({}) does not match integration dimension ({})",
796 qrng.dim(),
797 dim
798 )));
799 }
800
801 for i in 0..dim {
803 if (a[i] - b[i]).abs() < f64::EPSILON {
804 return Ok(QMCQuadResult {
805 integral: if log { f64::NEG_INFINITY } else { 0.0 },
806 standard_error: 0.0,
807 });
808 }
809 }
810
811 let mut a_mod = a.clone();
813 let mut b_mod = b.clone();
814 let mut sign = 1.0;
815
816 for i in 0..dim {
817 if a[i] > b[i] {
818 a_mod[i] = b[i];
819 b_mod[i] = a[i];
820 sign *= -1.0;
821 }
822 }
823
824 let mut volume = 1.0;
826 for i in 0..dim {
827 volume *= b_mod[i] - a_mod[i];
828 }
829
830 #[cfg(feature = "parallel")]
832 {
833 if let Some(num_workers) = workers {
834 use scirs2_core::parallel_ops::ThreadPoolBuilder;
836 let pool = ThreadPoolBuilder::new()
837 .num_threads(num_workers)
838 .build()
839 .map_err(|_| {
840 IntegrateError::ValueError("Failed to create thread pool".to_string())
841 })?;
842
843 pool.install(|| {
844 parallel_qmc_integration_impl(
845 func,
846 &a_mod,
847 &b_mod,
848 n_estimates,
849 n_points,
850 &*qrng,
851 log,
852 volume,
853 sign,
854 )
855 })
856 } else {
857 parallel_qmc_integration_impl(
859 func,
860 &a_mod,
861 &b_mod,
862 n_estimates,
863 n_points,
864 &*qrng,
865 log,
866 volume,
867 sign,
868 )
869 }
870 }
871
872 #[cfg(not(feature = "parallel"))]
873 {
874 let _ = workers; sequential_qmc_integration(
877 func,
878 &a_mod,
879 &b_mod,
880 n_estimates,
881 n_points,
882 qrng,
883 log,
884 volume,
885 sign,
886 )
887 }
888}
889
890#[cfg(feature = "parallel")]
891#[allow(dead_code)]
892fn parallel_qmc_integration_impl<F>(
893 func: F,
894 a: &Array1<f64>,
895 b: &Array1<f64>,
896 n_estimates: usize,
897 n_points: usize,
898 qrng: &dyn QRNGEngine,
899 log: bool,
900 volume: f64,
901 sign: f64,
902) -> IntegrateResult<QMCQuadResult<f64>>
903where
904 F: Fn(ArrayView1<f64>) -> f64 + Send + Sync,
905{
906 use scirs2_core::parallel_ops::*;
907
908 let _estimates: Vec<f64> = (0..n_estimates)
910 .into_par_iter()
911 .map(|_| {
912 let mut local_qrng = qrng.new_from_seed(scirs2_core::random::random());
914
915 let points = local_qrng.random(n_points);
917
918 let mut sum = 0.0;
920
921 for i in 0..n_points {
922 let point = points.row(i);
923
924 let mut transformed_point = Array1::zeros(a.len());
926 for j in 0..a.len() {
927 transformed_point[j] = a[j] + point[j] * (b[j] - a[j]);
928 }
929
930 let value = func(transformed_point.view());
931
932 if log {
933 if value > f64::NEG_INFINITY {
935 sum += value.exp() / n_points as f64;
936 }
937 } else {
938 sum += value / n_points as f64;
939 }
940 }
941
942 if log {
943 sum.ln() + volume.ln()
944 } else {
945 sum * volume
946 }
947 })
948 .collect();
949
950 compute_qmc_result(_estimates, log, sign)
951}
952
953#[cfg(not(feature = "parallel"))]
954#[allow(dead_code)]
955fn sequential_qmc_integration<F>(
956 func: F,
957 a: &Array1<f64>,
958 b: &Array1<f64>,
959 n_estimates: usize,
960 n_points: usize,
961 mut qrng: Box<dyn QRNGEngine>,
962 log: bool,
963 volume: f64,
964 sign: f64,
965) -> IntegrateResult<QMCQuadResult<f64>>
966where
967 F: Fn(ArrayView1<f64>) -> f64,
968{
969 let mut estimates = Vec::with_capacity(n_estimates);
970
971 for _ in 0..n_estimates {
972 let points = qrng.random(n_points);
974
975 let mut sum = 0.0;
977
978 for i in 0..n_points {
979 let point = points.row(i);
980
981 let mut transformed_point = Array1::zeros(a.len());
983 for j in 0..a.len() {
984 transformed_point[j] = a[j] + point[j] * (b[j] - a[j]);
985 }
986
987 let value = func(transformed_point.view());
988
989 if log {
990 if value > f64::NEG_INFINITY {
992 sum += value.exp() / n_points as f64;
993 }
994 } else {
995 sum += value / n_points as f64;
996 }
997 }
998
999 let estimate = if log {
1000 sum.ln() + volume.ln()
1001 } else {
1002 sum * volume
1003 };
1004
1005 estimates.push(estimate);
1006 }
1007
1008 compute_qmc_result(estimates, log, sign)
1009}
1010
1011#[allow(dead_code)]
1012fn compute_qmc_result(
1013 estimates: Vec<f64>,
1014 log: bool,
1015 sign: f64,
1016) -> IntegrateResult<QMCQuadResult<f64>> {
1017 let n_estimates = estimates.len();
1018
1019 let integral = if log {
1021 let max_val = estimates.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
1023 if max_val == f64::NEG_INFINITY {
1024 f64::NEG_INFINITY
1025 } else {
1026 let sum_exp: f64 = estimates.iter().map(|&x| (x - max_val).exp()).sum();
1027 max_val + (sum_exp / n_estimates as f64).ln()
1028 }
1029 } else {
1030 estimates.iter().sum::<f64>() / n_estimates as f64
1031 };
1032
1033 let standard_error = if estimates.len() > 1 {
1034 if log {
1035 let mean = integral;
1037 let mut variance = 0.0;
1038
1039 for estimate in estimates.iter().take(n_estimates) {
1040 let diff = estimate - mean;
1041 variance += diff * diff;
1042 }
1043
1044 variance /= (n_estimates - 1) as f64;
1045 (variance / (n_estimates as f64)).sqrt().ln()
1046 } else {
1047 let mean = integral;
1048 let mut variance = 0.0;
1049
1050 for estimate in estimates.iter().take(n_estimates) {
1051 variance += (estimate - mean).powi(2);
1052 }
1053
1054 variance /= (n_estimates - 1) as f64;
1055 (variance / (n_estimates as f64)).sqrt()
1056 }
1057 } else if log {
1058 f64::NEG_INFINITY
1059 } else {
1060 0.0
1061 };
1062
1063 let final_integral = if log && sign < 0.0 {
1065 -integral
1068 } else {
1069 integral * sign
1070 };
1071
1072 Ok(QMCQuadResult {
1073 integral: final_integral,
1074 standard_error,
1075 })
1076}
1077
1078#[cfg(test)]
1079mod tests {
1080 use super::*;
1081 use crate::{qmc_quad, qmc_quad_parallel, Faure};
1082 use approx::assert_abs_diff_eq;
1083 use scirs2_core::ndarray::{s, Array1, ArrayView1};
1084
1085 #[test]
1086 fn test_qmc_integral_1d() {
1087 let f = |x: ArrayView1<f64>| x[0].powi(2);
1089 let a = Array1::from_vec(vec![0.0]);
1090 let b = Array1::from_vec(vec![1.0]);
1091
1092 let result = qmc_quad(f, &a, &b, Some(8), Some(1000), None, false).unwrap();
1093
1094 assert_abs_diff_eq!(result.integral, 1.0 / 3.0, epsilon = 0.01);
1095 }
1096
1097 #[test]
1098 fn test_qmc_integral_2d() {
1099 let f = |x: ArrayView1<f64>| x[0] * x[1];
1101 let a = Array1::from_vec(vec![0.0, 0.0]);
1102 let b = Array1::from_vec(vec![1.0, 1.0]);
1103
1104 let result = qmc_quad(f, &a, &b, Some(8), Some(1000), None, false).unwrap();
1105
1106 assert_abs_diff_eq!(result.integral, 0.25, epsilon = 0.01);
1107 }
1108
1109 #[test]
1110 fn test_infinite_limits() {
1111 let f = |x: ArrayView1<f64>| (-x[0].powi(2)).exp();
1113 let a = Array1::from_vec(vec![-5.0]); let b = Array1::from_vec(vec![5.0]);
1115
1116 let result = qmc_quad(f, &a, &b, Some(8), Some(1000), None, false).unwrap();
1117
1118 assert_abs_diff_eq!(result.integral, std::f64::consts::PI.sqrt(), epsilon = 0.05);
1119 }
1120
1121 #[test]
1122 fn test_faure_sequence() {
1123 let mut faure = Faure::new(2, Some(42));
1125
1126 let points = faure.random(10);
1128
1129 assert_eq!(points.shape()[0], 10);
1131 assert_eq!(points.shape()[1], 2);
1132
1133 for i in 0..10 {
1135 for j in 0..2 {
1136 assert!(points[[i, j]] >= 0.0 && points[[i, j]] < 1.0);
1137 }
1138 }
1139
1140 let mut faure2 = Faure::new(2, Some(42));
1142 let points2 = faure2.random(5);
1143 let points_first_5 = points.slice(s![0..5, ..]);
1144
1145 for i in 0..5 {
1146 for j in 0..2 {
1147 assert_abs_diff_eq!(points_first_5[[i, j]], points2[[i, j]], epsilon = 1e-10);
1148 }
1149 }
1150 }
1151
1152 #[test]
1153 fn test_faure_integration() {
1154 let f = |x: ArrayView1<f64>| x[0] * x[1];
1156 let a = Array1::from_vec(vec![0.0, 0.0]);
1157 let b = Array1::from_vec(vec![1.0, 1.0]);
1158
1159 let faure = Faure::new(2, Some(42));
1160 let result =
1161 qmc_quad(f, &a, &b, Some(8), Some(1000), Some(Box::new(faure)), false).unwrap();
1162
1163 assert_abs_diff_eq!(result.integral, 0.25, epsilon = 0.2);
1167 }
1168
1169 #[test]
1170 fn test_qmc_quad_parallel_workers() {
1171 let f = |x: ArrayView1<f64>| x[0].powi(2) + x[1].powi(2);
1173 let a = Array1::from_vec(vec![0.0, 0.0]);
1174 let b = Array1::from_vec(vec![1.0, 1.0]);
1175
1176 let result =
1178 qmc_quad_parallel(f, &a, &b, Some(8), Some(1000), None, false, Some(2)).unwrap();
1179
1180 assert_abs_diff_eq!(result.integral, 2.0 / 3.0, epsilon = 0.1);
1182 assert!(result.standard_error >= 0.0);
1183 }
1184}