scirs2_stats/
sampling_simd.rs1use crate::error::{StatsError, StatsResult};
7use scirs2_core::ndarray::{Array1, ArrayView1};
8use scirs2_core::numeric::{Float, NumCast};
9use scirs2_core::random::uniform::SampleUniform;
10use scirs2_core::random::Rng;
11use scirs2_core::simd_ops::{AutoOptimizer, SimdUnifiedOps};
12
13pub fn box_muller_simd<F>(
38 n: usize,
39 mean: F,
40 std_dev: F,
41 seed: Option<u64>,
42) -> StatsResult<Array1<F>>
43where
44 F: Float + NumCast + SimdUnifiedOps + SampleUniform,
45{
46 if std_dev <= F::zero() {
47 return Err(StatsError::invalid_argument(
48 "Standard deviation must be positive",
49 ));
50 }
51
52 let n_pairs = (n + 1) / 2;
54 let n_total = n_pairs * 2;
55
56 let mut rng = {
57 let s = seed.unwrap_or_else(|| {
58 use scirs2_core::random::Rng;
59 scirs2_core::random::thread_rng().random()
60 });
61 scirs2_core::random::seeded_rng(s)
62 };
63
64 let optimizer = AutoOptimizer::new();
65
66 let u1: Array1<F> = Array1::from_shape_fn(n_pairs, |_| {
68 F::from(rng.gen_range(F::epsilon().to_f64().unwrap_or(1e-10)..1.0))
69 .unwrap_or_else(|| F::one())
70 });
71 let u2: Array1<F> = Array1::from_shape_fn(n_pairs, |_| {
72 F::from(rng.gen_range(0.0..1.0)).unwrap_or_else(|| F::zero())
73 });
74
75 if optimizer.should_use_simd(n_pairs) {
76 let two_pi = F::from(2.0 * std::f64::consts::PI).unwrap_or_else(|| F::one());
78
79 let ln_u1 = F::simd_ln(&u1.view());
81 let minus_two = F::from(-2.0).unwrap_or_else(|| F::one());
82 let minus_two_array = Array1::from_elem(n_pairs, minus_two);
83 let neg_two_ln_u1 = F::simd_mul(&minus_two_array.view(), &ln_u1.view());
84 let sqrt_term = F::simd_sqrt(&neg_two_ln_u1.view());
85
86 let two_pi_array = Array1::from_elem(n_pairs, two_pi);
88 let two_pi_u2 = F::simd_mul(&two_pi_array.view(), &u2.view());
89
90 let cos_term = F::simd_cos(&two_pi_u2.view());
92 let sin_term = F::simd_sin(&two_pi_u2.view());
93
94 let z0 = F::simd_mul(&sqrt_term.view(), &cos_term.view());
97 let z1 = F::simd_mul(&sqrt_term.view(), &sin_term.view());
98
99 let std_dev_array = Array1::from_elem(n_pairs, std_dev);
101 let mean_array = Array1::from_elem(n_pairs, mean);
102
103 let z0_scaled = F::simd_fma(&z0.view(), &std_dev_array.view(), &mean_array.view());
104 let z1_scaled = F::simd_fma(&z1.view(), &std_dev_array.view(), &mean_array.view());
105
106 let mut result = Array1::zeros(n_total);
108 for i in 0..n_pairs {
109 result[2 * i] = z0_scaled[i];
110 if 2 * i + 1 < n_total {
111 result[2 * i + 1] = z1_scaled[i];
112 }
113 }
114
115 Ok(result.slice(scirs2_core::ndarray::s![..n]).to_owned())
117 } else {
118 let mut result = Array1::zeros(n_total);
120 let two_pi = F::from(2.0 * std::f64::consts::PI).unwrap_or_else(|| F::one());
121 let two = F::from(2.0).unwrap_or_else(|| F::one());
122
123 for i in 0..n_pairs {
124 let r = (-two * u1[i].ln()).sqrt();
125 let theta = two_pi * u2[i];
126
127 result[2 * i] = mean + std_dev * r * theta.cos();
128 if 2 * i + 1 < n_total {
129 result[2 * i + 1] = mean + std_dev * r * theta.sin();
130 }
131 }
132
133 Ok(result.slice(scirs2_core::ndarray::s![..n]).to_owned())
134 }
135}
136
137pub fn inverse_transform_simd<F, InvCDF>(
152 n: usize,
153 inverse_cdf: InvCDF,
154 seed: Option<u64>,
155) -> StatsResult<Array1<F>>
156where
157 F: Float + NumCast + SimdUnifiedOps + SampleUniform,
158 InvCDF: Fn(F) -> F,
159{
160 let mut rng = {
161 let s = seed.unwrap_or_else(|| {
162 use scirs2_core::random::Rng;
163 scirs2_core::random::thread_rng().random()
164 });
165 scirs2_core::random::seeded_rng(s)
166 };
167
168 let u: Array1<F> = Array1::from_shape_fn(n, |_| {
170 F::from(rng.gen_range(0.0..1.0)).unwrap_or_else(|| F::zero())
171 });
172
173 let result = u.mapv(|ui| inverse_cdf(ui));
175
176 Ok(result)
177}
178
179pub fn exponential_simd<F>(n: usize, rate: F, seed: Option<u64>) -> StatsResult<Array1<F>>
194where
195 F: Float + NumCast + SimdUnifiedOps + SampleUniform,
196{
197 if rate <= F::zero() {
198 return Err(StatsError::invalid_argument("Rate must be positive"));
199 }
200
201 let mut rng = {
202 let s = seed.unwrap_or_else(|| {
203 use scirs2_core::random::Rng;
204 scirs2_core::random::thread_rng().random()
205 });
206 scirs2_core::random::seeded_rng(s)
207 };
208
209 let optimizer = AutoOptimizer::new();
210
211 let u: Array1<F> = Array1::from_shape_fn(n, |_| {
213 F::from(rng.gen_range(F::epsilon().to_f64().unwrap_or(1e-10)..1.0))
214 .unwrap_or_else(|| F::one())
215 });
216
217 if optimizer.should_use_simd(n) {
218 let ln_u = F::simd_ln(&u.view());
220 let minus_one = F::from(-1.0).unwrap_or_else(|| F::one());
221 let minus_one_array = Array1::from_elem(n, minus_one);
222 let neg_ln_u = F::simd_mul(&minus_one_array.view(), &ln_u.view());
223
224 let inv_rate = F::one() / rate;
225 let inv_rate_array = Array1::from_elem(n, inv_rate);
226 let result = F::simd_mul(&neg_ln_u.view(), &inv_rate_array.view());
227
228 Ok(result)
229 } else {
230 Ok(u.mapv(|ui| -ui.ln() / rate))
232 }
233}
234
235pub fn bootstrap_simd<F>(
250 data: &ArrayView1<F>,
251 n_samples: usize,
252 seed: Option<u64>,
253) -> StatsResult<Array1<F>>
254where
255 F: Float + NumCast + SimdUnifiedOps,
256{
257 if data.is_empty() {
258 return Err(StatsError::invalid_argument("Data array cannot be empty"));
259 }
260
261 let n_data = data.len();
262 let mut rng = {
263 let s = seed.unwrap_or_else(|| {
264 use scirs2_core::random::Rng;
265 scirs2_core::random::thread_rng().random()
266 });
267 scirs2_core::random::seeded_rng(s)
268 };
269
270 let mut result = Array1::zeros(n_samples);
272 for i in 0..n_samples {
273 let idx = rng.gen_range(0..n_data);
274 result[i] = data[idx];
275 }
276
277 Ok(result)
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283 use approx::assert_abs_diff_eq;
284
285 #[test]
286 fn test_box_muller_simd_basic() {
287 let samples = box_muller_simd(1000, 0.0, 1.0, Some(42)).expect("Sampling failed");
288 assert_eq!(samples.len(), 1000);
289
290 let mean: f64 = samples.iter().sum::<f64>() / samples.len() as f64;
292 assert_abs_diff_eq!(mean, 0.0, epsilon = 0.1);
293
294 let variance: f64 =
296 samples.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / samples.len() as f64;
297 assert_abs_diff_eq!(variance.sqrt(), 1.0, epsilon = 0.1);
298 }
299
300 #[test]
301 fn test_exponential_simd_basic() {
302 let rate = 2.0;
303 let samples = exponential_simd(1000, rate, Some(42)).expect("Sampling failed");
304 assert_eq!(samples.len(), 1000);
305
306 let mean: f64 = samples.iter().sum::<f64>() / samples.len() as f64;
308 let expected_mean = 1.0 / rate;
309 assert_abs_diff_eq!(mean, expected_mean, epsilon = 0.1);
310 }
311
312 #[test]
313 fn test_bootstrap_simd_basic() {
314 use scirs2_core::ndarray::array;
315
316 let data = array![1.0, 2.0, 3.0, 4.0, 5.0];
317 let samples = bootstrap_simd(&data.view(), 100, Some(42)).expect("Bootstrap failed");
318 assert_eq!(samples.len(), 100);
319
320 for &sample in samples.iter() {
322 assert!(data.iter().any(|&x| (x - sample).abs() < 1e-10));
323 }
324 }
325}