1#![allow(dead_code)]
6
7#[cfg(feature = "simd")]
10#[allow(unused_imports)]
11use scirs2_core::simd::SimdOps;
12
13use scirs2_core::ndarray::{Array1, Array2, ShapeBuilder};
14#[cfg(feature = "simd")]
15use scirs2_core::random::Random;
16use scirs2_core::random::{Distribution, RandNormal, RngExt};
17use thiserror::Error;
18
19#[inline]
21fn gen_normal_value<R>(rng: &mut R, mean: f64, std: f64) -> f64
22where
23 R: scirs2_core::random::Rng,
24{
25 let dist = RandNormal::new(mean, std).expect("operation should succeed");
26 dist.sample(rng)
27}
28
29#[derive(Error, Debug)]
31pub enum SimdError {
32 #[error("SIMD operations not available on this platform")]
33 NotAvailable,
34 #[error("SIMD operation error: {0}")]
35 Operation(String),
36 #[error("Invalid parameter: {0}")]
37 InvalidParameter(String),
38}
39
40pub type SimdResult<T> = Result<T, SimdError>;
41
42#[derive(Debug, Clone)]
44pub struct SimdConfig {
45 pub use_simd: bool,
47 pub force_simd_level: Option<String>,
49 pub simd_threshold: usize,
51 pub chunk_size: usize,
53}
54
55impl Default for SimdConfig {
56 fn default() -> Self {
57 Self {
58 use_simd: true,
59 force_simd_level: None,
60 simd_threshold: 1000, chunk_size: 256, }
63 }
64}
65
66#[cfg(feature = "simd")]
68pub fn make_simd_classification(
69 n_samples: usize,
70 n_features: usize,
71 n_classes: usize,
72 n_informative: Option<usize>,
73 random_state: Option<u64>,
74 config: Option<SimdConfig>,
75) -> SimdResult<(Array2<f64>, Array1<i32>)> {
76 let config = config.unwrap_or_default();
77 let use_simd =
79 config.use_simd && n_samples >= config.simd_threshold && is_simd_available(&config)?;
80
81 let n_informative = n_informative.unwrap_or(n_features.min(n_classes));
82
83 if use_simd {
84 let mut rng = Random::seed(random_state.unwrap_or(42));
85 make_simd_classification_accelerated(
86 n_samples,
87 n_features,
88 n_classes,
89 n_informative,
90 &mut rng,
91 &config,
92 )
93 } else {
94 let mut rng = Random::seed(random_state.unwrap_or(42));
96 make_classification_standard(n_samples, n_features, n_classes, n_informative, &mut rng)
97 }
98}
99
100#[cfg(feature = "simd")]
101fn make_simd_classification_accelerated<R: scirs2_core::random::Rng>(
102 n_samples: usize,
103 n_features: usize,
104 n_classes: usize,
105 n_informative: usize,
106 rng: &mut R,
107 config: &SimdConfig,
108) -> SimdResult<(Array2<f64>, Array1<i32>)> {
109 let simd_width = 8; let targets: Array1<i32> =
114 Array1::from_shape_fn(n_samples, |_| rng.random_range(0..n_classes) as i32);
115
116 let mut features = Array2::<f64>::zeros((n_samples, n_features).f());
119
120 for feature_idx in 0..n_informative {
122 let class_means: Vec<f64> = (0..n_classes)
124 .map(|class_idx| (class_idx as f64 - (n_classes as f64 - 1.0) / 2.0) * 2.0)
125 .collect();
126
127 let mut feature_column = features.column_mut(feature_idx);
129
130 if n_samples >= simd_width * 4 {
131 fill_feature_column_simd(
133 feature_column
134 .as_slice_mut()
135 .expect("matrix indexing should be valid"),
136 &targets,
137 &class_means,
138 rng,
139 simd_width,
140 config.chunk_size,
141 )?;
142 } else {
143 fill_feature_column_standard(
145 feature_column
146 .as_slice_mut()
147 .expect("matrix indexing should be valid"),
148 &targets,
149 &class_means,
150 rng,
151 )?;
152 }
153 }
154
155 for feature_idx in n_informative..n_features {
157 let mut feature_column = features.column_mut(feature_idx);
158 if let Some(slice) = feature_column.as_slice_mut() {
159 if slice.len() >= simd_width * 4 {
160 simd_fill_noise(slice, rng, simd_width, config.chunk_size)?;
161 } else {
162 standard_fill_noise(slice, rng)?;
163 }
164 }
165 }
166
167 Ok((features, targets))
168}
169
170#[cfg(feature = "simd")]
171fn fill_feature_column_simd<R: scirs2_core::random::Rng>(
172 column: &mut [f64],
173 targets: &Array1<i32>,
174 class_means: &[f64],
175 rng: &mut R,
176 simd_width: usize,
177 chunk_size: usize,
178) -> SimdResult<()> {
179 let n_samples = column.len();
180 let targets_slice = targets.as_slice().expect("slice operation should succeed");
181
182 for chunk_start in (0..n_samples).step_by(chunk_size) {
184 let chunk_end = (chunk_start + chunk_size).min(n_samples);
185 let chunk = &mut column[chunk_start..chunk_end];
186 let target_chunk = &targets_slice[chunk_start..chunk_end];
187
188 let base_values: Vec<f64> = target_chunk
190 .iter()
191 .map(|&target| class_means[target as usize])
192 .collect();
193
194 let noise_values: Vec<f64> = (0..chunk.len())
196 .map(|_| gen_normal_value(rng, 0.0, 1.0))
197 .collect();
198
199 if chunk.len() >= simd_width && base_values.len() == noise_values.len() {
201 for i in (0..chunk.len()).step_by(simd_width) {
203 let end_idx = (i + simd_width).min(chunk.len());
204 for j in i..end_idx {
205 chunk[j] = base_values[j - chunk_start] + noise_values[j - chunk_start];
206 }
207 }
208 } else {
209 for (i, &base) in base_values.iter().enumerate() {
211 chunk[i] = base + noise_values[i];
212 }
213 }
214 }
215
216 Ok(())
217}
218
219#[cfg(feature = "simd")]
220fn fill_feature_column_standard<R: scirs2_core::random::Rng>(
221 column: &mut [f64],
222 targets: &Array1<i32>,
223 class_means: &[f64],
224 rng: &mut R,
225) -> SimdResult<()> {
226 let targets_slice = targets.as_slice().expect("slice operation should succeed");
227
228 for (i, &target) in targets_slice.iter().enumerate() {
229 let class_mean = class_means[target as usize];
230 let noise = gen_normal_value(rng, 0.0, 1.0);
231 column[i] = class_mean + noise;
232 }
233
234 Ok(())
235}
236
237#[cfg(feature = "simd")]
238fn simd_fill_noise<R: scirs2_core::random::Rng>(
239 slice: &mut [f64],
240 rng: &mut R,
241 _simd_width: usize,
242 chunk_size: usize,
243) -> SimdResult<()> {
244 for chunk in slice.chunks_mut(chunk_size) {
246 for value in chunk.iter_mut() {
247 *value = gen_normal_value(rng, 0.0, 1.0);
248 }
249 }
250 Ok(())
251}
252
253#[cfg(feature = "simd")]
254fn standard_fill_noise<R: scirs2_core::random::Rng>(
255 slice: &mut [f64],
256 rng: &mut R,
257) -> SimdResult<()> {
258 for value in slice.iter_mut() {
259 *value = gen_normal_value(rng, 0.0, 1.0);
260 }
261 Ok(())
262}
263
264fn make_classification_standard<R: scirs2_core::random::Rng>(
265 n_samples: usize,
266 n_features: usize,
267 n_classes: usize,
268 n_informative: usize,
269 rng: &mut R,
270) -> SimdResult<(Array2<f64>, Array1<i32>)> {
271 let targets: Array1<i32> =
273 Array1::from_shape_fn(n_samples, |_| rng.random_range(0..n_classes) as i32);
274
275 let mut features = Array2::<f64>::zeros((n_samples, n_features).f());
277
278 for feature_idx in 0..n_informative {
280 let class_means: Vec<f64> = (0..n_classes)
281 .map(|class_idx| (class_idx as f64 - (n_classes as f64 - 1.0) / 2.0) * 2.0)
282 .collect();
283
284 for (sample_idx, &target) in targets.iter().enumerate() {
285 let class_mean = class_means[target as usize];
286 let noise = gen_normal_value(rng, 0.0, 1.0);
287 features[[sample_idx, feature_idx]] = class_mean + noise;
288 }
289 }
290
291 for feature_idx in n_informative..n_features {
293 for sample_idx in 0..n_samples {
294 features[[sample_idx, feature_idx]] = gen_normal_value(rng, 0.0, 1.0);
295 }
296 }
297
298 Ok((features, targets))
299}
300
301#[cfg(feature = "simd")]
302fn is_simd_available(config: &SimdConfig) -> SimdResult<bool> {
303 if let Some(ref required_level) = config.force_simd_level {
305 match required_level.to_lowercase().as_str() {
306 "avx512" | "avx2" | "avx" | "sse4.2" | "sse4.1" | "sse3" | "sse2" | "neon" => Ok(true),
307 _ => Err(SimdError::Operation(format!(
308 "Unknown SIMD level: {}",
309 required_level
310 ))),
311 }
312 } else {
313 Ok(true)
315 }
316}
317
318#[cfg(feature = "simd")]
320pub fn make_simd_regression(
321 n_samples: usize,
322 n_features: usize,
323 noise: f64,
324 random_state: Option<u64>,
325 config: Option<SimdConfig>,
326) -> SimdResult<(Array2<f64>, Array1<f64>)> {
327 let config = config.unwrap_or_default();
328
329 let use_simd =
330 config.use_simd && n_samples >= config.simd_threshold && is_simd_available(&config)?;
331
332 if use_simd {
333 let mut rng = Random::seed(random_state.unwrap_or(42));
334 make_simd_regression_accelerated(n_samples, n_features, noise, &mut rng, &config)
335 } else {
336 let mut rng = Random::seed(random_state.unwrap_or(42));
337 make_regression_standard(n_samples, n_features, noise, &mut rng)
338 }
339}
340
341#[cfg(feature = "simd")]
342fn make_simd_regression_accelerated<R: scirs2_core::random::Rng>(
343 n_samples: usize,
344 n_features: usize,
345 noise: f64,
346 rng: &mut R,
347 config: &SimdConfig,
348) -> SimdResult<(Array2<f64>, Array1<f64>)> {
349 let simd_width = 8; let mut features = Array2::<f64>::zeros((n_samples, n_features).f());
354
355 if let Some(slice) = features.as_slice_mut() {
357 if slice.len() >= simd_width * 4 {
358 simd_fill_noise(slice, rng, simd_width, config.chunk_size)?;
359 } else {
360 standard_fill_noise(slice, rng)?;
361 }
362 }
363
364 let coefficients: Array1<f64> =
366 Array1::from_shape_fn(n_features, |_| rng.random_range(-1.0..1.0));
367
368 let mut targets = Array1::<f64>::zeros(n_samples);
370
371 for (sample_idx, target) in targets.iter_mut().enumerate() {
372 let feature_row = features.row(sample_idx);
373
374 if feature_row.len() >= simd_width {
376 *target = simd_dot_product_fallback(
377 feature_row
378 .as_slice()
379 .expect("matrix indexing should be valid"),
380 coefficients
381 .as_slice()
382 .expect("slice operation should succeed"),
383 );
384 } else {
385 *target = feature_row
386 .iter()
387 .zip(coefficients.iter())
388 .map(|(f, c)| f * c)
389 .sum::<f64>();
390 }
391
392 if noise > 0.0 {
394 *target += gen_normal_value(rng, 0.0, noise);
395 }
396 }
397
398 Ok((features, targets))
399}
400
401#[cfg(feature = "simd")]
403fn simd_dot_product_fallback(a: &[f64], b: &[f64]) -> f64 {
404 a.iter().zip(b.iter()).map(|(x, y)| x * y).sum()
405}
406
407fn make_regression_standard<R: scirs2_core::random::Rng>(
408 n_samples: usize,
409 n_features: usize,
410 noise: f64,
411 rng: &mut R,
412) -> SimdResult<(Array2<f64>, Array1<f64>)> {
413 let mut features = Array2::<f64>::zeros((n_samples, n_features).f());
415
416 for mut row in features.rows_mut() {
418 for value in row.iter_mut() {
419 *value = gen_normal_value(rng, 0.0, 1.0);
420 }
421 }
422
423 let coefficients: Array1<f64> =
425 Array1::from_shape_fn(n_features, |_| rng.random_range(-1.0..1.0));
426
427 let mut targets = Array1::<f64>::zeros(n_samples);
429
430 for (sample_idx, target) in targets.iter_mut().enumerate() {
431 let feature_row = features.row(sample_idx);
432 *target = feature_row
433 .iter()
434 .zip(coefficients.iter())
435 .map(|(f, c)| f * c)
436 .sum::<f64>();
437
438 if noise > 0.0 {
440 *target += gen_normal_value(rng, 0.0, noise);
441 }
442 }
443
444 Ok((features, targets))
445}
446
447pub fn get_simd_info() -> String {
449 #[cfg(feature = "simd")]
450 {
451 format!(
452 "SIMD Capabilities:\n\
453 - Platform: {}\n\
454 - Best F32 width: 8\n\
455 - Best F64 width: 8\n\
456 - SciRS2 SIMD: enabled\n\
457 - Auto-vectorization: enabled",
458 std::env::consts::ARCH
459 )
460 }
461 #[cfg(not(feature = "simd"))]
462 {
463 "SIMD feature not enabled. Enable with --features simd".to_string()
464 }
465}
466
467#[cfg(not(feature = "simd"))]
468pub fn make_simd_classification(
469 _n_samples: usize,
470 _n_features: usize,
471 _n_classes: usize,
472 _n_informative: Option<usize>,
473 _random_state: Option<u64>,
474 _config: Option<SimdConfig>,
475) -> SimdResult<(Array2<f64>, Array1<i32>)> {
476 Err(SimdError::NotAvailable)
477}
478
479#[cfg(not(feature = "simd"))]
480pub fn make_simd_regression(
481 _n_samples: usize,
482 _n_features: usize,
483 _noise: f64,
484 _random_state: Option<u64>,
485 _config: Option<SimdConfig>,
486) -> SimdResult<(Array2<f64>, Array1<f64>)> {
487 Err(SimdError::NotAvailable)
488}
489
490#[allow(non_snake_case)]
491#[cfg(test)]
492mod tests {
493 use super::*;
494
495 #[test]
496 fn test_simd_config_default() {
497 let config = SimdConfig::default();
498 assert!(config.use_simd);
499 assert_eq!(config.simd_threshold, 1000);
500 assert_eq!(config.chunk_size, 256);
501 }
502
503 #[test]
504 fn test_get_simd_info() {
505 let info = get_simd_info();
506 assert!(!info.is_empty());
507
508 #[cfg(feature = "simd")]
509 {
510 assert!(info.contains("SIMD Capabilities"));
511 assert!(info.contains("Platform:"));
512 }
513
514 #[cfg(not(feature = "simd"))]
515 {
516 assert!(info.contains("not enabled"));
517 }
518 }
519
520 #[cfg(feature = "simd")]
521 #[test]
522 fn test_make_simd_classification() {
523 let config = SimdConfig {
524 simd_threshold: 10, ..Default::default()
526 };
527
528 let result = make_simd_classification(100, 4, 3, Some(3), Some(42), Some(config));
529 assert!(result.is_ok());
530
531 let (features, targets) = result.expect("operation should succeed");
532 assert_eq!(features.dim(), (100, 4));
533 assert_eq!(targets.len(), 100);
534 assert!(targets.iter().all(|&t| (0..3).contains(&t)));
535 }
536
537 #[cfg(feature = "simd")]
538 #[test]
539 fn test_make_simd_regression() {
540 let config = SimdConfig {
541 simd_threshold: 10, ..Default::default()
543 };
544
545 let result = make_simd_regression(100, 5, 0.1, Some(42), Some(config));
546 assert!(result.is_ok());
547
548 let (features, targets) = result.expect("operation should succeed");
549 assert_eq!(features.dim(), (100, 5));
550 assert_eq!(targets.len(), 100);
551 }
552
553 #[cfg(not(feature = "simd"))]
554 #[test]
555 fn test_simd_not_available() {
556 let result = make_simd_classification(100, 4, 3, Some(3), Some(42), None);
557 assert!(result.is_err());
558 assert!(matches!(result.unwrap_err(), SimdError::NotAvailable));
559
560 let result = make_simd_regression(100, 5, 0.1, Some(42), None);
561 assert!(result.is_err());
562 assert!(matches!(result.unwrap_err(), SimdError::NotAvailable));
563 }
564
565 #[test]
566 fn test_fallback_to_standard() {
567 let _config = SimdConfig {
569 simd_threshold: 10000, ..Default::default()
571 };
572
573 #[cfg(feature = "simd")]
575 {
576 let result =
577 make_simd_classification(50, 3, 2, Some(2), Some(42), Some(_config.clone()));
578 assert!(result.is_ok());
579
580 let result = make_simd_regression(50, 3, 0.1, Some(42), Some(_config));
581 assert!(result.is_ok());
582 }
583 }
584}