sc_neurocore_engine/
sobol.rs1#[derive(Clone, Debug)]
15pub struct SobolEngine {
16 state: u32,
17 index: u32,
18 direction: [u32; 32],
19}
20
21impl SobolEngine {
22 pub fn new(seed: u32) -> Self {
24 let mut direction = [0u32; 32];
25 for (i, d) in direction.iter_mut().enumerate() {
26 *d = 1u32 << (31 - i);
27 }
28 Self {
29 state: seed,
30 index: 0,
31 direction,
32 }
33 }
34
35 pub fn sample(&mut self) -> f64 {
37 let c = self.index.trailing_ones() as usize;
38 self.state ^= self.direction[c.min(31)];
39 self.index += 1;
40 self.state as f64 / (1u64 << 32) as f64
41 }
42
43 pub fn reset(&mut self, seed: u32) {
44 self.state = seed;
45 self.index = 0;
46 }
47}
48
49pub fn generate_sobol_bitstream(p: f64, length: usize, seed: u32) -> Vec<u8> {
51 assert!((0.0..=1.0).contains(&p), "p must be in [0,1]");
52 let mut engine = SobolEngine::new(seed);
53 (0..length)
54 .map(|_| if engine.sample() < p { 1u8 } else { 0u8 })
55 .collect()
56}
57
58pub fn generate_sobol_packed(p: f64, length: usize, seed: u32) -> Vec<u64> {
60 let bits = generate_sobol_bitstream(p, length, seed);
61 crate::bitstream::pack_fast(&bits).data
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 #[test]
69 fn deterministic_output() {
70 let a = generate_sobol_bitstream(0.5, 128, 0);
71 let b = generate_sobol_bitstream(0.5, 128, 0);
72 assert_eq!(a, b);
73 }
74
75 #[test]
76 fn p_zero_all_zeros() {
77 let bits = generate_sobol_bitstream(0.0, 256, 42);
78 assert!(bits.iter().all(|&b| b == 0));
79 }
80
81 #[test]
82 fn p_one_all_ones() {
83 let bits = generate_sobol_bitstream(1.0, 256, 42);
84 assert!(bits.iter().all(|&b| b == 1));
85 }
86
87 #[test]
88 fn proportion_close_to_p() {
89 let p = 0.3;
90 let bits = generate_sobol_bitstream(p, 4096, 0);
91 let ones = bits.iter().filter(|&&b| b == 1).count();
92 let ratio = ones as f64 / 4096.0;
93 assert!(
94 (ratio - p).abs() < 0.02,
95 "Sobol ratio {ratio} should be close to {p}"
96 );
97 }
98
99 #[test]
100 fn lower_discrepancy_than_bernoulli() {
101 let bits = generate_sobol_bitstream(0.5, 1024, 0);
103 let ones = bits.iter().filter(|&&b| b == 1).count();
104 let error = (ones as f64 / 1024.0 - 0.5).abs();
105 assert!(error < 0.05, "Sobol discrepancy {error} too high");
106 }
107
108 #[test]
109 fn packed_roundtrip() {
110 let bits = generate_sobol_bitstream(0.7, 200, 99);
111 let packed = generate_sobol_packed(0.7, 200, 99);
112 let unpacked = crate::bitstream::unpack(&crate::bitstream::BitStreamTensor {
113 data: packed,
114 length: 200,
115 });
116 assert_eq!(bits, unpacked);
117 }
118}