1#![allow(dead_code)]
7
8use crate::error::{SpecialError, SpecialResult};
9use scirs2_core::ndarray::{Array, ArrayView1, Dimension};
10
11#[allow(dead_code)]
13fn cast_slice_to_bytes<T>(slice: &[T]) -> &[u8] {
14 unsafe { std::slice::from_raw_parts(slice.as_ptr() as *const u8, std::mem::size_of_val(slice)) }
19}
20
21#[allow(dead_code)]
23fn cast_bytes_to_slice<T>(bytes: &[u8]) -> &[T] {
24 assert_eq!(bytes.len() % std::mem::size_of::<T>(), 0);
25 unsafe {
31 std::slice::from_raw_parts(
32 bytes.as_ptr() as *const T,
33 bytes.len() / std::mem::size_of::<T>(),
34 )
35 }
36}
37
38#[cfg(feature = "futures")]
39use futures::future::BoxFuture;
40
41#[derive(Debug, Clone, Default)]
52pub enum Backend {
53 #[default]
55 Cpu,
56 #[cfg(feature = "gpu")]
58 Gpu,
59 #[cfg(feature = "lazy")]
61 Lazy,
62 }
66
67#[derive(Debug, Clone)]
69pub struct ArrayConfig {
70 pub chunksize: usize,
72 pub parallel: bool,
74 pub memory_limit: usize,
76 pub backend: Backend,
78 pub cache_results: bool,
80 pub max_cachesize: usize,
82 pub lazy_threshold: usize,
84}
85
86impl Default for ArrayConfig {
87 fn default() -> Self {
88 Self {
89 chunksize: 1024,
90 parallel: cfg!(feature = "parallel"),
91 memory_limit: 1024 * 1024 * 1024, backend: Backend::default(),
93 cache_results: true,
94 max_cachesize: 1000,
95 lazy_threshold: 10_000, }
97 }
98}
99
100pub mod memory_efficient {
102 use super::*;
103
104 pub fn estimate_memory_usage<T>(shape: &[usize], numarrays: usize) -> usize {
106 let elemsize = std::mem::size_of::<T>();
107 let total_elements: usize = shape.iter().product();
108 total_elements * elemsize * numarrays
109 }
110
111 pub fn check_memory_limit<T>(shape: &[usize], numarrays: usize, config: &ArrayConfig) -> bool {
113 estimate_memory_usage::<T>(shape, numarrays) <= config.memory_limit
114 }
115}
116
117#[cfg(feature = "lazy")]
119pub mod lazy {
120 use super::*;
121 use std::fmt::Debug;
123
124 pub trait LazyOperation: Send + Sync + Debug {
126 type Output;
127
128 fn execute(&self) -> SpecialResult<Self::Output>;
130
131 fn description(&self) -> String;
133
134 fn cost_estimate(&self) -> usize;
136 }
137
138 #[derive(Debug)]
140 pub struct LazyArray<T, D>
141 where
142 D: Dimension,
143 {
144 operation: Box<dyn LazyOperation<Output = Array<T, D>>>,
146 shape: Vec<usize>,
148 computed: std::sync::Mutex<Option<Array<T, D>>>,
150 config: ArrayConfig,
152 }
153
154 impl<T, D> LazyArray<T, D>
155 where
156 D: Dimension,
157 T: Clone + Send + Sync,
158 {
159 pub fn new(
161 operation: Box<dyn LazyOperation<Output = Array<T, D>>>,
162 shape: Vec<usize>,
163 config: ArrayConfig,
164 ) -> Self {
165 Self {
166 operation,
167 shape,
168 computed: std::sync::Mutex::new(None),
169 config,
170 }
171 }
172
173 pub fn shape(&self) -> &[usize] {
175 &self.shape
176 }
177
178 pub fn compute(&self) -> SpecialResult<Array<T, D>> {
180 let mut computed = self.computed.lock().expect("Operation failed");
181
182 if let Some(ref cached) = *computed {
183 return Ok(cached.clone());
184 }
185
186 let result = self.operation.execute()?;
187 *computed = Some(result.clone());
188 Ok(result)
189 }
190
191 pub fn is_computed(&self) -> bool {
193 self.computed.lock().expect("Operation failed").is_some()
194 }
195
196 pub fn description(&self) -> String {
198 self.operation.description()
199 }
200
201 pub fn cost_estimate(&self) -> usize {
203 self.operation.cost_estimate()
204 }
205 }
206
207 #[derive(Debug)]
209 pub struct LazyGamma<D>
210 where
211 D: Dimension,
212 {
213 input: Array<f64, D>,
214 }
215
216 impl<D> LazyGamma<D>
217 where
218 D: Dimension,
219 {
220 pub fn new(input: Array<f64, D>) -> Self {
221 Self { input }
222 }
223 }
224
225 impl<D> LazyOperation for LazyGamma<D>
226 where
227 D: Dimension + Send + Sync,
228 {
229 type Output = Array<f64, D>;
230
231 fn execute(&self) -> SpecialResult<Self::Output> {
232 Ok(self.input.mapv(crate::gamma::gamma))
233 }
234
235 fn description(&self) -> String {
236 format!("LazyGamma(shape={:?})", self.input.shape())
237 }
238
239 fn cost_estimate(&self) -> usize {
240 self.input.len() * 100 }
242 }
243
244 #[derive(Debug)]
246 pub struct LazyBesselJ0<D>
247 where
248 D: Dimension,
249 {
250 input: Array<f64, D>,
251 }
252
253 impl<D> LazyBesselJ0<D>
254 where
255 D: Dimension,
256 {
257 pub fn new(input: Array<f64, D>) -> Self {
258 Self { input }
259 }
260 }
261
262 impl<D> LazyOperation for LazyBesselJ0<D>
263 where
264 D: Dimension + Send + Sync,
265 {
266 type Output = Array<f64, D>;
267
268 fn execute(&self) -> SpecialResult<Self::Output> {
269 Ok(self.input.mapv(crate::bessel::j0))
270 }
271
272 fn description(&self) -> String {
273 format!("LazyBesselJ0(shape={:?})", self.input.shape())
274 }
275
276 fn cost_estimate(&self) -> usize {
277 self.input.len() * 150 }
279 }
280
281 pub fn lazy_gamma<D>(input: Array<f64, D>, config: ArrayConfig) -> LazyArray<f64, D>
283 where
284 D: Dimension + Send + Sync + 'static,
285 {
286 let shape = input.shape().to_vec();
287 let operation = Box::new(LazyGamma::new(input));
288 LazyArray::new(operation, shape, config)
289 }
290
291 pub fn lazy_bessel_j0<D>(input: Array<f64, D>, config: ArrayConfig) -> LazyArray<f64, D>
293 where
294 D: Dimension + Send + Sync + 'static,
295 {
296 let shape = input.shape().to_vec();
297 let operation = Box::new(LazyBesselJ0::new(input));
298 LazyArray::new(operation, shape, config)
299 }
300}
301
302#[cfg(feature = "gpu")]
304pub mod gpu {
305 use super::*;
306
307 pub struct GpuBuffer {
309 #[cfg(feature = "gpu")]
310 buffer: Option<std::sync::Arc<scirs2_core::gpu::GpuBuffer<f64>>>,
311 size: usize,
312 elementsize: usize,
313 shape: Vec<usize>,
314 allocatedsize: usize,
315 }
316
317 impl GpuBuffer {
318 #[cfg(feature = "gpu")]
320 pub fn new(ctx: &scirs2_core::gpu::GpuContext, data: &[f64]) -> SpecialResult<Self> {
321 let buffer = ctx.create_buffer_from_slice(data);
322
323 Ok(Self {
324 buffer: Some(std::sync::Arc::new(buffer)),
325 size: data.len(),
326 elementsize: std::mem::size_of::<f64>(),
327 shape: vec![data.len()],
328 allocatedsize: data.len() * std::mem::size_of::<f64>(),
329 })
330 }
331
332 pub fn size(&self) -> usize {
334 self.size
335 }
336
337 pub fn shape(&self) -> &[usize] {
339 &self.shape
340 }
341
342 #[cfg(feature = "gpu")]
344 pub fn is_valid(&self) -> bool {
345 self.buffer.is_some()
346 }
347
348 #[cfg(not(feature = "gpu"))]
349 pub fn is_valid(&self) -> bool {
350 false
351 }
352 }
353
354 pub struct GpuPipeline {
356 #[cfg(feature = "gpu")]
357 context: Option<std::sync::Arc<scirs2_core::gpu::GpuContext>>,
358 #[cfg(feature = "gpu")]
359 pipelines:
360 std::collections::HashMap<String, std::sync::Arc<scirs2_core::gpu::GpuKernelHandle>>,
361 cache_enabled: bool,
362 performance_stats:
363 std::sync::Mutex<std::collections::HashMap<String, (u64, std::time::Duration)>>,
364 }
365
366 impl GpuPipeline {
367 #[cfg(feature = "gpu")]
369 pub fn new() -> SpecialResult<Self> {
370 use crate::gpu_context_manager::get_best_gpu_context;
371
372 let context = get_best_gpu_context().map_err(|e| {
373 SpecialError::ComputationError(format!("Failed to create GPU context: {}", e))
374 })?;
375
376 let mut pipelines = std::collections::HashMap::new();
377
378 Ok(Self {
382 context: Some(context),
383 pipelines,
384 cache_enabled: true,
385 performance_stats: std::sync::Mutex::new(std::collections::HashMap::new()),
386 })
387 }
388
389 #[cfg(feature = "gpu")]
391 pub fn execute_kernel<T>(
392 &self,
393 kernel_name: &str,
394 input: &[T],
395 output: &mut [T],
396 ) -> SpecialResult<std::time::Duration>
397 where
398 T: Clone + Copy + scirs2_core::gpu::GpuDataType,
399 {
400 let start_time = std::time::Instant::now();
401
402 let context = self.context.as_ref().ok_or_else(|| {
403 SpecialError::ComputationError("No GPU context available".to_string())
404 })?;
405
406 let pipeline = self.pipelines.get(kernel_name).ok_or_else(|| {
407 SpecialError::ComputationError(format!("Kernel '{}' not found", kernel_name))
408 })?;
409
410 let input_buffer = context.create_buffer_from_slice(input);
412
413 let output_buffer = context.create_buffer::<T>(output.len());
414
415 return Err(SpecialError::ComputationError(
418 "GPU kernel execution not yet implemented".to_string(),
419 ));
420
421 #[allow(unreachable_code)]
423 {
424 let elapsed = start_time.elapsed();
425
426 if let Ok(mut stats) = self.performance_stats.lock() {
428 let entry = stats
429 .entry(kernel_name.to_string())
430 .or_insert((0, std::time::Duration::ZERO));
431 entry.0 += 1;
432 entry.1 += elapsed;
433 }
434
435 Ok(elapsed)
436 }
437 }
438
439 pub fn get_kernel_stats(&self, kernelname: &str) -> Option<(u64, std::time::Duration)> {
441 self.performance_stats.lock().ok()?.get(kernelname).copied()
442 }
443
444 pub fn clear_stats(&self) {
446 if let Ok(mut stats) = self.performance_stats.lock() {
447 stats.clear();
448 }
449 }
450
451 #[cfg(feature = "gpu")]
453 pub fn gamma_gpu<D>(&self, input: &Array<f64, D>) -> SpecialResult<Array<f64, D>>
454 where
455 D: Dimension,
456 {
457 if input.ndim() == 1 {
459 let input_slice = input.as_slice().ok_or_else(|| {
460 SpecialError::ComputationError("Array not contiguous".to_string())
461 })?;
462
463 let mut output = vec![0.0f64; input_slice.len()];
464 self.execute_kernel("gamma", input_slice, &mut output)?;
465
466 let result = Array::from_vec(output)
467 .into_dimensionality::<D>()
468 .map_err(|e| {
469 SpecialError::ComputationError(format!("Shape conversion error: {}", e))
470 })?;
471
472 Ok(result)
473 } else {
474 let flattened: Vec<f64> = input.iter().copied().collect();
476 let mut output = vec![0.0f64; flattened.len()];
477
478 self.execute_kernel("gamma", &flattened, &mut output)?;
479
480 let result = Array::from_vec(output)
481 .to_shape(input.dim())
482 .map_err(|e| SpecialError::ComputationError(format!("Shape error: {}", e)))?
483 .into_owned();
484
485 Ok(result)
486 }
487 }
488
489 #[cfg(feature = "gpu")]
491 pub fn bessel_j0_gpu<D>(&self, input: &Array<f64, D>) -> SpecialResult<Array<f64, D>>
492 where
493 D: Dimension,
494 {
495 let flattened: Vec<f64> = input.iter().copied().collect();
496 let mut output = vec![0.0f64; flattened.len()];
497
498 self.execute_kernel("bessel_j0", &flattened, &mut output)?;
499
500 let result = Array::from_vec(output)
501 .to_shape(input.dim())
502 .map_err(|e| SpecialError::ComputationError(format!("Shape error: {}", e)))?
503 .into_owned();
504
505 Ok(result)
506 }
507
508 #[cfg(feature = "gpu")]
510 pub fn erf_gpu<D>(&self, input: &Array<f64, D>) -> SpecialResult<Array<f64, D>>
511 where
512 D: Dimension,
513 {
514 let flattened: Vec<f64> = input.iter().copied().collect();
515 let mut output = vec![0.0f64; flattened.len()];
516
517 self.execute_kernel("erf", &flattened, &mut output)?;
518
519 let result = Array::from_vec(output)
520 .to_shape(input.dim())
521 .map_err(|e| SpecialError::ComputationError(format!("Shape error: {}", e)))?
522 .into_owned();
523
524 Ok(result)
525 }
526
527 #[cfg(not(feature = "gpu"))]
529 pub async fn gamma_gpu<D>(&self, input: &Array<f64, D>) -> SpecialResult<Array<f64, D>>
530 where
531 D: Dimension,
532 {
533 Ok(input.mapv(crate::gamma::gamma))
535 }
536 }
537
538 pub async fn gamma_gpu<D>(input: &Array<f64, D>) -> SpecialResult<Array<f64, D>>
540 where
541 D: Dimension,
542 {
543 #[cfg(feature = "gpu")]
544 {
545 let pipeline = GpuPipeline::new()?;
546 pipeline.gamma_gpu(input)
547 }
548 #[cfg(not(feature = "gpu"))]
549 {
550 Ok(input.mapv(crate::gamma::gamma))
552 }
553 }
554}
555
556pub mod broadcasting {
558 use super::*;
559
560 pub fn can_broadcast(shape1: &[usize], shape2: &[usize]) -> bool {
562 let max_len = shape1.len().max(shape2.len());
563
564 for i in 0..max_len {
565 let dim1 = shape1.get(shape1.len().wrapping_sub(i + 1)).unwrap_or(&1);
566 let dim2 = shape2.get(shape2.len().wrapping_sub(i + 1)).unwrap_or(&1);
567
568 if *dim1 != 1 && *dim2 != 1 && *dim1 != *dim2 {
569 return false;
570 }
571 }
572
573 true
574 }
575
576 pub fn broadcastshape(shape1: &[usize], shape2: &[usize]) -> Result<Vec<usize>, SpecialError> {
578 if !can_broadcast(shape1, shape2) {
579 return Err(SpecialError::DomainError(
580 "Arrays cannot be broadcast together".to_string(),
581 ));
582 }
583
584 let max_len = shape1.len().max(shape2.len());
585 let mut result = Vec::with_capacity(max_len);
586
587 for i in 0..max_len {
588 let dim1 = shape1.get(shape1.len().wrapping_sub(i + 1)).unwrap_or(&1);
589 let dim2 = shape2.get(shape2.len().wrapping_sub(i + 1)).unwrap_or(&1);
590
591 result.push((*dim1).max(*dim2));
592 }
593
594 result.reverse();
595 Ok(result)
596 }
597}
598
599pub mod vectorized {
601 use super::*;
602
603 #[cfg(feature = "lazy")]
604 use super::lazy::*;
605
606 pub fn gamma_array<D>(
608 input: &Array<f64, D>,
609 config: &ArrayConfig,
610 ) -> SpecialResult<GammaResult<D>>
611 where
612 D: Dimension + Send + Sync + 'static,
613 {
614 let total_elements = input.len();
615
616 match &config.backend {
618 #[cfg(feature = "lazy")]
619 Backend::Lazy => {
620 if total_elements >= config.lazy_threshold {
621 let lazy_array = lazy_gamma(input.clone(), config.clone());
622 return Ok(GammaResult::Lazy(lazy_array));
623 }
624 }
625 #[cfg(all(feature = "gpu", feature = "futures"))]
626 Backend::Gpu => {
627 if total_elements >= 1000 {
628 let input_owned = input.to_owned();
630 return Ok(GammaResult::Future(Box::pin(async move {
632 if input_owned.ndim() == 1 {
634 let input_1d = input_owned
635 .into_dimensionality::<scirs2_core::ndarray::Ix1>()
636 .map_err(|e| {
637 SpecialError::ComputationError(format!(
638 "Dimension error: {}",
639 e
640 ))
641 })?;
642 let mut output = Array::zeros(input_1d.len());
643 match crate::gpu_ops::gamma_gpu(
644 &input_1d.view(),
645 &mut output.view_mut(),
646 ) {
647 Ok(_) => {
648 let result =
650 output.into_dimensionality::<D>().map_err(|e| {
651 SpecialError::ComputationError(format!(
652 "Dimension error: {}",
653 e
654 ))
655 })?;
656 Ok(result)
657 }
658 Err(e) => {
659 Err(SpecialError::ComputationError(format!("GPU error: {}", e)))
660 }
661 }
662 } else {
663 Ok(input_owned.mapv(crate::gamma::gamma))
665 }
666 })));
667 }
668 }
669 #[cfg(all(feature = "gpu", not(feature = "futures")))]
670 Backend::Gpu => {
671 }
673 Backend::Cpu => {
674 } }
680
681 if config.parallel && total_elements > config.chunksize {
683 #[cfg(feature = "parallel")]
684 {
685 use scirs2_core::parallel_ops::*;
686 let data: Vec<f64> = input.iter().copied().collect();
687 let result: Vec<f64> = data.par_iter().map(|&x| crate::gamma::gamma(x)).collect();
688 let result_array = Array::from_vec(result)
689 .to_shape(input.dim())
690 .map_err(|e| SpecialError::ComputationError(format!("Shape error: {}", e)))?
691 .into_owned();
692 return Ok(GammaResult::Immediate(result_array));
693 }
694 }
695
696 Ok(GammaResult::Immediate(input.mapv(crate::gamma::gamma)))
697 }
698
699 pub fn j0_array<D>(
701 input: &Array<f64, D>,
702 config: &ArrayConfig,
703 ) -> SpecialResult<BesselResult<D>>
704 where
705 D: Dimension + Send + Sync + 'static,
706 {
707 let _total_elements = input.len();
708
709 match &config.backend {
711 #[cfg(feature = "lazy")]
712 Backend::Lazy => {
713 if _total_elements >= config.lazy_threshold {
714 let lazy_array = lazy_bessel_j0(input.clone(), config.clone());
715 return Ok(BesselResult::Lazy(lazy_array));
716 }
717 }
718 Backend::Cpu => {
719 }
721 #[cfg(feature = "gpu")]
722 Backend::Gpu => {
723 }
725 }
726
727 Ok(BesselResult::Immediate(input.mapv(crate::bessel::j0)))
729 }
730
731 pub fn erf_array<D>(input: &Array<f64, D>, config: &ArrayConfig) -> SpecialResult<Array<f64, D>>
733 where
734 D: Dimension,
735 {
736 if config.parallel && input.len() > config.chunksize {
737 #[cfg(feature = "parallel")]
738 {
739 use scirs2_core::parallel_ops::*;
740 let data: Vec<f64> = input.iter().copied().collect();
741 let result: Vec<f64> = data.par_iter().map(|&x| crate::erf::erf(x)).collect();
742 return Ok(Array::from_vec(result)
743 .to_shape(input.dim())
744 .map_err(|e| SpecialError::ComputationError(format!("Shape error: {}", e)))?
745 .into_owned());
746 }
747 }
748
749 Ok(input.mapv(crate::erf::erf))
750 }
751
752 pub fn factorial_array<D>(
754 input: &Array<u32, D>,
755 config: &ArrayConfig,
756 ) -> SpecialResult<Array<f64, D>>
757 where
758 D: Dimension,
759 {
760 if config.parallel && input.len() > config.chunksize {
761 #[cfg(feature = "parallel")]
762 {
763 use scirs2_core::parallel_ops::*;
764 let data: Vec<u32> = input.iter().copied().collect();
765 let result: Vec<f64> = data
766 .par_iter()
767 .map(|&x| crate::combinatorial::factorial(x).unwrap_or(f64::NAN))
768 .collect();
769 return Ok(Array::from_vec(result)
770 .to_shape(input.dim())
771 .map_err(|e| SpecialError::ComputationError(format!("Shape error: {}", e)))?
772 .into_owned());
773 }
774 }
775
776 Ok(input.mapv(|x| crate::combinatorial::factorial(x).unwrap_or(f64::NAN)))
777 }
778
779 pub fn softmax_1d(
781 input: ArrayView1<f64>,
782 _config: &ArrayConfig,
783 ) -> SpecialResult<Array<f64, scirs2_core::ndarray::Ix1>> {
784 crate::statistical::softmax(input)
786 }
787
788 pub enum GammaResult<D>
790 where
791 D: Dimension,
792 {
793 Immediate(Array<f64, D>),
795 #[cfg(feature = "lazy")]
797 Lazy(LazyArray<f64, D>),
798 #[cfg(feature = "futures")]
800 Future(BoxFuture<'static, SpecialResult<Array<f64, D>>>),
801 }
802
803 impl<D> GammaResult<D>
804 where
805 D: Dimension,
806 {
807 pub async fn compute(self) -> SpecialResult<Array<f64, D>> {
809 match self {
810 GammaResult::Immediate(array) => Ok(array),
811 #[cfg(feature = "lazy")]
812 GammaResult::Lazy(lazy_array) => lazy_array.compute(),
813 #[cfg(feature = "futures")]
814 GammaResult::Future(future) => future.await,
815 }
816 }
817
818 pub fn is_ready(&self) -> bool {
820 match self {
821 GammaResult::Immediate(_) => true,
822 #[cfg(feature = "lazy")]
823 GammaResult::Lazy(lazy_array) => lazy_array.is_computed(),
824 #[cfg(feature = "futures")]
825 GammaResult::Future(_) => false,
826 }
827 }
828 }
829
830 pub enum BesselResult<D>
832 where
833 D: Dimension,
834 {
835 Immediate(Array<f64, D>),
837 #[cfg(feature = "lazy")]
839 Lazy(LazyArray<f64, D>),
840 }
841
842 impl<D> BesselResult<D>
843 where
844 D: Dimension,
845 {
846 pub fn compute(self) -> SpecialResult<Array<f64, D>> {
848 match self {
849 BesselResult::Immediate(array) => Ok(array),
850 #[cfg(feature = "lazy")]
851 BesselResult::Lazy(lazy_array) => lazy_array.compute(),
852 }
853 }
854 }
855
856 pub fn process_chunks<T, D, F>(
988 input: &Array<T, D>,
989 config: &ArrayConfig,
990 operation: F,
991 ) -> SpecialResult<Array<T, D>>
992 where
993 T: Clone + Send + Sync,
994 D: Dimension,
995 F: Fn(T) -> T + Send + Sync,
996 {
997 if input.len() <= config.chunksize {
998 return Ok(input.mapv(operation));
999 }
1000
1001 #[cfg(feature = "parallel")]
1003 if config.parallel {
1004 use scirs2_core::parallel_ops::*;
1005 let data: Vec<T> = input.iter().cloned().collect();
1006 let processed: Vec<T> = data.into_par_iter().map(operation).collect();
1007 let result = Array::from_vec(processed)
1008 .to_shape(input.dim())
1009 .map_err(|e| SpecialError::ComputationError(format!("Shape error: {}", e)))?
1010 .into_owned();
1011 return Ok(result);
1012 }
1013
1014 Ok(input.mapv(operation))
1016 }
1017}
1018
1019pub mod complex {
1021 use super::*;
1022 use scirs2_core::numeric::Complex64;
1023
1024 pub fn lambert_w_array<D>(
1026 input: &Array<Complex64, D>,
1027 branch: i32,
1028 tolerance: f64,
1029 _config: &ArrayConfig,
1030 ) -> SpecialResult<Array<Complex64, D>>
1031 where
1032 D: Dimension,
1033 {
1034 Ok(input.mapv(|z| {
1035 crate::lambert::lambert_w(z, branch, tolerance)
1036 .unwrap_or(Complex64::new(f64::NAN, f64::NAN))
1037 }))
1038 }
1039}
1040
1041pub mod convenience {
1043 use super::*;
1044 use scirs2_core::ndarray::{Array1, Array2};
1045
1046 pub async fn gamma_1d(input: &Array1<f64>) -> SpecialResult<Array1<f64>> {
1048 let config = ArrayConfig::default();
1049 let result = vectorized::gamma_array(input, &config)?;
1050 result.compute().await
1051 }
1052
1053 pub async fn gamma_1d_with_config(
1055 input: &Array1<f64>,
1056 config: &ArrayConfig,
1057 ) -> SpecialResult<Array1<f64>> {
1058 let result = vectorized::gamma_array(input, config)?;
1059 result.compute().await
1060 }
1061
1062 pub async fn gamma_2d(input: &Array2<f64>) -> SpecialResult<Array2<f64>> {
1064 let config = ArrayConfig::default();
1065 let result = vectorized::gamma_array(input, &config)?;
1066 result.compute().await
1067 }
1068
1069 #[cfg(feature = "lazy")]
1071 pub fn gamma_lazy<D>(
1072 input: &Array<f64, D>,
1073 config: Option<ArrayConfig>,
1074 ) -> SpecialResult<super::lazy::LazyArray<f64, D>>
1075 where
1076 D: Dimension + Send + Sync + 'static,
1077 {
1078 let config = config.unwrap_or_else(|| ArrayConfig {
1079 backend: Backend::Lazy,
1080 ..Default::default()
1081 });
1082
1083 if let vectorized::GammaResult::Lazy(lazy_array) = vectorized::gamma_array(input, &config)?
1084 {
1085 Ok(lazy_array)
1086 } else {
1087 Ok(super::lazy::lazy_gamma(input.clone(), config))
1089 }
1090 }
1091
1092 #[cfg(feature = "gpu")]
1094 pub async fn gamma_gpu<D>(input: &Array<f64, D>) -> SpecialResult<Array<f64, D>>
1095 where
1096 D: Dimension,
1097 {
1098 super::gpu::gamma_gpu(input).await
1099 }
1100
1101 pub fn j0_1d(input: &Array1<f64>) -> SpecialResult<Array1<f64>> {
1103 let config = ArrayConfig::default();
1104 let result = vectorized::j0_array(input, &config)?;
1105 result.compute()
1106 }
1107
1108 pub fn j0_with_config<D>(
1110 input: &Array<f64, D>,
1111 config: &ArrayConfig,
1112 ) -> SpecialResult<Array<f64, D>>
1113 where
1114 D: Dimension + Send + Sync + 'static,
1115 {
1116 let result = vectorized::j0_array(input, config)?;
1117 result.compute()
1118 }
1119
1120 pub fn erf_1d(input: &Array1<f64>) -> SpecialResult<Array1<f64>> {
1122 let config = ArrayConfig::default();
1123 vectorized::erf_array(input, &config)
1124 }
1125
1126 pub fn erf_parallel<D>(input: &Array<f64, D>) -> SpecialResult<Array<f64, D>>
1128 where
1129 D: Dimension,
1130 {
1131 let config = ArrayConfig {
1132 parallel: true,
1133 ..Default::default()
1134 };
1135 vectorized::erf_array(input, &config)
1136 }
1137
1138 pub fn factorial_1d(input: &Array1<u32>) -> SpecialResult<Array1<f64>> {
1140 let config = ArrayConfig::default();
1141 vectorized::factorial_array(input, &config)
1142 }
1143
1144 pub fn softmax_1d(input: &Array1<f64>) -> SpecialResult<Array1<f64>> {
1146 let config = ArrayConfig::default();
1147 vectorized::softmax_1d(input.view(), &config)
1148 }
1149
1150 pub async fn batch_gamma<D>(
1152 inputs: &[Array<f64, D>],
1153 config: &ArrayConfig,
1154 ) -> SpecialResult<Vec<Array<f64, D>>>
1155 where
1156 D: Dimension + Send + Sync + 'static,
1157 {
1158 let mut results = Vec::with_capacity(inputs.len());
1159
1160 for input in inputs {
1161 let result = vectorized::gamma_array(input, config)?;
1162 results.push(result.compute().await?);
1163 }
1164
1165 Ok(results)
1166 }
1167
1168 pub struct ConfigBuilder {
1170 config: ArrayConfig,
1171 }
1172
1173 impl ConfigBuilder {
1174 pub fn new() -> Self {
1176 Self {
1177 config: ArrayConfig::default(),
1178 }
1179 }
1180
1181 pub fn backend(mut self, backend: Backend) -> Self {
1183 self.config.backend = backend;
1184 self
1185 }
1186
1187 pub fn parallel(mut self, parallel: bool) -> Self {
1189 self.config.parallel = parallel;
1190 self
1191 }
1192
1193 pub fn chunksize(mut self, size: usize) -> Self {
1195 self.config.chunksize = size;
1196 self
1197 }
1198
1199 pub fn memory_limit(mut self, limit: usize) -> Self {
1201 self.config.memory_limit = limit;
1202 self
1203 }
1204
1205 pub fn lazy_threshold(mut self, threshold: usize) -> Self {
1207 self.config.lazy_threshold = threshold;
1208 self
1209 }
1210
1211 pub fn build(self) -> ArrayConfig {
1213 self.config
1214 }
1215 }
1216
1217 impl Default for ConfigBuilder {
1218 fn default() -> Self {
1219 Self::new()
1220 }
1221 }
1222
1223 pub fn large_array_config() -> ArrayConfig {
1225 ConfigBuilder::new()
1226 .chunksize(8192)
1227 .memory_limit(4 * 1024 * 1024 * 1024) .lazy_threshold(50_000)
1229 .parallel(true)
1230 .build()
1231 }
1232
1233 pub fn small_array_config() -> ArrayConfig {
1235 ConfigBuilder::new()
1236 .chunksize(256)
1237 .lazy_threshold(100_000) .parallel(false)
1239 .build()
1240 }
1241
1242 #[cfg(feature = "gpu")]
1244 pub fn gpu_config() -> ArrayConfig {
1245 ConfigBuilder::new()
1246 .backend(Backend::Gpu)
1247 .chunksize(4096)
1248 .build()
1249 }
1250
1251 #[cfg(feature = "lazy")]
1253 pub fn lazy_config() -> ArrayConfig {
1254 ConfigBuilder::new()
1255 .backend(Backend::Lazy)
1256 .lazy_threshold(1000)
1257 .build()
1258 }
1259}
1260
1261#[cfg(test)]
1262mod tests {
1263 use super::*;
1264 use approx::assert_relative_eq;
1265 use scirs2_core::ndarray::{arr1, arr2, Array};
1266
1267 #[test]
1268 fn test_broadcasting() {
1269 assert!(broadcasting::can_broadcast(&[3, 1], &[1, 4]));
1270 assert!(broadcasting::can_broadcast(&[2, 3, 4], &[3, 4]));
1271 assert!(!broadcasting::can_broadcast(&[3, 2], &[4, 5]));
1272
1273 let shape = broadcasting::broadcastshape(&[3, 1], &[1, 4]).expect("Operation failed");
1274 assert_eq!(shape, vec![3, 4]);
1275 }
1276
1277 #[tokio::test]
1278 async fn test_vectorized_gamma() {
1279 let input = arr1(&[1.0, 2.0, 3.0, 4.0, 5.0]);
1280 let result = convenience::gamma_1d(&input)
1281 .await
1282 .expect("Operation failed");
1283
1284 assert_relative_eq!(result[0], 1.0, epsilon = 1e-10);
1286 assert_relative_eq!(result[1], 1.0, epsilon = 1e-10);
1287 assert_relative_eq!(result[2], 2.0, epsilon = 1e-10);
1288 assert_relative_eq!(result[3], 6.0, epsilon = 1e-10);
1289 assert_relative_eq!(result[4], 24.0, epsilon = 1e-10);
1290 }
1291
1292 #[tokio::test]
1293 async fn test_vectorized_gamma_2d() {
1294 let input = arr2(&[[1.0, 2.0], [3.0, 4.0]]);
1295 let result = convenience::gamma_2d(&input)
1296 .await
1297 .expect("Operation failed");
1298
1299 assert_relative_eq!(result[[0, 0]], 1.0, epsilon = 1e-10);
1300 assert_relative_eq!(result[[0, 1]], 1.0, epsilon = 1e-10);
1301 assert_relative_eq!(result[[1, 0]], 2.0, epsilon = 1e-10);
1302 assert_relative_eq!(result[[1, 1]], 6.0, epsilon = 1e-10);
1303 }
1304
1305 #[test]
1306 fn test_vectorized_bessel() {
1307 let input = arr1(&[0.0, 1.0, 2.0]);
1308 let result = convenience::j0_1d(&input).expect("Operation failed");
1309
1310 assert_relative_eq!(result[0], 1.0, epsilon = 1e-10);
1311 assert_relative_eq!(result[1], crate::bessel::j0(1.0), epsilon = 1e-10);
1312 assert_relative_eq!(result[2], crate::bessel::j0(2.0), epsilon = 1e-10);
1313 }
1314
1315 #[test]
1316 fn test_softmax_1d() {
1317 let input = arr1(&[1.0, 2.0, 3.0]);
1318 let result = convenience::softmax_1d(&input).expect("Operation failed");
1319
1320 assert_relative_eq!(result.sum(), 1.0, epsilon = 1e-10);
1322
1323 for &val in result.iter() {
1325 assert!(val > 0.0);
1326 }
1327 }
1328
1329 #[test]
1330 fn test_memory_estimation() {
1331 let shape = [1000, 1000];
1332 let memory = memory_efficient::estimate_memory_usage::<f64>(&shape, 2);
1333 assert_eq!(memory, 1000 * 1000 * 8 * 2); let config = ArrayConfig::default();
1336 assert!(memory_efficient::check_memory_limit::<f64>(
1337 &shape, 2, &config
1338 ));
1339 }
1340
1341 #[test]
1342 fn test_config_builder() {
1343 let config = convenience::ConfigBuilder::new()
1344 .chunksize(2048)
1345 .parallel(true)
1346 .memory_limit(2 * 1024 * 1024 * 1024)
1347 .lazy_threshold(5000)
1348 .build();
1349
1350 assert_eq!(config.chunksize, 2048);
1351 assert!(config.parallel);
1352 assert_eq!(config.memory_limit, 2 * 1024 * 1024 * 1024);
1353 assert_eq!(config.lazy_threshold, 5000);
1354 }
1355
1356 #[test]
1357 fn test_predefined_configs() {
1358 let large_config = convenience::large_array_config();
1359 assert_eq!(large_config.chunksize, 8192);
1360 assert!(large_config.parallel);
1361 assert_eq!(large_config.lazy_threshold, 50_000);
1362
1363 let small_config = convenience::small_array_config();
1364 assert_eq!(small_config.chunksize, 256);
1365 assert!(!small_config.parallel);
1366 assert_eq!(small_config.lazy_threshold, 100_000);
1367 }
1368
1369 #[cfg(feature = "lazy")]
1370 #[test]
1371 fn test_lazy_evaluation() {
1372 let input = Array::linspace(1.0, 5.0, 1000);
1373 let lazy_array = convenience::gamma_lazy(&input, None).expect("Operation failed");
1374
1375 assert!(!lazy_array.is_computed());
1377 assert_eq!(lazy_array.shape(), input.shape());
1378
1379 let result = lazy_array.compute().expect("Operation failed");
1381 assert_eq!(result.shape(), input.shape());
1382
1383 assert_relative_eq!(result[0], crate::gamma::gamma(1.0), epsilon = 1e-10);
1385 }
1386
1387 #[cfg(feature = "lazy")]
1388 #[test]
1389 fn test_lazy_bessel() {
1390 let input = Array::linspace(0.0, 5.0, 1500); let config = convenience::lazy_config();
1392 let result = vectorized::j0_array(&input, &config).expect("Operation failed");
1393
1394 if let vectorized::BesselResult::Lazy(lazy_array) = result {
1395 assert!(!lazy_array.is_computed());
1396 let computed = lazy_array.compute().expect("Operation failed");
1397 assert_eq!(computed.shape(), input.shape());
1398 } else {
1399 panic!("Expected lazy result");
1400 }
1401 }
1402
1403 #[tokio::test]
1404 async fn test_batch_processing() {
1405 let arrays = vec![
1406 arr1(&[1.0, 2.0, 3.0]),
1407 arr1(&[4.0, 5.0, 6.0]),
1408 arr1(&[7.0, 8.0, 9.0]),
1409 ];
1410
1411 let config = ArrayConfig::default();
1412 let results = convenience::batch_gamma(&arrays, &config)
1413 .await
1414 .expect("Operation failed");
1415
1416 assert_eq!(results.len(), 3);
1417 for (i, result) in results.iter().enumerate() {
1418 assert_eq!(result.len(), 3);
1419 for (j, &val) in result.iter().enumerate() {
1420 let expected = crate::gamma::gamma(arrays[i][j]);
1421 assert_relative_eq!(val, expected, epsilon = 1e-10);
1422 }
1423 }
1424 }
1425
1426 #[test]
1427 fn test_chunked_processing() {
1428 let input = Array::ones(2000);
1429 let config = ArrayConfig {
1430 chunksize: 100,
1431 ..Default::default()
1432 };
1433
1434 let result = vectorized::process_chunks(&input, &config, |x: f64| x * 2.0)
1435 .expect("Operation failed");
1436
1437 assert_eq!(result.len(), input.len());
1438 for &val in result.iter() {
1439 assert_relative_eq!(val, 2.0, epsilon = 1e-10);
1440 }
1441 }
1442
1443 #[test]
1444 fn test_backend_selection() {
1445 let config = ArrayConfig {
1446 backend: Backend::Cpu,
1447 ..Default::default()
1448 };
1449
1450 let input = arr1(&[1.0, 2.0, 3.0]);
1451 let result = vectorized::gamma_array(&input, &config).expect("Operation failed");
1452
1453 assert!(result.is_ready());
1455 }
1456
1457 #[cfg(feature = "parallel")]
1458 #[test]
1459 fn test_parallel_processing() {
1460 let input = Array::linspace(1.0, 10.0, 1000);
1461 let result = convenience::erf_parallel(&input).expect("Operation failed");
1462
1463 assert_eq!(result.len(), input.len());
1464 for (i, &val) in result.iter().enumerate() {
1465 let expected = crate::erf::erf(input[i]);
1466 assert_relative_eq!(val, expected, epsilon = 1e-10);
1467 }
1468 }
1469
1470 #[test]
1471 fn test_gamma_result_types() {
1472 let input = arr1(&[1.0, 2.0, 3.0]);
1473 let config = ArrayConfig::default();
1474 let result = vectorized::gamma_array(&input, &config).expect("Operation failed");
1475
1476 match result {
1478 vectorized::GammaResult::Immediate(array) => {
1479 assert_eq!(array.len(), 3);
1480 assert_relative_eq!(array[0], 1.0, epsilon = 1e-10);
1481 assert_relative_eq!(array[1], 1.0, epsilon = 1e-10);
1482 assert_relative_eq!(array[2], 2.0, epsilon = 1e-10);
1483 }
1484 #[cfg(feature = "lazy")]
1485 vectorized::GammaResult::Lazy(_) => {
1486 panic!("Expected immediate result but got lazy result");
1487 }
1488 #[cfg(feature = "futures")]
1489 vectorized::GammaResult::Future(_) => {
1490 panic!("Expected immediate result but got future result");
1491 }
1492 }
1493 }
1494
1495 #[cfg(feature = "lazy")]
1496 #[test]
1497 fn test_lazy_array_operations() {
1498 let input = Array::linspace(1.0, 5.0, 100);
1499 let lazy_gamma = super::lazy::lazy_gamma(input.clone(), ArrayConfig::default());
1500
1501 assert_eq!(lazy_gamma.shape(), input.shape());
1503 assert!(!lazy_gamma.is_computed());
1504 assert!(lazy_gamma.cost_estimate() > 0);
1505 assert!(lazy_gamma.description().contains("LazyGamma"));
1506
1507 let result = lazy_gamma.compute().expect("Operation failed");
1509 assert_eq!(result.shape(), input.shape());
1510 }
1511
1512 #[tokio::test]
1513 #[cfg(feature = "gpu")]
1514 async fn test_gpu_fallback() {
1515 let input = arr1(&[1.0, 2.0, 3.0]);
1517
1518 match convenience::gamma_gpu(&input).await {
1519 Ok(result) => {
1520 assert_eq!(result.len(), 3);
1521 assert_relative_eq!(result[0], 1.0, epsilon = 1e-6); assert_relative_eq!(result[1], 1.0, epsilon = 1e-6);
1523 assert_relative_eq!(result[2], 2.0, epsilon = 1e-6);
1524 }
1525 Err(_) => {
1526 }
1528 }
1529 }
1530
1531 #[test]
1532 fn test_complex_array_operations() {
1533 use scirs2_core::numeric::Complex64;
1534
1535 let input = Array::from_vec(vec![
1536 Complex64::new(1.0, 0.0),
1537 Complex64::new(2.0, 0.5),
1538 Complex64::new(0.5, 1.0),
1539 ]);
1540
1541 let config = ArrayConfig::default();
1542 let result = complex::lambert_w_array(&input, 0, 1e-8, &config).expect("Operation failed");
1543
1544 assert_eq!(result.len(), 3);
1545 for val in result.iter() {
1547 assert!(val.re.is_finite());
1548 assert!(val.im.is_finite());
1549 }
1550 }
1551}