Skip to main content

scirs2_special/
array_ops.rs

1//! Enhanced array operations for special functions
2//!
3//! This module provides comprehensive array support for special functions with
4//! lazy evaluation, GPU acceleration, and multidimensional operations.
5
6#![allow(dead_code)]
7
8use crate::error::{SpecialError, SpecialResult};
9use scirs2_core::ndarray::{Array, ArrayView1, Dimension};
10
11/// Safe slice casting replacement for bytemuck::cast_slice
12#[allow(dead_code)]
13fn cast_slice_to_bytes<T>(slice: &[T]) -> &[u8] {
14    // SAFETY: This is safe because:
15    // 1. The pointer is derived from a valid slice
16    // 2. The size calculation is correct using size_of_val
17    // 3. The lifetime is bounded by the input slice
18    unsafe { std::slice::from_raw_parts(slice.as_ptr() as *const u8, std::mem::size_of_val(slice)) }
19}
20
21/// Safe slice casting replacement for bytemuck::cast_slice (reverse)
22#[allow(dead_code)]
23fn cast_bytes_to_slice<T>(bytes: &[u8]) -> &[T] {
24    assert_eq!(bytes.len() % std::mem::size_of::<T>(), 0);
25    // SAFETY: This is safe because:
26    // 1. We assert that the byte length is a multiple of T's size
27    // 2. The pointer is derived from a valid slice
28    // 3. The length calculation ensures we don't exceed bounds
29    // 4. The lifetime is bounded by the input slice
30    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// #[cfg(feature = "arrayfire")]
42// use arrayfire;
43
44// #[cfg(feature = "arrayfire")]
45// use log;
46
47// #[cfg(feature = "lazy")]
48// use std::collections::HashMap;
49
50/// Execution backend for array operations
51#[derive(Debug, Clone, Default)]
52pub enum Backend {
53    /// CPU-based computation with ndarray
54    #[default]
55    Cpu,
56    /// GPU-based computation (requires gpu feature)
57    #[cfg(feature = "gpu")]
58    Gpu,
59    /// Lazy evaluation (requires lazy feature)
60    #[cfg(feature = "lazy")]
61    Lazy,
62    // /// ArrayFire backend for GPU acceleration (disabled - placeholder)
63    // #[cfg(feature = "arrayfire")]
64    // ArrayFire,
65}
66
67/// Configuration for array operations
68#[derive(Debug, Clone)]
69pub struct ArrayConfig {
70    /// Chunk size for memory-efficient processing
71    pub chunksize: usize,
72    /// Whether to use parallel processing
73    pub parallel: bool,
74    /// Memory limit for operations (in bytes)
75    pub memory_limit: usize,
76    /// Execution backend
77    pub backend: Backend,
78    /// Whether to cache computed results
79    pub cache_results: bool,
80    /// Maximum cache size (number of entries)
81    pub max_cachesize: usize,
82    /// Lazy evaluation threshold (array size)
83    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, // 1GB
92            backend: Backend::default(),
93            cache_results: true,
94            max_cachesize: 1000,
95            lazy_threshold: 10_000, // Use lazy evaluation for arrays > 10k elements
96        }
97    }
98}
99
100/// Memory-efficient array operations
101pub mod memory_efficient {
102    use super::*;
103
104    /// Estimate memory usage for an operation
105    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    /// Check if operation fits within memory limits
112    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/// Lazy evaluation system for deferred computation
118#[cfg(feature = "lazy")]
119pub mod lazy {
120    use super::*;
121    // use std::any::Any;
122    use std::fmt::Debug;
123
124    /// Trait for lazy operations that can be computed on demand
125    pub trait LazyOperation: Send + Sync + Debug {
126        type Output;
127
128        /// Execute the lazy operation
129        fn execute(&self) -> SpecialResult<Self::Output>;
130
131        /// Get operation description for debugging
132        fn description(&self) -> String;
133
134        /// Estimate computational cost (arbitrary units)
135        fn cost_estimate(&self) -> usize;
136    }
137
138    /// Container for lazy array operations
139    #[derive(Debug)]
140    pub struct LazyArray<T, D>
141    where
142        D: Dimension,
143    {
144        /// The operation to be performed
145        operation: Box<dyn LazyOperation<Output = Array<T, D>>>,
146        /// Shape of the resulting array
147        shape: Vec<usize>,
148        /// Whether the result has been computed and cached
149        computed: std::sync::Mutex<Option<Array<T, D>>>,
150        /// Configuration for execution
151        config: ArrayConfig,
152    }
153
154    impl<T, D> LazyArray<T, D>
155    where
156        D: Dimension,
157        T: Clone + Send + Sync,
158    {
159        /// Create a new lazy array
160        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        /// Get the shape of the lazy array
174        pub fn shape(&self) -> &[usize] {
175            &self.shape
176        }
177
178        /// Force evaluation of the lazy array
179        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        /// Check if the array has been computed
192        pub fn is_computed(&self) -> bool {
193            self.computed.lock().expect("Operation failed").is_some()
194        }
195
196        /// Get operation description
197        pub fn description(&self) -> String {
198            self.operation.description()
199        }
200
201        /// Get cost estimate
202        pub fn cost_estimate(&self) -> usize {
203            self.operation.cost_estimate()
204        }
205    }
206
207    /// Lazy operation for gamma function
208    #[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 // Estimate: 100 units per gamma computation
241        }
242    }
243
244    /// Lazy operation for Bessel J0 function
245    #[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 // Estimate: 150 units per Bessel computation
278        }
279    }
280
281    /// Create a lazy gamma array
282    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    /// Create a lazy Bessel J0 array
292    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/// GPU acceleration for array operations
303#[cfg(feature = "gpu")]
304pub mod gpu {
305    use super::*;
306
307    /// Advanced GPU buffer for array data with memory management
308    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        /// Create a new GPU buffer
319        #[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        /// Get buffer size in elements
333        pub fn size(&self) -> usize {
334            self.size
335        }
336
337        /// Get buffer shape
338        pub fn shape(&self) -> &[usize] {
339            &self.shape
340        }
341
342        /// Check if buffer is valid
343        #[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    /// Advanced GPU compute pipeline for special functions
355    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        /// Create a new advanced GPU pipeline with comprehensive functionality
368        #[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            // Note: GPU pipelines not currently supported in scirs2-core
379            // Pre-compiled kernels would be loaded here when available
380
381            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        /// Execute a kernel on GPU with performance monitoring
390        #[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            // Create GPU buffers
411            let input_buffer = context.create_buffer_from_slice(input);
412
413            let output_buffer = context.create_buffer::<T>(output.len());
414
415            // Note: Direct kernel execution not currently supported in scirs2-core
416            // Fall back to CPU computation for now
417            return Err(SpecialError::ComputationError(
418                "GPU kernel execution not yet implemented".to_string(),
419            ));
420
421            // TODO: Implement GPU kernel execution and re-enable performance tracking
422            #[allow(unreachable_code)]
423            {
424                let elapsed = start_time.elapsed();
425
426                // Update performance statistics
427                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        /// Get performance statistics for a kernel
440        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        /// Clear performance statistics
445        pub fn clear_stats(&self) {
446            if let Ok(mut stats) = self.performance_stats.lock() {
447                stats.clear();
448            }
449        }
450
451        /// Execute gamma function on GPU with advanced features
452        #[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            // For 1D arrays, use direct GPU execution
458            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                // For multi-dimensional arrays, flatten, process, and reshape
475                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        /// Execute Bessel J0 function on GPU
490        #[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        /// Execute error function on GPU
509        #[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        /// Execute gamma function on CPU as fallback
528        #[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            // Fallback to CPU implementation
534            Ok(input.mapv(crate::gamma::gamma))
535        }
536    }
537
538    /// GPU-accelerated gamma computation
539    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            // Fallback to CPU
551            Ok(input.mapv(crate::gamma::gamma))
552        }
553    }
554}
555
556/// Broadcasting utilities for array operations
557pub mod broadcasting {
558    use super::*;
559
560    /// Check if two shapes can be broadcast together
561    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    /// Compute the broadcast shape of two arrays
577    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
599/// Vectorized special function operations with automatic backend selection
600pub mod vectorized {
601    use super::*;
602
603    #[cfg(feature = "lazy")]
604    use super::lazy::*;
605
606    /// Enhanced gamma function computation with backend selection
607    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        // Choose backend based on configuration and array size
617        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                    // GPU efficient for larger arrays
629                    let input_owned = input.to_owned();
630                    // Since gamma_gpu is not async, we create a future wrapper
631                    return Ok(GammaResult::Future(Box::pin(async move {
632                        // Convert to appropriate array views for 1D operations
633                        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                                    // Convert back to original dimensions
649                                    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                            // For multi-dimensional arrays, fall back to CPU implementation
664                            Ok(input_owned.mapv(crate::gamma::gamma))
665                        }
666                    })));
667                }
668            }
669            #[cfg(all(feature = "gpu", not(feature = "futures")))]
670            Backend::Gpu => {
671                // Without futures, fall through to CPU implementation
672            }
673            Backend::Cpu => {
674                // Use CPU implementation
675            } // #[cfg(feature = "arrayfire")]
676              // Backend::ArrayFire => {
677              //     return arrayfire_gamma(input, config);
678              // }
679        }
680
681        // Default CPU implementation with optional parallelization
682        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    /// Enhanced Bessel J0 function computation with backend selection  
700    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        // Choose backend based on configuration and array size
710        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                // Use CPU implementation
720            }
721            #[cfg(feature = "gpu")]
722            Backend::Gpu => {
723                // Use CPU fallback for Bessel functions
724            }
725        }
726
727        // Default CPU implementation
728        Ok(BesselResult::Immediate(input.mapv(crate::bessel::j0)))
729    }
730
731    /// Enhanced error function computation
732    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    /// Enhanced factorial function computation
753    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    /// Enhanced softmax computation
780    pub fn softmax_1d(
781        input: ArrayView1<f64>,
782        _config: &ArrayConfig,
783    ) -> SpecialResult<Array<f64, scirs2_core::ndarray::Ix1>> {
784        // Use existing optimized implementation from statistical module
785        crate::statistical::softmax(input)
786    }
787
788    /// Result type for gamma computations that can be immediate, lazy, or async
789    pub enum GammaResult<D>
790    where
791        D: Dimension,
792    {
793        /// Immediate result computed synchronously
794        Immediate(Array<f64, D>),
795        /// Lazy result computed on demand
796        #[cfg(feature = "lazy")]
797        Lazy(LazyArray<f64, D>),
798        /// Future result computed asynchronously (e.g., on GPU)
799        #[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        /// Force evaluation of the result
808        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        /// Check if result is immediately available
819        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    /// Result type for Bessel function computations
831    pub enum BesselResult<D>
832    where
833        D: Dimension,
834    {
835        /// Immediate result computed synchronously
836        Immediate(Array<f64, D>),
837        /// Lazy result computed on demand
838        #[cfg(feature = "lazy")]
839        Lazy(LazyArray<f64, D>),
840    }
841
842    impl<D> BesselResult<D>
843    where
844        D: Dimension,
845    {
846        /// Force evaluation of the result
847        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    // ArrayFire backend implementation for gamma function (disabled - placeholder)
857    // #[cfg(feature = "arrayfire")]
858    // fn arrayfire_gamma<D>(
859    //     input: &Array<f64, D>,
860    //     config: &ArrayConfig,
861    // ) -> SpecialResult<GammaResult<D>>
862    // where
863    //     D: Dimension,
864    // {
865    //     use arrayfire as af;
866    //
867    //     // Initialize ArrayFire if not already done
868    //     af::set_backend(af::Backend::DEFAULT);
869    //     af::set_device(0);
870    //
871    //     // Convert ndarray to ArrayFire array
872    //     let input_vec: Vec<f64> = input.iter().cloned().collect();
873    //     let dims = input.shape();
874    //
875    //     // Create ArrayFire array
876    //     let afinput = match dims.len() {
877    //         1 => af::Array::new(&input_vec, af::Dim4::new(&[dims[0] as u64, 1, 1, 1])),
878    //         2 => af::Array::new(&input_vec, af::Dim4::new(&[dims[0] as u64, dims[1] as u64, 1, 1])),
879    //         3 => af::Array::new(&input_vec, af::Dim4::new(&[dims[0] as u64, dims[1] as u64, dims[2] as u64, 1])),
880    //         4 => af::Array::new(&input_vec, af::Dim4::new(&[dims[0] as u64, dims[1] as u64, dims[2] as u64, dims[3] as u64])),
881    //         _ => {
882    //             // For higher dimensions, flatten and reshape later
883    //             af::Array::new(&input_vec, af::Dim4::new(&[input_vec.len() as u64, 1, 1, 1]))
884    //         }
885    //     };
886    //
887    //     // Compute gamma function using ArrayFire
888    //     let af_result = arrayfire_gamma_kernel(&afinput)?;
889    //
890    //     // Convert result back to ndarray
891    //     let mut result_vec = vec![0.0; input.len()];
892    //     af_result.host(&mut result_vec);
893    //
894    //     let result = Array::from_vec(result_vec)
895    //         .to_shape(input.dim())
896    //         .map_err(|e| SpecialError::ComputationError(format!("Shape conversion error: {}", e)))?
897    //         .into_owned();
898    //
899    //     Ok(GammaResult::Immediate(result))
900    // }
901
902    // ArrayFire kernel for gamma function computation (disabled - placeholder)
903    // #[cfg(feature = "arrayfire")]
904    // fn arrayfire_gamma_kernel(input: &arrayfire::Array<f64>) -> SpecialResult<arrayfire::Array<f64>> {
905    //     use arrayfire as af;
906    //
907    //     // Check for negative values (gamma undefined for negative integers)
908    //     let negative_mask = af::lt(input, &0.0, false);
909    //     let has_negatives = af::any_true_all(&negative_mask).0;
910    //
911    //     if has_negatives {
912    //         log::warn!("Gamma function called with negative values, may produce NaN");
913    //     }
914    //
915    //     // Compute gamma using ArrayFire's built-in function if available,
916    //     // otherwise implement Lanczos approximation
917    //     let result = if af::get_backend() == af::Backend::CUDA || af::get_backend() == af::Backend::OPENCL {
918    //         // Use GPU-accelerated computation
919    //         arrayfire_gamma_lanczos(input)?
920    //     } else {
921    //         // Fallback to CPU
922    //         arrayfire_gamma_lanczos(input)?
923    //     };
924    //
925    //     Ok(result)
926    // }
927
928    // Lanczos approximation for gamma function in ArrayFire (disabled - placeholder)
929    // #[cfg(feature = "arrayfire")]
930    // fn arrayfire_gamma_lanczos(x: &arrayfire::Array<f64>) -> SpecialResult<arrayfire::Array<f64>> {
931    //     use arrayfire as af;
932    //
933    //     // Lanczos coefficients
934    //     let g = 7.0;
935    //     let coeffs = vec![
936    //         0.99999999999980993,
937    //         676.5203681218851,
938    //         -1259.1392167224028,
939    //         771.32342877765313,
940    //         -176.61502916214059,
941    //         12.507343278686905,
942    //         -0.13857109526572012,
943    //         9.9843695780195716e-6,
944    //         1.5056327351493116e-7,
945    //     ];
946    //
947    //     // Handle reflection formula for x < 0.5
948    //     let half = af::constant(0.5, x.dims());
949    //     let use_reflection = af::lt(x, &half, false);
950    //
951    //     // For reflection formula: Γ(z) = π / (sin(πz) × Γ(1-z))
952    //     let pi = af::constant(std::f64::consts::PI, x.dims());
953    //     let one = af::constant(1.0, x.dims());
954    //     let reflected_x = af::sub(&one, x, false);
955    //
956    //     // Compute main Lanczos approximation
957    //     let z = af::sub(x, &one, false);
958    //     let mut acc = af::constant(coeffs[0], x.dims());
959    //
960    //     for (i, &coeff) in coeffs.iter().enumerate().skip(1) {
961    //         let k = af::constant(i as f64, x.dims());
962    //         let denominator = af::add(&z, &k, false);
963    //         let term = af::div(&af::constant(coeff, x.dims()), &denominator, false);
964    //         acc = af::add(&acc, &term, false);
965    //     }
966    //
967    //     let t = af::add(&z, &af::constant(g + 0.5, x.dims()), false);
968    //     let sqrt_2pi = af::constant((2.0 * std::f64::consts::PI).sqrt(), x.dims());
969    //
970    //     let z_plus_half = af::add(&z, &af::constant(0.5, x.dims()), false);
971    //     let t_pow = af::pow(&t, &z_plus_half, false);
972    //     let exp_neg_t = af::exp(&af::mul(&t, &af::constant(-1.0, x.dims()), false));
973    //
974    //     let gamma_main = af::mul(&af::mul(&sqrt_2pi, &acc, false), &af::mul(&t_pow, &exp_neg_t, false), false);
975    //
976    //     // Apply reflection formula where needed
977    //     let sin_pi_x = af::sin(&af::mul(&pi, x, false));
978    //     let gamma_reflected = af::div(&pi, &af::mul(&sin_pi_x, &gamma_main, false), false);
979    //
980    //     // Select appropriate result based on x value
981    //     let result = af::select(&use_reflection, &gamma_reflected, &gamma_main);
982    //
983    //     Ok(result)
984    // }
985
986    /// Chunked processing for large arrays
987    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        // Process in chunks to manage memory usage
1002        #[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        // Default sequential processing
1015        Ok(input.mapv(operation))
1016    }
1017}
1018
1019/// Complex number array operations
1020pub mod complex {
1021    use super::*;
1022    use scirs2_core::numeric::Complex64;
1023
1024    /// Apply Lambert W function to complex array
1025    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
1041/// High-level convenience functions for common array operations
1042pub mod convenience {
1043    use super::*;
1044    use scirs2_core::ndarray::{Array1, Array2};
1045
1046    /// Apply gamma function to 1D array with automatic backend selection
1047    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    /// Apply gamma function to 1D array with custom config
1054    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    /// Apply gamma function to 2D array with automatic backend selection
1063    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    /// Create lazy gamma computation for large arrays
1070    #[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            // Force lazy evaluation
1088            Ok(super::lazy::lazy_gamma(input.clone(), config))
1089        }
1090    }
1091
1092    /// GPU-accelerated gamma computation
1093    #[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    /// Apply Bessel J0 function to 1D array
1102    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    /// Apply Bessel J0 function with custom config
1109    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    /// Apply error function to 1D array
1121    pub fn erf_1d(input: &Array1<f64>) -> SpecialResult<Array1<f64>> {
1122        let config = ArrayConfig::default();
1123        vectorized::erf_array(input, &config)
1124    }
1125
1126    /// Apply error function with parallel processing
1127    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    /// Apply factorial to 1D array
1139    pub fn factorial_1d(input: &Array1<u32>) -> SpecialResult<Array1<f64>> {
1140        let config = ArrayConfig::default();
1141        vectorized::factorial_array(input, &config)
1142    }
1143
1144    /// Apply softmax to 1D array
1145    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    /// Batch processing for multiple arrays
1151    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    /// Create configuration for different use cases
1169    pub struct ConfigBuilder {
1170        config: ArrayConfig,
1171    }
1172
1173    impl ConfigBuilder {
1174        /// Create a new configuration builder
1175        pub fn new() -> Self {
1176            Self {
1177                config: ArrayConfig::default(),
1178            }
1179        }
1180
1181        /// Set backend type
1182        pub fn backend(mut self, backend: Backend) -> Self {
1183            self.config.backend = backend;
1184            self
1185        }
1186
1187        /// Enable parallel processing
1188        pub fn parallel(mut self, parallel: bool) -> Self {
1189            self.config.parallel = parallel;
1190            self
1191        }
1192
1193        /// Set chunk size for processing
1194        pub fn chunksize(mut self, size: usize) -> Self {
1195            self.config.chunksize = size;
1196            self
1197        }
1198
1199        /// Set memory limit
1200        pub fn memory_limit(mut self, limit: usize) -> Self {
1201            self.config.memory_limit = limit;
1202            self
1203        }
1204
1205        /// Set lazy evaluation threshold
1206        pub fn lazy_threshold(mut self, threshold: usize) -> Self {
1207            self.config.lazy_threshold = threshold;
1208            self
1209        }
1210
1211        /// Build the configuration
1212        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    /// Create a configuration optimized for large arrays
1224    pub fn large_array_config() -> ArrayConfig {
1225        ConfigBuilder::new()
1226            .chunksize(8192)
1227            .memory_limit(4 * 1024 * 1024 * 1024) // 4GB
1228            .lazy_threshold(50_000)
1229            .parallel(true)
1230            .build()
1231    }
1232
1233    /// Create a configuration optimized for small arrays
1234    pub fn small_array_config() -> ArrayConfig {
1235        ConfigBuilder::new()
1236            .chunksize(256)
1237            .lazy_threshold(100_000) // Higher threshold to avoid lazy overhead
1238            .parallel(false)
1239            .build()
1240    }
1241
1242    /// Create a configuration for GPU acceleration
1243    #[cfg(feature = "gpu")]
1244    pub fn gpu_config() -> ArrayConfig {
1245        ConfigBuilder::new()
1246            .backend(Backend::Gpu)
1247            .chunksize(4096)
1248            .build()
1249    }
1250
1251    /// Create a configuration for lazy evaluation
1252    #[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        // Γ(1)=1, Γ(2)=1, Γ(3)=2, Γ(4)=6, Γ(5)=24
1285        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        // Check that result sums to 1
1321        assert_relative_eq!(result.sum(), 1.0, epsilon = 1e-10);
1322
1323        // Check that all values are positive
1324        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); // 16MB for two f64 arrays
1334
1335        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        // Check that computation is deferred
1376        assert!(!lazy_array.is_computed());
1377        assert_eq!(lazy_array.shape(), input.shape());
1378
1379        // Force computation
1380        let result = lazy_array.compute().expect("Operation failed");
1381        assert_eq!(result.shape(), input.shape());
1382
1383        // Verify some values
1384        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); // Size above lazy threshold
1391        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        // Should get immediate result for CPU backend
1454        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        // Test immediate result
1477        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        // Test properties before computation
1502        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        // Test computation
1508        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        // Test that GPU functions work (fallback to CPU if no GPU)
1516        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); // Lower precision for GPU
1522                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                // GPU not available, which is acceptable for tests
1527            }
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        // Check that results are finite (not NaN)
1546        for val in result.iter() {
1547            assert!(val.re.is_finite());
1548            assert!(val.im.is_finite());
1549        }
1550    }
1551}