1use ::ndarray::{Array1, Array2};
39use rand::Rng;
40use std::f64;
41use thiserror::Error;
42
43#[derive(Error, Debug)]
45pub enum QmcError {
46 #[error("Invalid dimension: {0}. Must be between 1 and {1}")]
48 InvalidDimension(usize, usize),
49
50 #[error("Invalid number of points: {0}. Must be positive")]
52 InvalidPointCount(usize),
53
54 #[error("Sequence initialization failed: {0}")]
56 InitializationFailed(String),
57
58 #[error("Unsupported dimension {0} for sequence type")]
60 UnsupportedDimension(usize),
61
62 #[error("Invalid base: {0}. Must be prime and greater than 1")]
64 InvalidBase(u32),
65}
66
67pub trait LowDiscrepancySequence {
69 fn next_point(&mut self) -> Vec<f64>;
71
72 fn generate(&mut self, n: usize) -> Array2<f64> {
74 let dim = self.dimension();
75 let mut points = Array2::zeros((n, dim));
76
77 for i in 0..n {
78 let point = self.next_point();
79 for j in 0..dim {
80 points[[i, j]] = point[j];
81 }
82 }
83
84 points
85 }
86
87 fn dimension(&self) -> usize;
89
90 fn reset(&mut self);
92
93 fn skip(&mut self, n: usize) {
95 for _ in 0..n {
96 self.next_point();
97 }
98 }
99}
100
101pub struct SobolGenerator {
106 dimension: usize,
107 current_index: u64,
108 direction_numbers: Vec<Vec<u32>>,
109 current_point: Vec<u64>,
110 max_bits: usize,
111}
112
113impl SobolGenerator {
114 pub const MAX_DIMENSION: usize = 21201;
116
117 pub fn dimension(dimension: usize) -> Result<Self, QmcError> {
119 if dimension == 0 || dimension > Self::MAX_DIMENSION {
120 return Err(QmcError::InvalidDimension(dimension, Self::MAX_DIMENSION));
121 }
122
123 let max_bits = 63; let mut generator = Self {
125 dimension,
126 current_index: 0,
127 direction_numbers: Vec::new(),
128 current_point: vec![0; dimension],
129 max_bits,
130 };
131
132 generator.initialize_direction_numbers()?;
133 Ok(generator)
134 }
135
136 fn initialize_direction_numbers(&mut self) -> Result<(), QmcError> {
138 self.direction_numbers.clear();
139
140 let mut first_dim = Vec::new();
142 for i in 0..self.max_bits.min(32) {
143 if i <= 31 {
144 first_dim.push(1u32 << (31 - i));
145 }
146 }
147 self.direction_numbers.push(first_dim);
148
149 for dim in 1..self.dimension {
152 let direction_nums = self.generate_direction_numbers_for_dimension(dim)?;
153 self.direction_numbers.push(direction_nums);
154 }
155
156 Ok(())
157 }
158
159 fn generate_direction_numbers_for_dimension(&self, dim: usize) -> Result<Vec<u32>, QmcError> {
161 let mut direction_nums = Vec::with_capacity(self.max_bits);
164
165 let base_values = [1, 1, 3, 1, 3, 3, 1];
167 let poly_coeffs = [0, 1, 1, 2, 1, 4, 2]; let start_val = base_values[dim % base_values.len()];
170 let poly_coeff = poly_coeffs[dim % poly_coeffs.len()];
171
172 direction_nums.push(start_val << 30);
174 if self.max_bits > 1 {
175 direction_nums.push((start_val * 2 + 1) << 29);
176 }
177
178 for i in 2..self.max_bits {
180 let prev2 = direction_nums[i - 2];
181 let prev1 = direction_nums[i.saturating_sub(1)];
182
183 let next_val = prev1 ^ (prev2 >> poly_coeff) ^ (prev1 >> 1);
185 direction_nums.push(next_val);
186 }
187
188 Ok(direction_nums)
189 }
190
191 pub fn estimate_discrepancy(&self, n: usize) -> f64 {
193 let base_discrepancy = (self.dimension as f64).ln() / (n as f64);
196 base_discrepancy.max(1e-10)
197 }
198}
199
200impl LowDiscrepancySequence for SobolGenerator {
201 fn next_point(&mut self) -> Vec<f64> {
202 if self.current_index == 0 {
203 self.current_index += 1;
204 return vec![0.0; self.dimension];
205 }
206
207 let rightmost_zero_pos = (!self.current_index).trailing_zeros() as usize;
209
210 for dim in 0..self.dimension {
212 if rightmost_zero_pos < self.direction_numbers[dim].len() {
213 self.current_point[dim] ^= self.direction_numbers[dim][rightmost_zero_pos] as u64;
214 }
215 }
216
217 self.current_index += 1;
218
219 self.current_point
221 .iter()
222 .map(|&x| (x as f64) / (1u64 << 32) as f64)
223 .collect()
224 }
225
226 fn dimension(&self) -> usize {
227 self.dimension
228 }
229
230 fn reset(&mut self) {
231 self.current_index = 0;
232 self.current_point.fill(0);
233 }
234}
235
236pub struct HaltonGenerator {
241 dimension: usize,
242 bases: Vec<u32>,
243 indices: Vec<u64>,
244}
245
246impl HaltonGenerator {
247 pub fn new(bases: &[u32]) -> Self {
249 let dimension = bases.len();
250 Self {
251 dimension,
252 bases: bases.to_vec(),
253 indices: vec![0; dimension],
254 }
255 }
256
257 pub fn dimension(dimension: usize) -> Result<Self, QmcError> {
259 if dimension == 0 {
260 return Err(QmcError::InvalidDimension(dimension, 1000));
261 }
262
263 let primes = Self::generate_primes(dimension);
264 Ok(Self::new(&primes))
265 }
266
267 fn generate_primes(n: usize) -> Vec<u32> {
269 let mut primes = Vec::new();
270 let mut candidate = 2u32;
271
272 while primes.len() < n {
273 if Self::is_prime(candidate) {
274 primes.push(candidate);
275 }
276 candidate += if candidate == 2 { 1 } else { 2 };
277 }
278
279 primes
280 }
281
282 fn is_prime(n: u32) -> bool {
284 if n < 2 {
285 return false;
286 }
287 if n == 2 {
288 return true;
289 }
290 if n % 2 == 0 {
291 return false;
292 }
293
294 let limit = (n as f64).sqrt() as u32 + 1;
295 for i in (3..=limit).step_by(2) {
296 if n % i == 0 {
297 return false;
298 }
299 }
300 true
301 }
302
303 fn radical_inverse(mut n: u64, base: u32) -> f64 {
305 let mut result = 0.0;
306 let mut denominator = base as f64;
307
308 while n > 0 {
309 result += (n % base as u64) as f64 / denominator;
310 n /= base as u64;
311 denominator *= base as f64;
312 }
313
314 result
315 }
316
317 pub fn bases_2(&self) -> &[u32] {
319 &self.bases
320 }
321}
322
323impl LowDiscrepancySequence for HaltonGenerator {
324 fn next_point(&mut self) -> Vec<f64> {
325 let mut point = Vec::with_capacity(self.dimension);
326
327 for dim in 0..self.dimension {
328 let value = Self::radical_inverse(self.indices[dim], self.bases[dim]);
329 point.push(value);
330 self.indices[dim] += 1;
331 }
332
333 point
334 }
335
336 fn dimension(&self) -> usize {
337 self.dimension
338 }
339
340 fn reset(&mut self) {
341 self.indices.fill(0);
342 }
343}
344
345pub struct LatinHypercubeSampler<R: rand::Rng = rand::prelude::ThreadRng> {
350 dimension: usize,
351 rng: crate::random::Random<R>,
352}
353
354impl<R: rand::Rng> LatinHypercubeSampler<R> {
355 pub fn new(dimension: usize) -> LatinHypercubeSampler<rand::prelude::ThreadRng> {
357 LatinHypercubeSampler {
358 dimension,
359 rng: crate::random::Random::default(),
360 }
361 }
362
363 pub fn with_seed(dimension: usize, seed: u64) -> LatinHypercubeSampler<rand::prelude::StdRng> {
365 LatinHypercubeSampler {
366 dimension,
367 rng: crate::random::Random::seed(seed),
368 }
369 }
370
371 pub fn sample(&mut self, n: usize) -> Result<Array2<f64>, QmcError> {
373 if n == 0 {
374 return Err(QmcError::InvalidPointCount(n));
375 }
376
377 let mut points = Array2::zeros((n, self.dimension));
378
379 for dim in 0..self.dimension {
381 let mut permutation: Vec<usize> = (0..n).collect();
382
383 for i in (1..n).rev() {
385 let j = self.rng.random_range(0..i + 1);
386 permutation.swap(i, j);
387 }
388
389 for (idx, &perm_val) in permutation.iter().enumerate() {
391 let uniform_sample = self.rng.random_range(0.0..1.0);
392 let lh_value = (perm_val as f64 + uniform_sample) / n as f64;
393 points[[idx, dim]] = lh_value;
394 }
395 }
396
397 Ok(points)
398 }
399
400 pub fn optimal_sample(&mut self, n: usize, iterations: usize) -> Result<Array2<f64>, QmcError> {
402 let mut best_sample = self.sample(n)?;
403 let mut best_criterion = self.maximin_criterion(&best_sample);
404
405 for _ in 0..iterations {
407 let candidate = self.sample(n)?;
408 let criterion = self.maximin_criterion(&candidate);
409
410 if criterion > best_criterion {
411 best_sample = candidate;
412 best_criterion = criterion;
413 }
414 }
415
416 Ok(best_sample)
417 }
418
419 fn maximin_criterion(&self, points: &Array2<f64>) -> f64 {
421 let n = points.nrows();
422 let mut min_distance = f64::INFINITY;
423
424 for i in 0..n {
425 for j in (i + 1)..n {
426 let distance =
427 self.euclidean_distance(&points.row(i).to_owned(), &points.row(j).to_owned());
428 min_distance = min_distance.min(distance);
429 }
430 }
431
432 min_distance
433 }
434
435 fn euclidean_distance(&self, p1: &Array1<f64>, p2: &Array1<f64>) -> f64 {
437 p1.iter()
438 .zip(p2.iter())
439 .map(|(a, b)| (a - b).powi(2))
440 .sum::<f64>()
441 .sqrt()
442 }
443
444 pub fn dimension(&self) -> usize {
446 self.dimension
447 }
448}
449
450pub struct FaureGenerator {
454 dimension: usize,
455 base: u32,
456 current_index: u64,
457 pascalmatrix: Vec<Vec<u32>>,
458}
459
460impl FaureGenerator {
461 pub fn dimension(dimension: usize) -> Result<Self, QmcError> {
463 if dimension == 0 {
464 return Err(QmcError::InvalidDimension(dimension, 1000));
465 }
466
467 let base = Self::next_prime(dimension as u32);
469
470 let mut generator = Self {
471 dimension,
472 base,
473 current_index: 0,
474 pascalmatrix: Vec::new(),
475 };
476
477 generator.initialize_pascalmatrix();
478 Ok(generator)
479 }
480
481 fn next_prime(n: u32) -> u32 {
483 let mut candidate = n.max(2);
484 while !HaltonGenerator::is_prime(candidate) {
485 candidate += 1;
486 }
487 candidate
488 }
489
490 fn initialize_pascalmatrix(&mut self) {
492 let size = self.base as usize;
493 self.pascalmatrix = vec![vec![0; size]; size];
494
495 for i in 0..size {
497 self.pascalmatrix[i][0] = 1;
498 for j in 1..=i {
499 let prev_row = if i > 0 {
500 self.pascalmatrix[i.saturating_sub(1)][j.saturating_sub(1)]
501 } else {
502 0
503 };
504 let prev_diag = if i > 0 && j < size {
505 self.pascalmatrix[i.saturating_sub(1)][j]
506 } else {
507 0
508 };
509 self.pascalmatrix[i][j] = (prev_row + prev_diag) % self.base;
510 }
511 }
512 }
513
514 fn scrambled_radical_inverse(&self, n: u64, dimension: usize) -> f64 {
516 let mut result = 0.0;
517 let mut denominator = self.base as f64;
518 let mut index = n;
519
520 while index > 0 {
521 let digit = index % self.base as u64;
522
523 let scrambled_digit = if dimension < self.pascalmatrix.len() {
525 (digit
526 + self.pascalmatrix[dimension % self.pascalmatrix.len()]
527 [digit as usize % self.pascalmatrix.len()] as u64)
528 % self.base as u64
529 } else {
530 digit
531 };
532
533 result += scrambled_digit as f64 / denominator;
534 index /= self.base as u64;
535 denominator *= self.base as f64;
536 }
537
538 result
539 }
540}
541
542impl LowDiscrepancySequence for FaureGenerator {
543 fn next_point(&mut self) -> Vec<f64> {
544 let mut point = Vec::with_capacity(self.dimension);
545
546 for dim in 0..self.dimension {
547 let value = self.scrambled_radical_inverse(self.current_index, dim);
548 point.push(value);
549 }
550
551 self.current_index += 1;
552 point
553 }
554
555 fn dimension(&self) -> usize {
556 self.dimension
557 }
558
559 fn reset(&mut self) {
560 self.current_index = 0;
561 }
562}
563
564pub mod integration {
566 use super::*;
567 use std::sync::{Arc, Mutex};
568 use std::thread;
569
570 #[derive(Debug, Clone)]
572 pub struct QmcIntegrationResult {
573 pub value: f64,
575 pub error: f64,
577 pub evaluations: usize,
579 pub convergence_rate: f64,
581 }
582
583 pub fn qmc_integrate<F>(
585 f: F,
586 bounds: &[(f64, f64)],
587 n_points: usize,
588 sequence_type: QmcSequenceType,
589 ) -> Result<QmcIntegrationResult, QmcError>
590 where
591 F: Fn(&[f64]) -> f64 + Send + Sync,
592 {
593 let dimension = bounds.len();
594 let mut generator = create_qmc_generator(sequence_type, dimension)?;
595
596 let points = generator.generate(n_points);
597 let mut sum = 0.0;
598 let mut sum_sq = 0.0;
599
600 for i in 0..n_points {
602 let mut transformed_point = Vec::with_capacity(dimension);
603 for dim in 0..dimension {
604 let (a, b) = bounds[dim];
605 let x = points[[i, dim]];
606 transformed_point.push(a + x * (b - a));
607 }
608
609 let value = f(&transformed_point);
610 sum += value;
611 sum_sq += value * value;
612 }
613
614 let volume: f64 = bounds.iter().map(|(a, b)| b - a).product();
616
617 let mean = sum / n_points as f64;
619 let variance = (sum_sq / n_points as f64) - (mean * mean);
620 let integral = volume * mean;
621 let error = volume * (variance / n_points as f64).sqrt();
622
623 let convergence_rate = (dimension as f64 * (n_points as f64).ln()) / n_points as f64;
625
626 Ok(QmcIntegrationResult {
627 value: integral,
628 error,
629 evaluations: n_points,
630 convergence_rate,
631 })
632 }
633
634 pub fn parallel_qmc_integrate<F>(
636 f: F,
637 bounds: &[(f64, f64)],
638 n_points: usize,
639 sequence_type: QmcSequenceType,
640 n_threads: usize,
641 ) -> Result<QmcIntegrationResult, QmcError>
642 where
643 F: Fn(&[f64]) -> f64 + Send + Sync + 'static,
644 {
645 let dimension = bounds.len();
646 let points_per_thread = n_points / n_threads;
647 let f = Arc::new(f);
648 let bounds = Arc::new(bounds.to_vec());
649
650 let results = Arc::new(Mutex::new(Vec::new()));
651 let mut handles = Vec::new();
652
653 for thread_id in 0..n_threads {
654 let f_clone = Arc::clone(&f);
655 let bounds_clone = Arc::clone(&bounds);
656 let results_clone = Arc::clone(&results);
657
658 let handle = thread::spawn(move || {
659 let mut generator =
660 create_qmc_generator(sequence_type, dimension).expect("Operation failed");
661 generator.skip(thread_id * points_per_thread);
662
663 let points = generator.generate(points_per_thread);
664 let mut sum = 0.0;
665 let mut sum_sq = 0.0;
666
667 for i in 0..points_per_thread {
668 let mut transformed_point = Vec::with_capacity(dimension);
669 for dim in 0..dimension {
670 let (a, b) = bounds_clone[dim];
671 let x = points[[i, dim]];
672 transformed_point.push(a + x * (b - a));
673 }
674
675 let value = f_clone(&transformed_point);
676 sum += value;
677 sum_sq += value * value;
678 }
679
680 results_clone
681 .lock()
682 .expect("Operation failed")
683 .push((sum, sum_sq));
684 });
685
686 handles.push(handle);
687 }
688
689 for handle in handles {
690 handle.join().expect("Operation failed");
691 }
692
693 let results = results.lock().expect("Operation failed");
694 let (total_sum, total_sum_sq) = results
695 .iter()
696 .fold((0.0, 0.0), |(s, ss), (sum, sum_sq)| (s + sum, ss + sum_sq));
697
698 let volume: f64 = bounds.iter().map(|(a, b)| b - a).product();
699 let mean = total_sum / n_points as f64;
700 let variance = (total_sum_sq / n_points as f64) - (mean * mean);
701 let integral = volume * mean;
702 let error = volume * (variance / n_points as f64).sqrt();
703 let convergence_rate = (dimension as f64 * (n_points as f64).ln()) / n_points as f64;
704
705 Ok(QmcIntegrationResult {
706 value: integral,
707 error,
708 evaluations: n_points,
709 convergence_rate,
710 })
711 }
712}
713
714#[derive(Debug, Clone, Copy, PartialEq, Eq)]
716pub enum QmcSequenceType {
717 Sobol,
719 Halton,
721 Faure,
723 LatinHypercube,
725}
726
727#[allow(dead_code)]
729pub fn create_qmc_generator(
730 sequence_type: QmcSequenceType,
731 dimension: usize,
732) -> Result<Box<dyn LowDiscrepancySequence>, QmcError> {
733 match sequence_type {
734 QmcSequenceType::Sobol => Ok(Box::new(SobolGenerator::dimension(dimension)?)),
735 QmcSequenceType::Halton => Ok(Box::new(HaltonGenerator::dimension(dimension)?)),
736 QmcSequenceType::Faure => Ok(Box::new(FaureGenerator::dimension(dimension)?)),
737 QmcSequenceType::LatinHypercube => {
738 Err(QmcError::UnsupportedDimension(dimension))
741 }
742 }
743}
744
745#[cfg(test)]
746mod tests {
747 use super::*;
748 use approx::assert_abs_diff_eq;
749
750 #[test]
751 fn test_sobol_generator_creation() {
752 let sobol = SobolGenerator::dimension(2);
753 assert!(sobol.is_ok());
754
755 let invalid_sobol = SobolGenerator::dimension(0);
756 assert!(invalid_sobol.is_err());
757 }
758
759 #[test]
760 fn test_sobol_sequence_properties() {
761 let mut sobol = SobolGenerator::dimension(2).expect("Operation failed");
762
763 let first = sobol.next_point();
765 assert_eq!(first, vec![0.0, 0.0]);
766
767 for _ in 0..100 {
769 let point = sobol.next_point();
770 assert_eq!(point.len(), 2);
771 for coord in point {
772 assert!((0.0..1.0).contains(&coord));
773 }
774 }
775 }
776
777 #[test]
778 fn test_halton_generator() {
779 let mut halton = HaltonGenerator::new(&[2, 3]);
780
781 let points = halton.generate(50);
783 assert_eq!(points.nrows(), 50);
784 assert_eq!(points.ncols(), 2);
785
786 for i in 0..50 {
788 for j in 0..2 {
789 let val = points[[i, j]];
790 assert!((0.0..1.0).contains(&val));
791 }
792 }
793 }
794
795 #[test]
796 fn test_halton_primebases() {
797 let halton = HaltonGenerator::dimension(3).expect("Operation failed");
798 assert_eq!(halton.bases, &[2, 3, 5]);
799 }
800
801 #[test]
802 fn test_latin_hypercube_sampling() {
803 let mut lhs = LatinHypercubeSampler::<rand::prelude::ThreadRng>::new(2);
804 let points = lhs.sample(10).expect("Operation failed");
805
806 assert_eq!(points.nrows(), 10);
807 assert_eq!(points.ncols(), 2);
808
809 for dim in 0..2 {
811 let column = points.column(dim);
812 let mut sorted: Vec<f64> = column.to_vec();
813 sorted.sort_by(|a, b| a.partial_cmp(b).expect("Operation failed"));
814
815 for &value in sorted.iter().take(10) {
817 assert!((0.0..=1.0).contains(&value));
818 }
819 }
820 }
821
822 #[test]
823 fn test_faure_generator() {
824 let mut faure = FaureGenerator::dimension(2).expect("Operation failed");
825
826 let points = faure.generate(20);
827 assert_eq!(points.nrows(), 20);
828 assert_eq!(points.ncols(), 2);
829
830 for i in 0..20 {
832 for j in 0..2 {
833 let val = points[[i, j]];
834 assert!((0.0..1.0).contains(&val));
835 }
836 }
837 }
838
839 #[test]
840 fn test_qmc_integration() {
841 use integration::*;
842
843 let result = qmc_integrate(
848 |x| x[0] * x[1],
849 &[(0.0, 1.0), (0.0, 1.0)],
850 10000,
851 QmcSequenceType::Halton,
852 )
853 .expect("Operation failed");
854
855 assert_abs_diff_eq!(result.value, 0.25, epsilon = 0.03);
856 assert!(result.error > 0.0);
857 assert_eq!(result.evaluations, 10000);
858 }
859
860 #[test]
861 fn test_sequence_reset() {
862 let mut sobol = SobolGenerator::dimension(2).expect("Operation failed");
863
864 let first_sequence: Vec<_> = (0..5).map(|_| sobol.next_point()).collect();
865
866 sobol.reset();
867 let second_sequence: Vec<_> = (0..5).map(|_| sobol.next_point()).collect();
868
869 assert_eq!(first_sequence, second_sequence);
870 }
871
872 #[test]
873 fn test_discrepancy_estimation() {
874 let sobol = SobolGenerator::dimension(2).expect("Operation failed");
875 let discrepancy = sobol.estimate_discrepancy(1000);
876
877 assert!(discrepancy > 0.0);
879 assert!(discrepancy < 0.1);
880 }
881}