Skip to main content

lds_rs/
sphere_n.rs

1//! Generates points on n-dimensional spheres.
2//!
3//! # Algorithm Overview
4//!
5//! ```text
6//!          VdCorput Sequence
7//!                 |
8//!                 v
9//!   [0,1] ------------------> [0,π] -----> Sphere(n)
10//!                    Mapping      Interpolation
11//! ```
12//!
13//! This module implements sphere generators for arbitrary dimensions using
14//! low-discrepancy sequences.
15
16use crate::{Sphere, VdCorput};
17use std::f64::consts::PI;
18use std::sync::{Arc, LazyLock, Mutex};
19
20/// Simple implementation of numpy.linspace
21fn linspace(start: f64, stop: f64, num: usize) -> Vec<f64> {
22    if num == 1 {
23        return vec![start];
24    }
25    let step = (stop - start) / (num as f64 - 1.0);
26    (0..num).map(|i| start + i as f64 * step).collect()
27}
28
29/// Simple implementation of numpy.interp for 1D interpolation
30fn simple_interp(x: f64, xp: &[f64], yp: &[f64]) -> f64 {
31    if x <= xp[0] {
32        return yp[0];
33    }
34    if x >= xp[xp.len() - 1] {
35        return yp[yp.len() - 1];
36    }
37
38    // Binary search for the interval (O(log n) instead of O(n) linear scan)
39    let i = match xp.binary_search_by(|&v| v.partial_cmp(&x).unwrap()) {
40        Ok(i) => i,
41        Err(i) => i - 1,
42    };
43
44    // Linear interpolation
45    let t = (x - xp[i]) / (xp[i + 1] - xp[i]);
46    yp[i] + t * (yp[i + 1] - yp[i])
47}
48
49/// Precomputed tables for sphere generation
50struct SphereTables {
51    x: Vec<f64>,
52    neg_cosine: Vec<f64>,
53    sine: Vec<f64>,
54    f2: Vec<f64>,
55    half_pi: f64,
56}
57
58impl SphereTables {
59    fn new() -> Self {
60        let x = linspace(0.0, PI, 300);
61        let neg_cosine = x.iter().map(|&x| -x.cos()).collect();
62        let sine = x.iter().map(|&x| x.sin()).collect();
63        let f2 = x
64            .iter()
65            .zip(&neg_cosine)
66            .zip(&sine)
67            .map(|((&x, &nc), &s)| (x + nc * s) / 2.0)
68            .collect();
69        let half_pi = PI / 2.0;
70
71        Self {
72            x,
73            neg_cosine,
74            sine,
75            f2,
76            half_pi,
77        }
78    }
79
80    fn get(&self) -> (&[f64], &[f64], &[f64], &[f64], f64) {
81        (
82            &self.x,
83            &self.neg_cosine,
84            &self.sine,
85            &self.f2,
86            self.half_pi,
87        )
88    }
89}
90
91/// Thread-safe cached sphere tables
92static SPHERE_TABLES: LazyLock<SphereTables> = LazyLock::new(SphereTables::new);
93
94const TP_CACHE_MAX: usize = 64;
95
96/// Shared cached reference-counted tp tables (bounded to prevent unbounded growth).
97fn tp_cache_get(n: usize) -> Arc<[f64]> {
98    static TP_CACHE: LazyLock<Mutex<Vec<Arc<[f64]>>>> = LazyLock::new(|| Mutex::new(Vec::new()));
99
100    let mut cache = TP_CACHE.lock().unwrap();
101
102    while cache.len() <= n && cache.len() < TP_CACHE_MAX {
103        let new_n = cache.len();
104        let x = &SPHERE_TABLES.x;
105        let neg_cosine = &SPHERE_TABLES.neg_cosine;
106        let sine = &SPHERE_TABLES.sine;
107
108        let tp: Arc<[f64]> = if new_n == 0 {
109            Arc::from(x.clone().into_boxed_slice())
110        } else if new_n == 1 {
111            Arc::from(neg_cosine.clone().into_boxed_slice())
112        } else {
113            let tp_minus2 = &cache[new_n - 2];
114            let v: Vec<f64> = x
115                .iter()
116                .enumerate()
117                .map(|(i, _xi)| {
118                    ((new_n - 1) as f64 * tp_minus2[i]
119                        + neg_cosine[i] * sine[i].powi((new_n - 1) as i32))
120                        / new_n as f64
121                })
122                .collect();
123            Arc::from(v.into_boxed_slice())
124        };
125        cache.push(tp);
126    }
127
128    if n < cache.len() {
129        cache[n].clone()
130    } else {
131        compute_tp_arc(n)
132    }
133}
134
135/// Compute tp(n) from scratch without using the cache (slow path for n >= TP_CACHE_MAX).
136///
137/// Tp(n) depends only on Tp(n-2), so we iterate by step 2 from the
138/// appropriate parity base case (Tp(0)=X for even n, Tp(1)=neg_cosine for odd).
139fn compute_tp_arc(n: usize) -> Arc<[f64]> {
140    if n == 0 {
141        return Arc::from(SPHERE_TABLES.x.clone().into_boxed_slice());
142    }
143    if n == 1 {
144        return Arc::from(SPHERE_TABLES.neg_cosine.clone().into_boxed_slice());
145    }
146
147    let x = &SPHERE_TABLES.x;
148    let neg_cosine = &SPHERE_TABLES.neg_cosine;
149    let sine = &SPHERE_TABLES.sine;
150
151    let even = n.is_multiple_of(2);
152    let mut prev: Vec<f64> = if even {
153        x.to_vec()
154    } else {
155        neg_cosine.to_vec()
156    };
157    let start: usize = if even { 2 } else { 3 };
158
159    for k in (start..=n).step_by(2) {
160        let tp_k: Vec<f64> = x
161            .iter()
162            .enumerate()
163            .map(|(i, _xi)| {
164                ((k - 1) as f64 * prev[i] + neg_cosine[i] * sine[i].powi((k - 1) as i32)) / k as f64
165            })
166            .collect();
167        if k == n {
168            return Arc::from(tp_k.into_boxed_slice());
169        }
170        prev = tp_k;
171    }
172    unreachable!()
173}
174
175/// Base trait for sphere generators
176///
177/// This trait defines the common interface for all sphere sequence generators.
178/// Implementors must be thread-safe (`Send + Sync`).
179pub trait SphereGen: Send + Sync {
180    /// Generates and returns the next point on the n-sphere
181    ///
182    /// Returns a vector of f64 values representing coordinates on the sphere.
183    /// The dimension of the output depends on the specific implementation.
184    fn pop(&mut self) -> Vec<f64>;
185
186    /// Reseeds the generator with a new seed
187    ///
188    /// # Arguments
189    ///
190    /// * `seed` - The seed value that determines the starting point of the sequence
191    fn reseed(&mut self, seed: u64);
192}
193
194impl SphereGen for Sphere {
195    fn pop(&mut self) -> Vec<f64> {
196        Sphere::pop(self).to_vec()
197    }
198
199    fn reseed(&mut self, seed: u64) {
200        Sphere::reseed(self, seed);
201    }
202}
203
204/// 3-Sphere sequence generator
205///
206/// # Examples
207///
208/// ```
209/// use lds_rs::sphere_n::{Sphere3, SphereGen};
210/// let mut sgen = Sphere3::new(&[2, 3, 5]);
211/// sgen.reseed(0);
212/// let point = sgen.pop();
213/// assert_eq!(point.len(), 4);
214/// ```
215pub struct Sphere3 {
216    vdc: VdCorput,
217    sphere2: Sphere,
218}
219
220impl Sphere3 {
221    /// Creates a new 3-Sphere generator
222    ///
223    /// # Arguments
224    ///
225    /// * `base` - Array of 3 integers used as bases for the sequence
226    pub fn new(base: &[u64]) -> Self {
227        assert!(base.len() >= 3, "Sphere3 requires at least 3 bases");
228        Self {
229            vdc: VdCorput::new(base[0]),
230            sphere2: Sphere::new([base[1], base[2]]),
231        }
232    }
233
234    /// Returns the current index (number of points generated so far)
235    pub fn get_index(&self) -> u64 {
236        self.vdc.get_index()
237    }
238
239    /// Advances the sequence by `n` points without computing them
240    ///
241    /// # Arguments
242    ///
243    /// * `n` - The number of points to advance
244    pub fn advance(&self, n: u64) {
245        self.vdc.advance(n);
246        self.sphere2.advance(n);
247    }
248}
249
250impl SphereGen for Sphere3 {
251    /// Next point on $$ S^3 $$ using the covariance-mapping technique.
252    ///
253    /// $$ \theta = \frac{\pi}{2} v, \quad \chi = F_2^{-1}(\theta), \quad (\sin\chi \cdot \mathbf{s},\; \cos\chi) $$
254    ///
255    /// where $$ \mathbf{s} \in S^2 $$ is a uniform point on the 2-sphere
256    /// and $$ F_2(\chi) $$ is the marginal CDF for dimension 2.
257    fn pop(&mut self) -> Vec<f64> {
258        let tables = SPHERE_TABLES.get();
259        let ti = tables.4 * self.vdc.pop();
260        let xi = simple_interp(ti, tables.3, tables.0);
261        let cosxi = xi.cos();
262        let sinxi = xi.sin();
263
264        let sphere2_point = self.sphere2.pop();
265        let mut result = Vec::with_capacity(4);
266        for &s in &sphere2_point {
267            result.push(sinxi * s);
268        }
269        result.push(cosxi);
270        result
271    }
272
273    fn reseed(&mut self, seed: u64) {
274        self.vdc.reseed(seed);
275        self.sphere2.reseed(seed);
276    }
277}
278
279/// Sphere-N sequence generator for arbitrary dimensions
280///
281/// # Examples
282///
283/// ```
284/// use lds_rs::sphere_n::{SphereN, SphereGen};
285/// let mut sgen = SphereN::new(&[2, 3, 5, 7]);
286/// sgen.reseed(0);
287/// let point = sgen.pop();
288/// assert_eq!(point.len(), 5); // 4 bases produce 5D point
289/// ```
290pub struct SphereN {
291    vdc: VdCorput,
292    s_gen: Box<dyn SphereGen>,
293    n: usize,
294    tp: Arc<[f64]>,
295    tp_start: f64,
296    range: f64,
297}
298
299impl SphereN {
300    /// Creates a new n-sphere generator
301    ///
302    /// # Arguments
303    ///
304    /// * `base` - Array of integers used as bases for the sequence
305    ///   Length must be at least 3 (produces n+1 dimensional sphere)
306    pub fn new(base: &[u64]) -> Self {
307        let n = base.len() - 1;
308        assert!(n >= 2, "SphereN requires at least 3 bases (n >= 2)");
309
310        let vdc = VdCorput::new(base[0]);
311
312        let s_gen: Box<dyn SphereGen> = if n == 2 {
313            Box::new(Sphere::new([base[1], base[2]]))
314        } else {
315            Box::new(SphereN::new(&base[1..]))
316        };
317
318        let tp = tp_cache_get(n);
319        let tp_start = tp[0];
320        let range = tp[tp.len() - 1] - tp_start;
321
322        Self {
323            vdc,
324            s_gen,
325            n,
326            tp,
327            tp_start,
328            range,
329        }
330    }
331}
332
333impl SphereGen for SphereN {
334    /// Next point uniformly distributed on $$ S^{n-1} $$.
335    ///
336    /// $$ \theta = v \in \[0,1\], \quad \chi = T_n^{-1}(\theta), \quad (\sin\chi \cdot \mathbf{s}_{n-2},\; \cos\chi) $$
337    ///
338    /// where $$ \mathbf{s}_{n-2} \in S^{n-2} $$ is generated recursively.
339    fn pop(&mut self) -> Vec<f64> {
340        if self.n == 2 {
341            let tables = SPHERE_TABLES.get();
342            let ti = tables.4 * self.vdc.pop(); // map to [t0, tm-1]
343            let xi = simple_interp(ti, tables.3, tables.0);
344            let cosxi = xi.cos();
345            let sinxi = xi.sin();
346
347            let sphere_point = self.s_gen.pop();
348            let mut result = Vec::with_capacity(sphere_point.len() + 1);
349            for &s in &sphere_point {
350                result.push(sinxi * s);
351            }
352            result.push(cosxi);
353            return result;
354        }
355
356        let vd = self.vdc.pop();
357        let ti = self.tp_start + self.range * vd; // map to [t0, tm-1]
358        let xi = simple_interp(ti, &self.tp, &SPHERE_TABLES.x);
359        let sinphi = xi.sin();
360
361        let sphere_point = self.s_gen.pop();
362        let mut result = Vec::with_capacity(sphere_point.len() + 1);
363        for &s in &sphere_point {
364            result.push(s * sinphi);
365        }
366        result.push(xi.cos());
367        result
368    }
369
370    fn reseed(&mut self, seed: u64) {
371        self.vdc.reseed(seed);
372        self.s_gen.reseed(seed);
373    }
374}
375
376impl SphereN {
377    /// Returns the current index (number of points generated so far)
378    pub fn get_index(&self) -> u64 {
379        self.vdc.get_index()
380    }
381
382    /// Advances the sequence by `n` points without computing them
383    ///
384    /// # Arguments
385    ///
386    /// * `n` - The number of points to advance
387    pub fn advance(&self, n: u64) {
388        self.vdc.advance(n);
389    }
390}
391
392#[cfg(test)]
393mod tests {
394    use super::*;
395    use approx::assert_relative_eq;
396
397    #[test]
398    fn test_linspace() {
399        let result = linspace(0.0, 1.0, 5);
400        let expected = [0.0, 0.25, 0.5, 0.75, 1.0];
401        assert_eq!(result.len(), 5);
402        for i in 0..5 {
403            assert_relative_eq!(result[i], expected[i], epsilon = 1e-10);
404        }
405
406        let result = linspace(0.0, 1.0, 1);
407        assert_eq!(result, vec![0.0]);
408
409        let result = linspace(-1.0, 1.0, 3);
410        let expected = [-1.0, 0.0, 1.0];
411        for i in 0..3 {
412            assert_relative_eq!(result[i], expected[i], epsilon = 1e-10);
413        }
414    }
415
416    #[test]
417    fn test_simple_interp() {
418        let xp = vec![0.0, 1.0, 2.0, 3.0];
419        let yp = vec![0.0, 2.0, 4.0, 6.0]; // Linear function y = 2x
420
421        let result = simple_interp(0.5, &xp, &yp);
422        assert_relative_eq!(result, 1.0, epsilon = 1e-10);
423
424        let result = simple_interp(1.5, &xp, &yp);
425        assert_relative_eq!(result, 3.0, epsilon = 1e-10);
426
427        let result = simple_interp(-0.5, &xp, &yp);
428        assert_relative_eq!(result, 0.0, epsilon = 1e-10);
429
430        let result = simple_interp(3.5, &xp, &yp);
431        assert_relative_eq!(result, 6.0, epsilon = 1e-10);
432
433        let result = simple_interp(2.0, &xp, &yp);
434        assert_relative_eq!(result, 4.0, epsilon = 1e-10);
435    }
436
437    #[test]
438    fn test_get_tp() {
439        let tp0 = tp_cache_get(0);
440        assert_eq!(tp0.len(), 300);
441        assert_relative_eq!(tp0[0], 0.0, epsilon = 1e-10);
442        assert_relative_eq!(tp0[tp0.len() - 1], PI, epsilon = 1e-10);
443
444        let tp1 = tp_cache_get(1);
445        assert_eq!(tp1.len(), 300);
446        assert_relative_eq!(tp1[0], -0.0f64.cos(), epsilon = 1e-10);
447        assert_relative_eq!(tp1[tp1.len() - 1], -PI.cos(), epsilon = 1e-10);
448
449        let tp2 = tp_cache_get(2);
450        assert_eq!(tp2.len(), 300);
451    }
452
453    #[test]
454    fn test_sphere3_basic() {
455        let mut sgen = Sphere3::new(&[2, 3, 5]);
456        sgen.reseed(0);
457
458        let point = sgen.pop();
459        assert_eq!(point.len(), 4);
460
461        let radius_sq = point.iter().map(|&x| x * x).sum::<f64>();
462        assert_relative_eq!(radius_sq, 1.0, epsilon = 1e-10);
463
464        for &coord in &point {
465            assert!((-1.0..=1.0).contains(&coord));
466        }
467    }
468
469    #[test]
470    fn test_sphere3_consistency() {
471        let bases = vec![vec![2, 3, 5], vec![2, 5, 3], vec![3, 2, 7]];
472
473        for base in bases {
474            let mut sgen = Sphere3::new(&base);
475            sgen.reseed(0);
476
477            let points: Vec<_> = (0..5).map(|_| sgen.pop()).collect();
478
479            for point in points.iter() {
480                let radius_sq = point.iter().map(|&x| x * x).sum::<f64>();
481                assert_relative_eq!(radius_sq, 1.0, epsilon = 1e-10);
482
483                for &coord in point {
484                    assert!((-1.0..=1.0).contains(&coord));
485                }
486            }
487        }
488    }
489
490    #[test]
491    fn test_sphere3_reseed() {
492        let mut sgen = Sphere3::new(&[2, 3, 5]);
493
494        sgen.reseed(0);
495        let seq1: Vec<_> = (0..3).map(|_| sgen.pop()).collect();
496
497        sgen.reseed(0);
498        let seq2: Vec<_> = (0..3).map(|_| sgen.pop()).collect();
499
500        for i in 0..3 {
501            for j in 0..4 {
502                assert_relative_eq!(seq1[i][j], seq2[i][j], epsilon = 1e-10);
503            }
504        }
505
506        sgen.reseed(1);
507        let seq3: Vec<_> = (0..3).map(|_| sgen.pop()).collect();
508
509        let mut different = false;
510        for i in 0..3 {
511            for j in 0..4 {
512                if (seq1[i][j] - seq3[i][j]).abs() > 1e-10 {
513                    different = true;
514                    break;
515                }
516            }
517            if different {
518                break;
519            }
520        }
521        assert!(
522            different,
523            "Sequences with different seeds should be different"
524        );
525    }
526
527    #[test]
528    fn test_spheren_basic() {
529        let mut sgen = SphereN::new(&[2, 3, 5, 7]);
530        sgen.reseed(0);
531
532        let point = sgen.pop();
533        assert_eq!(point.len(), 5);
534
535        let radius_sq = point.iter().map(|&x| x * x).sum::<f64>();
536        assert_relative_eq!(radius_sq, 1.0, epsilon = 1e-10);
537    }
538
539    #[test]
540    fn test_spheren_higher_dimensions() {
541        let mut sgen = SphereN::new(&[2, 3, 5, 7, 11]);
542        sgen.reseed(0);
543
544        let point = sgen.pop();
545        assert_eq!(point.len(), 6);
546
547        let radius_sq = point.iter().map(|&x| x * x).sum::<f64>();
548        assert_relative_eq!(radius_sq, 1.0, epsilon = 1e-10);
549    }
550
551    #[test]
552    fn test_spheren_reseed() {
553        let mut sgen = SphereN::new(&[2, 3, 5, 7]);
554
555        sgen.reseed(0);
556        let seq1: Vec<_> = (0..3).map(|_| sgen.pop()).collect();
557
558        sgen.reseed(0);
559        let seq2: Vec<_> = (0..3).map(|_| sgen.pop()).collect();
560
561        for i in 0..3 {
562            for j in 0..5 {
563                assert_relative_eq!(seq1[i][j], seq2[i][j], epsilon = 1e-10);
564            }
565        }
566    }
567
568    #[test]
569    fn test_comparison_with_python() {
570        // Expected values from Python doctest examples
571        let expected_sphere3 = [
572            0.2913440162992141,
573            0.8966646826186098,
574            -0.33333333333333337,
575            6.123233995736766e-17,
576        ];
577
578        let expected_spheren = [
579            0.4809684718990214,
580            0.6031153874276115,
581            -0.5785601510223212,
582            0.2649326520763179,
583            6.123233995736766e-17,
584        ];
585
586        let mut sgen3 = Sphere3::new(&[2, 3, 5]);
587        sgen3.reseed(0);
588        let result3 = sgen3.pop();
589
590        for i in 0..4 {
591            assert_relative_eq!(result3[i], expected_sphere3[i], epsilon = 1e-10);
592        }
593
594        let mut sgen_n = SphereN::new(&[2, 3, 5, 7]);
595        sgen_n.reseed(0);
596        let result_n = sgen_n.pop();
597
598        for i in 0..5 {
599            assert_relative_eq!(result_n[i], expected_spheren[i], epsilon = 1e-10);
600        }
601    }
602
603    #[test]
604    #[should_panic(expected = "Sphere3 requires at least 3 bases")]
605    fn test_sphere3_insufficient_bases() {
606        Sphere3::new(&[2, 3]);
607    }
608
609    #[test]
610    #[should_panic(expected = "SphereN requires at least 3 bases")]
611    fn test_spheren_insufficient_bases() {
612        SphereN::new(&[2, 3]);
613    }
614
615    #[test]
616    fn test_sphere_tables_thread_safety() {
617        use std::sync::{Arc, Barrier};
618        use std::thread;
619
620        let num_threads = 8;
621        let barrier = Arc::new(Barrier::new(num_threads));
622        let mut handles = vec![];
623
624        for _ in 0..num_threads {
625            let barrier_clone = Arc::clone(&barrier);
626            let handle = thread::spawn(move || {
627                // Wait for all threads to be ready
628                barrier_clone.wait();
629
630                // All threads access SPHERE_TABLES simultaneously
631                let tables = SPHERE_TABLES.get();
632                assert_eq!(tables.0.len(), 300); // x table
633                assert_eq!(tables.1.len(), 300); // neg_cosine table
634                assert_eq!(tables.2.len(), 300); // sine table
635                assert_eq!(tables.3.len(), 300); // f2 table
636                assert_eq!(tables.4, std::f64::consts::PI / 2.0); // half_pi
637            });
638            handles.push(handle);
639        }
640
641        for handle in handles {
642            handle.join().unwrap();
643        }
644    }
645
646    #[test]
647    fn test_get_tp_cache_thread_safety() {
648        use std::sync::{Arc, Barrier};
649        use std::thread;
650
651        let num_threads = 8;
652        let barrier = Arc::new(Barrier::new(num_threads));
653        let mut handles = vec![];
654
655        for thread_id in 0..num_threads {
656            let barrier_clone = Arc::clone(&barrier);
657            let handle = thread::spawn(move || {
658                barrier_clone.wait();
659
660                // Each thread requests different tp values
661                let n = thread_id % 5; // Request tp values 0-4
662                let tp = tp_cache_get(n);
663
664                // Verify the returned tp values
665                assert_eq!(tp.len(), 300);
666
667                // For n=0, tp is x which ranges from 0 to PI
668                // For n=1, tp is neg_cosine which ranges from -1 to 1
669                // For n>1, tp can have different ranges
670                if n == 0 {
671                    assert!(tp[0] >= 0.0 && tp[0] <= std::f64::consts::PI);
672                    assert!(tp[tp.len() - 1] >= 0.0 && tp[tp.len() - 1] <= std::f64::consts::PI);
673                } else if n == 1 {
674                    assert!(tp[0] >= -1.0 && tp[0] <= 1.0);
675                    assert!(tp[tp.len() - 1] >= -1.0 && tp[tp.len() - 1] <= 1.0);
676                }
677                // For n>1, we just check that the values are finite
678                for &val in &*tp {
679                    assert!(val.is_finite());
680                }
681            });
682            handles.push(handle);
683        }
684
685        for handle in handles {
686            handle.join().unwrap();
687        }
688    }
689
690    #[test]
691    fn test_sphere3_concurrent_access() {
692        use std::sync::{Arc, Mutex};
693        use std::thread;
694
695        let sgen = Arc::new(Mutex::new(Sphere3::new(&[2, 3, 5])));
696        sgen.lock().unwrap().reseed(0);
697
698        let mut handles = vec![];
699        let results = Arc::new(Mutex::new(Vec::new()));
700
701        for _ in 0..4 {
702            let sgen_clone = Arc::clone(&sgen);
703            let results_clone = Arc::clone(&results);
704
705            let handle = thread::spawn(move || {
706                let mut local_points = Vec::new();
707                for _ in 0..5 {
708                    let mut generator = sgen_clone.lock().unwrap();
709                    let point = generator.pop();
710                    local_points.push(point);
711                }
712                let mut results = results_clone.lock().unwrap();
713                results.push(local_points);
714            });
715
716            handles.push(handle);
717        }
718
719        for handle in handles {
720            handle.join().unwrap();
721        }
722
723        let results = results.lock().unwrap();
724        assert_eq!(results.len(), 4);
725
726        for thread_results in results.iter() {
727            assert_eq!(thread_results.len(), 5);
728            for point in thread_results {
729                assert_eq!(point.len(), 4);
730                let radius_sq = point.iter().map(|&x| x * x).sum::<f64>();
731                assert_relative_eq!(radius_sq, 1.0, epsilon = 1e-10);
732            }
733        }
734    }
735
736    #[test]
737    fn test_spheren_concurrent_access() {
738        use std::sync::{Arc, Mutex};
739        use std::thread;
740
741        let sgen = Arc::new(Mutex::new(SphereN::new(&[2, 3, 5, 7, 11])));
742        sgen.lock().unwrap().reseed(0);
743
744        let mut handles = vec![];
745        let results = Arc::new(Mutex::new(Vec::new()));
746
747        for _ in 0..4 {
748            let sgen_clone = Arc::clone(&sgen);
749            let results_clone = Arc::clone(&results);
750
751            let handle = thread::spawn(move || {
752                let mut local_points = Vec::new();
753                for _ in 0..3 {
754                    let mut generator = sgen_clone.lock().unwrap();
755                    let point = generator.pop();
756                    local_points.push(point);
757                }
758                let mut results = results_clone.lock().unwrap();
759                results.push(local_points);
760            });
761
762            handles.push(handle);
763        }
764
765        for handle in handles {
766            handle.join().unwrap();
767        }
768
769        let results = results.lock().unwrap();
770        assert_eq!(results.len(), 4);
771
772        for thread_results in results.iter() {
773            assert_eq!(thread_results.len(), 3);
774            for point in thread_results {
775                assert_eq!(point.len(), 6);
776                let radius_sq = point.iter().map(|&x| x * x).sum::<f64>();
777                assert_relative_eq!(radius_sq, 1.0, epsilon = 1e-10);
778            }
779        }
780    }
781
782    #[test]
783    fn test_multiple_sphere_instances_concurrent() {
784        use std::sync::{Arc, Barrier};
785        use std::thread;
786
787        let num_threads = 6;
788        let barrier = Arc::new(Barrier::new(num_threads));
789        let mut handles = vec![];
790
791        for thread_id in 0..num_threads {
792            let barrier_clone = Arc::clone(&barrier);
793            let handle = thread::spawn(move || {
794                barrier_clone.wait();
795
796                // Each thread creates its own sphere generator
797                let bases = match thread_id % 3 {
798                    0 => &[2, 3, 5][..],
799                    1 => &[3, 5, 7][..],
800                    _ => &[5, 7, 11][..],
801                };
802
803                let mut sgen: Box<dyn SphereGen> = if thread_id < 3 {
804                    Box::new(Sphere3::new(bases))
805                } else {
806                    Box::new(SphereN::new(&[bases[0], bases[1], bases[2], 13]))
807                };
808
809                sgen.reseed(thread_id as u64);
810
811                // Generate points
812                for _ in 0..5 {
813                    let point = sgen.pop();
814                    let radius_sq = point.iter().map(|&x| x * x).sum::<f64>();
815                    assert_relative_eq!(radius_sq, 1.0, epsilon = 1e-10);
816                }
817            });
818
819            handles.push(handle);
820        }
821
822        for handle in handles {
823            handle.join().unwrap();
824        }
825    }
826
827    #[test]
828    fn test_sphere_trait_send_sync() {
829        // This test verifies that SphereGen trait objects are Send + Sync
830        fn is_send_sync<T: Send + Sync>() {}
831
832        is_send_sync::<Sphere3>();
833        is_send_sync::<SphereN>();
834
835        // Verify trait objects are Send
836        let mut sgen3: Box<dyn SphereGen> = Box::new(Sphere3::new(&[2, 3, 5]));
837        let mut sgen_n: Box<dyn SphereGen> = Box::new(SphereN::new(&[2, 3, 5, 7]));
838
839        // These operations should compile if Send is implemented
840        sgen3.reseed(0);
841        sgen_n.reseed(0);
842
843        let _point3 = sgen3.pop();
844        let _point_n = sgen_n.pop();
845    }
846
847    // Additional comprehensive tests for edge cases and higher dimensions
848
849    #[test]
850    fn test_linspace_edge_cases() {
851        // Test with start = stop
852        let result = linspace(1.0, 1.0, 5);
853        assert_eq!(result, vec![1.0, 1.0, 1.0, 1.0, 1.0]);
854
855        // Test with negative range
856        let result = linspace(-1.0, -0.5, 3);
857        let expected = [-1.0, -0.75, -0.5];
858        for i in 0..3 {
859            assert_relative_eq!(result[i], expected[i], epsilon = 1e-10);
860        }
861
862        // Test with large number of points
863        let result = linspace(0.0, 1.0, 1000);
864        assert_eq!(result.len(), 1000);
865        assert_relative_eq!(result[0], 0.0, epsilon = 1e-10);
866        assert_relative_eq!(result[999], 1.0, epsilon = 1e-10);
867        assert_relative_eq!(result[500], 0.5005005005005005, epsilon = 1e-10);
868    }
869
870    #[test]
871    fn test_simple_interp_edge_cases() {
872        // Test with single point
873        let xp = vec![0.5];
874        let yp = vec![1.0];
875        let result = simple_interp(0.5, &xp, &yp);
876        assert_relative_eq!(result, 1.0, epsilon = 1e-10);
877
878        // Test with constant function
879        let xp = vec![0.0, 1.0, 2.0];
880        let yp = vec![5.0, 5.0, 5.0];
881        let result = simple_interp(1.5, &xp, &yp);
882        assert_relative_eq!(result, 5.0, epsilon = 1e-10);
883
884        // Test with non-uniform x points
885        let xp = vec![0.0, 0.1, 0.5, 2.0];
886        let yp = vec![0.0, 1.0, 2.0, 3.0];
887        let result = simple_interp(0.3, &xp, &yp);
888        // Should interpolate between points (0.1, 1.0) and (0.5, 2.0)
889        let expected = 1.0 + (0.3 - 0.1) / (0.5 - 0.1) * (2.0 - 1.0);
890        assert_relative_eq!(result, expected, epsilon = 1e-10);
891    }
892
893    #[test]
894    fn test_sphere_n_higher_dimensions() {
895        // Test with 10 dimensions (11 bases)
896        let bases: Vec<u64> = (2..=12).collect();
897        let mut sgen = SphereN::new(&bases);
898        sgen.reseed(0);
899
900        let point = sgen.pop();
901        assert_eq!(point.len(), 12); // n+1 dimensions where n = 11
902
903        let radius_sq = point.iter().map(|&x| x * x).sum::<f64>();
904        assert_relative_eq!(radius_sq, 1.0, epsilon = 1e-10);
905
906        // Test with 20 dimensions (21 bases)
907        let bases: Vec<u64> = (2..=22).collect();
908        let mut sgen = SphereN::new(&bases);
909        sgen.reseed(0);
910
911        let point = sgen.pop();
912        assert_eq!(point.len(), 22); // n+1 dimensions where n = 21
913
914        let radius_sq = point.iter().map(|&x| x * x).sum::<f64>();
915        assert_relative_eq!(radius_sq, 1.0, epsilon = 1e-10);
916    }
917
918    #[test]
919    fn test_sphere_n_recursive_structure() {
920        // Test that SphereN with 4 bases wraps a Sphere3
921        let mut sgen4 = SphereN::new(&[2, 3, 5, 7]);
922        sgen4.reseed(0);
923
924        let point4 = sgen4.pop();
925        assert_eq!(point4.len(), 5);
926
927        // Test that SphereN with 5 bases wraps a SphereN with 4 bases
928        let mut sgen5 = SphereN::new(&[2, 3, 5, 7, 11]);
929        sgen5.reseed(0);
930
931        let point5 = sgen5.pop();
932        assert_eq!(point5.len(), 6);
933
934        // Both should be on unit sphere
935        let radius_sq4 = point4.iter().map(|&x| x * x).sum::<f64>();
936        let radius_sq5 = point5.iter().map(|&x| x * x).sum::<f64>();
937        assert_relative_eq!(radius_sq4, 1.0, epsilon = 1e-10);
938        assert_relative_eq!(radius_sq5, 1.0, epsilon = 1e-10);
939    }
940
941    #[test]
942    fn test_sphere_tables_properties() {
943        let tables = SPHERE_TABLES.get();
944        let (x, neg_cosine, sine, f2, half_pi) = tables;
945
946        // Check table lengths
947        assert_eq!(x.len(), 300);
948        assert_eq!(neg_cosine.len(), 300);
949        assert_eq!(sine.len(), 300);
950        assert_eq!(f2.len(), 300);
951
952        // Check half_pi value
953        assert_relative_eq!(half_pi, PI / 2.0, epsilon = 1e-10);
954
955        // Check x table ranges from 0 to PI
956        assert_relative_eq!(x[0], 0.0, epsilon = 1e-10);
957        assert_relative_eq!(x[299], PI, epsilon = 1e-10);
958
959        // Check neg_cosine is -cos(x)
960        for i in 0..300 {
961            assert_relative_eq!(neg_cosine[i], -x[i].cos(), epsilon = 1e-10);
962        }
963
964        // Check sine is sin(x)
965        for i in 0..300 {
966            assert_relative_eq!(sine[i], x[i].sin(), epsilon = 1e-10);
967        }
968
969        // Check f2 formula: (x + neg_cosine * sine) / 2.0
970        for i in 0..300 {
971            let expected = (x[i] + neg_cosine[i] * sine[i]) / 2.0;
972            assert_relative_eq!(f2[i], expected, epsilon = 1e-10);
973        }
974    }
975
976    #[test]
977    fn test_get_tp_higher_dimensions() {
978        // Test tp values for higher dimensions
979        let tp5 = tp_cache_get(5);
980        assert_eq!(tp5.len(), 300);
981
982        let tp10 = tp_cache_get(10);
983        assert_eq!(tp10.len(), 300);
984
985        let tp20 = tp_cache_get(20);
986        assert_eq!(tp20.len(), 300);
987
988        // All values should be finite
989        for &val in &*tp5 {
990            assert!(val.is_finite());
991        }
992        for &val in &*tp10 {
993            assert!(val.is_finite());
994        }
995        for &val in &*tp20 {
996            assert!(val.is_finite());
997        }
998    }
999
1000    #[test]
1001    fn test_sphere_sequence_distribution() {
1002        // Test that points are well-distributed (basic check)
1003        let mut sgen = SphereN::new(&[2, 3, 5, 7, 11, 13]);
1004        sgen.reseed(0);
1005
1006        let mut points = Vec::new();
1007        for _ in 0..100 {
1008            points.push(sgen.pop());
1009        }
1010
1011        // All points should be on unit sphere
1012        for point in &points {
1013            let radius_sq = point.iter().map(|&x| x * x).sum::<f64>();
1014            assert_relative_eq!(radius_sq, 1.0, epsilon = 1e-10);
1015        }
1016
1017        // Points should be different (basic diversity check)
1018        for i in 1..points.len() {
1019            let mut same = true;
1020            for (j, &coord) in points[i].iter().enumerate() {
1021                if (coord - points[0][j]).abs() > 1e-10 {
1022                    same = false;
1023                    break;
1024                }
1025            }
1026            if i < 10 {
1027                assert!(!same, "First few points should be different");
1028            }
1029        }
1030    }
1031
1032    #[test]
1033    fn test_sphere_spheregen_impl() {
1034        let mut sgen: Box<dyn SphereGen> = Box::new(Sphere::new([2, 3]));
1035        sgen.reseed(0);
1036
1037        let point = sgen.pop();
1038        assert_eq!(point.len(), 3);
1039
1040        let radius_sq = point.iter().map(|&x| x * x).sum::<f64>();
1041        assert_relative_eq!(radius_sq, 1.0, epsilon = 1e-10);
1042
1043        // Test reseed
1044        sgen.reseed(0);
1045        let point1 = sgen.pop();
1046
1047        sgen.reseed(0);
1048        let point2 = sgen.pop();
1049
1050        for i in 0..3 {
1051            assert_relative_eq!(point1[i], point2[i], epsilon = 1e-10);
1052        }
1053    }
1054
1055    #[test]
1056    fn test_sphere_n_different_bases() {
1057        // Test with different prime bases
1058        let bases = vec![vec![3, 5, 7, 11], vec![5, 7, 11, 13], vec![7, 11, 13, 17]];
1059
1060        for base in bases {
1061            let mut sgen = SphereN::new(&base);
1062            sgen.reseed(0);
1063
1064            let point = sgen.pop();
1065            assert_eq!(point.len(), base.len() + 1); // n+1 dimensions where n = base.len()
1066
1067            let radius_sq = point.iter().map(|&x| x * x).sum::<f64>();
1068            assert_relative_eq!(radius_sq, 1.0, epsilon = 1e-10);
1069        }
1070    }
1071
1072    #[test]
1073    fn test_sphere3_different_bases() {
1074        // Test with different prime bases
1075        let bases = vec![vec![3, 5, 7], vec![5, 7, 11], vec![7, 11, 13]];
1076
1077        for base in bases {
1078            let mut sgen = Sphere3::new(&base);
1079            sgen.reseed(0);
1080
1081            let point = sgen.pop();
1082            assert_eq!(point.len(), 4);
1083
1084            let radius_sq = point.iter().map(|&x| x * x).sum::<f64>();
1085            assert_relative_eq!(radius_sq, 1.0, epsilon = 1e-10);
1086        }
1087    }
1088
1089    #[test]
1090    fn test_sphere_n_large_seed() {
1091        // Test with large seed values
1092        let mut sgen = SphereN::new(&[2, 3, 5, 7]);
1093
1094        for seed in [0, 100, 1000, 10000, 100000] {
1095            sgen.reseed(seed);
1096            let point = sgen.pop();
1097
1098            let radius_sq = point.iter().map(|&x| x * x).sum::<f64>();
1099            assert_relative_eq!(radius_sq, 1.0, epsilon = 1e-10);
1100
1101            for coord in point {
1102                assert!(coord.is_finite());
1103                assert!((-1.0..=1.0).contains(&coord));
1104            }
1105        }
1106    }
1107
1108    #[test]
1109    fn test_sphere_coordinate_bounds() {
1110        // Test that all coordinates are within expected bounds
1111        let mut sgen = SphereN::new(&[2, 3, 5, 7, 11, 13, 17]);
1112        sgen.reseed(0);
1113
1114        for _ in 0..100 {
1115            let point = sgen.pop();
1116
1117            for &coord in &point {
1118                assert!((-1.0..=1.0).contains(&coord));
1119                assert!(coord.is_finite());
1120            }
1121        }
1122    }
1123
1124    #[test]
1125    fn test_sphere3_get_index() {
1126        let mut sgen = Sphere3::new(&[2, 3, 5]);
1127        assert_eq!(sgen.get_index(), 0);
1128        sgen.pop();
1129        assert_eq!(sgen.get_index(), 1);
1130    }
1131
1132    #[test]
1133    fn test_spheren_get_index() {
1134        let mut sgen = SphereN::new(&[2, 3, 5, 7]);
1135        assert_eq!(sgen.get_index(), 0);
1136        sgen.pop();
1137        assert_eq!(sgen.get_index(), 1);
1138    }
1139
1140    #[test]
1141    fn test_sphere3_skip() {
1142        let mut sgen = Sphere3::new(&[2, 3, 5]);
1143        sgen.reseed(0);
1144        sgen.advance(5);
1145        let point = sgen.pop();
1146        let radius_sq = point.iter().map(|&x| x * x).sum::<f64>();
1147        assert_relative_eq!(radius_sq, 1.0, epsilon = 1e-10);
1148    }
1149
1150    #[test]
1151    fn test_spheren_skip() {
1152        let mut sgen = SphereN::new(&[2, 3, 5, 7]);
1153        sgen.reseed(0);
1154        sgen.advance(5);
1155        let point = sgen.pop();
1156        let radius_sq = point.iter().map(|&x| x * x).sum::<f64>();
1157        assert_relative_eq!(radius_sq, 1.0, epsilon = 1e-10);
1158    }
1159
1160    #[test]
1161    fn test_struct_sizes() {
1162        use std::mem::size_of;
1163        assert_eq!(size_of::<Sphere3>(), 120);
1164        assert_eq!(size_of::<SphereN>(), 96);
1165    }
1166
1167    #[test]
1168    fn test_arc_sharing() {
1169        let _s1 = Sphere3::new(&[2, 3, 5]);
1170        let _s2 = Sphere3::new(&[3, 5, 7]);
1171        assert_eq!(SPHERE_TABLES.x.len(), 300);
1172
1173        let tp4 = tp_cache_get(3);
1174        let _n1 = SphereN::new(&[2, 3, 5, 7]);
1175        let _n2 = SphereN::new(&[3, 5, 7, 11]);
1176        assert_eq!(tp4.len(), 300);
1177    }
1178}