Skip to main content

scirs2_core/random/
qmc.rs

1//! # Quasi-Monte Carlo Sequences
2//!
3//! This module provides implementations of low-discrepancy sequences and other
4//! quasi-Monte Carlo (QMC) methods for more efficient Monte Carlo integration
5//! and sampling compared to traditional pseudo-random sequences.
6//!
7//! ## Features
8//!
9//! * **Sobol sequences**: Multi-dimensional low-discrepancy sequences
10//! * **Halton sequences**: Simple low-discrepancy sequences based on prime bases
11//! * **Latin hypercube sampling**: Stratified sampling for space-filling designs
12//! * **Faure sequences**: Another family of low-discrepancy sequences
13//! * **Niederreiter sequences**: Generalization of Sobol sequences
14//!
15//! ## Usage
16//!
17//! ```rust
18//! use scirs2_core::random::qmc::{SobolGenerator, HaltonGenerator, LatinHypercubeSampler, LowDiscrepancySequence};
19//!
20//! fn example() -> Result<(), Box<dyn std::error::Error>> {
21//!     // Generate Sobol sequence in 2D
22//!     let mut sobol = SobolGenerator::dimension(2)?;
23//!     let points = sobol.generate(1000);
24//!
25//!     // Generate Halton sequence
26//!     let mut halton = HaltonGenerator::new(&[2, 3]);
27//!     let points = halton.generate(1000);
28//!
29//!     // Latin hypercube sampling
30//!     let mut lhs = LatinHypercubeSampler::<rand::prelude::ThreadRng>::new(2);
31//!     let points = lhs.sample(100)?;
32//!     
33//!     Ok(())
34//! }
35//! # example().expect("Operation failed");
36//! ```
37
38use ::ndarray::{Array1, Array2};
39use rand::Rng;
40use std::f64;
41use thiserror::Error;
42
43/// Error types for QMC operations
44#[derive(Error, Debug)]
45pub enum QmcError {
46    /// Invalid dimension
47    #[error("Invalid dimension: {0}. Must be between 1 and {1}")]
48    InvalidDimension(usize, usize),
49
50    /// Invalid number of points
51    #[error("Invalid number of points: {0}. Must be positive")]
52    InvalidPointCount(usize),
53
54    /// Sequence initialization failed
55    #[error("Sequence initialization failed: {0}")]
56    InitializationFailed(String),
57
58    /// Unsupported dimension for this sequence type
59    #[error("Unsupported dimension {0} for sequence type")]
60    UnsupportedDimension(usize),
61
62    /// Invalid base for sequence generation
63    #[error("Invalid base: {0}. Must be prime and greater than 1")]
64    InvalidBase(u32),
65}
66
67/// Low-discrepancy sequence trait
68pub trait LowDiscrepancySequence {
69    /// Generate the next point in the sequence
70    fn next_point(&mut self) -> Vec<f64>;
71
72    /// Generate multiple points at once
73    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    /// Get the dimension of the sequence
88    fn dimension(&self) -> usize;
89
90    /// Reset the sequence to the beginning
91    fn reset(&mut self);
92
93    /// Skip ahead in the sequence
94    fn skip(&mut self, n: usize) {
95        for _ in 0..n {
96            self.next_point();
97        }
98    }
99}
100
101/// Sobol sequence generator
102///
103/// Generates points in `[0,1]^d` using the Sobol low-discrepancy sequence.
104/// Excellent for high-dimensional integration and Monte Carlo methods.
105pub 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    /// Maximum supported dimension for Sobol sequences
115    pub const MAX_DIMENSION: usize = 21201;
116
117    /// Create a new Sobol generator for the given dimension
118    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; // Using 64-bit integers, reserve 1 bit for safety
124        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    /// Initialize direction numbers for Sobol sequence
137    fn initialize_direction_numbers(&mut self) -> Result<(), QmcError> {
138        self.direction_numbers.clear();
139
140        // First dimension uses powers of 2
141        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 higher dimensions, we would need primitive polynomials and initial direction numbers
150        // For now, implementing a simplified version with basic polynomials
151        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    /// Generate direction numbers for a specific dimension
160    fn generate_direction_numbers_for_dimension(&self, dim: usize) -> Result<Vec<u32>, QmcError> {
161        // Simplified implementation using basic recurrence relations
162        // In a full implementation, this would use tabulated primitive polynomials
163        let mut direction_nums = Vec::with_capacity(self.max_bits);
164
165        // Use different starting values for different dimensions
166        let base_values = [1, 1, 3, 1, 3, 3, 1];
167        let poly_coeffs = [0, 1, 1, 2, 1, 4, 2]; // Simplified polynomial coefficients
168
169        let start_val = base_values[dim % base_values.len()];
170        let poly_coeff = poly_coeffs[dim % poly_coeffs.len()];
171
172        // Initialize first few values
173        direction_nums.push(start_val << 30);
174        if self.max_bits > 1 {
175            direction_nums.push((start_val * 2 + 1) << 29);
176        }
177
178        // Generate remaining values using recurrence relation
179        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            // Simplified recurrence (real Sobol uses proper polynomial recurrence)
184            let next_val = prev1 ^ (prev2 >> poly_coeff) ^ (prev1 >> 1);
185            direction_nums.push(next_val);
186        }
187
188        Ok(direction_nums)
189    }
190
191    /// Get the discrepancy of the current sequence (quality measure)
192    pub fn estimate_discrepancy(&self, n: usize) -> f64 {
193        // Simplified discrepancy estimation
194        // Real implementation would compute L2 or star discrepancy
195        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        // Find the rightmost zero bit in the current index
208        let rightmost_zero_pos = (!self.current_index).trailing_zeros() as usize;
209
210        // Update current point using Gray code ordering
211        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        // Convert to floating point in [0,1)
220        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
236/// Halton sequence generator
237///
238/// Generates points using the Halton sequence based on coprime bases.
239/// Simpler than Sobol but can have correlation issues in higher dimensions.
240pub struct HaltonGenerator {
241    dimension: usize,
242    bases: Vec<u32>,
243    indices: Vec<u64>,
244}
245
246impl HaltonGenerator {
247    /// Create a new Halton generator with specified bases
248    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    /// Create a Halton generator using the first n prime numbers as bases
258    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    /// Generate the first n prime numbers
268    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    /// Check if a number is prime
283    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    /// Compute the radical inverse in the given base
304    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    /// Get the bases used by this generator
318    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
345/// Latin Hypercube Sampler
346///
347/// Provides stratified sampling that ensures each dimension is evenly divided.
348/// Excellent for design of experiments and space-filling designs.
349pub 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    /// Create a new Latin hypercube sampler
356    pub fn new(dimension: usize) -> LatinHypercubeSampler<rand::prelude::ThreadRng> {
357        LatinHypercubeSampler {
358            dimension,
359            rng: crate::random::Random::default(),
360        }
361    }
362
363    /// Create a Latin hypercube sampler with a specific seed
364    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    /// Generate a Latin hypercube sample
372    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 each dimension, create a permutation of [0, 1, ..., n-1]
380        for dim in 0..self.dimension {
381            let mut permutation: Vec<usize> = (0..n).collect();
382
383            // Fisher-Yates shuffle
384            for i in (1..n).rev() {
385                let j = self.rng.random_range(0..i + 1);
386                permutation.swap(i, j);
387            }
388
389            // Convert to Latin hypercube coordinates
390            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    /// Generate an optimal Latin hypercube using optimization
401    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        // Simple optimization: try multiple random samples and keep the best
406        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    /// Compute the maximin criterion (minimum distance between points)
420    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    /// Compute Euclidean distance between two points
436    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    /// Get the dimension of the sampler
445    pub fn dimension(&self) -> usize {
446        self.dimension
447    }
448}
449
450/// Faure sequence generator
451///
452/// Another family of low-discrepancy sequences based on prime bases.
453pub struct FaureGenerator {
454    dimension: usize,
455    base: u32,
456    current_index: u64,
457    pascalmatrix: Vec<Vec<u32>>,
458}
459
460impl FaureGenerator {
461    /// Create a new Faure generator
462    pub fn dimension(dimension: usize) -> Result<Self, QmcError> {
463        if dimension == 0 {
464            return Err(QmcError::InvalidDimension(dimension, 1000));
465        }
466
467        // Find the smallest prime >= dimension
468        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    /// Find the next prime number >= n
482    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    /// Initialize the Pascal matrix modulo the base
491    fn initialize_pascalmatrix(&mut self) {
492        let size = self.base as usize;
493        self.pascalmatrix = vec![vec![0; size]; size];
494
495        // Initialize Pascal's triangle modulo base
496        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    /// Compute the scrambled radical inverse
515    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            // Apply scrambling based on dimension and Pascal matrix
524            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
564/// QMC integration utilities
565pub mod integration {
566    use super::*;
567    use std::sync::{Arc, Mutex};
568    use std::thread;
569
570    /// Quasi-Monte Carlo integration result
571    #[derive(Debug, Clone)]
572    pub struct QmcIntegrationResult {
573        /// Estimated integral value
574        pub value: f64,
575        /// Estimated standard error
576        pub error: f64,
577        /// Number of function evaluations
578        pub evaluations: usize,
579        /// Convergence rate
580        pub convergence_rate: f64,
581    }
582
583    /// Perform QMC integration using the specified sequence
584    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        // Transform _points to integration bounds and evaluate function
601        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        // Calculate volume of integration region
615        let volume: f64 = bounds.iter().map(|(a, b)| b - a).product();
616
617        // Estimate integral and error
618        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        // Estimate convergence rate (QMC typically achieves O((log n)^d / n))
624        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    /// Parallel QMC integration
635    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/// QMC sequence types
715#[derive(Debug, Clone, Copy, PartialEq, Eq)]
716pub enum QmcSequenceType {
717    /// Sobol sequence
718    Sobol,
719    /// Halton sequence
720    Halton,
721    /// Faure sequence
722    Faure,
723    /// Latin hypercube sampling
724    LatinHypercube,
725}
726
727/// Create a QMC generator of the specified type
728#[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            // Note: LHS doesn't implement LowDiscrepancySequence directly
739            // This is a simplified adapter
740            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        // First point should be [0, 0]
764        let first = sobol.next_point();
765        assert_eq!(first, vec![0.0, 0.0]);
766
767        // Generate some points and check they're in [0,1]^2
768        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        // Generate points and verify properties
782        let points = halton.generate(50);
783        assert_eq!(points.nrows(), 50);
784        assert_eq!(points.ncols(), 2);
785
786        // Check all points are in [0,1]^2
787        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        // Check that each dimension has points spread across [0,1]
810        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            // Should be well-distributed
816            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        // Verify points are in unit cube
831        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        // Test integration of f(x,y) = x*y over [0,1]^2
844        // Analytical result should be 1/4
845        // Note: Sobol sequences can have systematic biases for specific integrands
846        // Using Halton sequence for more reliable results
847        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        // Should be small for low-discrepancy sequences
878        assert!(discrepancy > 0.0);
879        assert!(discrepancy < 0.1);
880    }
881}