Skip to main content

scirs2_core/array_protocol/
mod.rs

1// Copyright (c) 2025, `SciRS2` Team
2//
3// Licensed under the Apache License, Version 2.0
4// (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
5//
6
7//! Implementation of Array Protocol (similar to ``NumPy``'s `__array_function__` protocol)
8//!
9//! This module provides a mechanism for third-party array implementations to
10//! override ``SciRS2`` functions. It is inspired by ``NumPy``'s `__array_function__`
11//! protocol defined in NEP-18.
12//!
13//! The protocol enables third-party arrays to implement ``SciRS2`` functions in a way
14//! that is recognized by the ``SciRS2`` library. This allows for seamless integration with
15//! distributed arrays, GPU arrays, and other custom array implementations.
16//!
17//! ## Core Components
18//!
19//! The Array Protocol system includes:
20//!
21//! * Specialized array implementations (GPU, distributed, JIT)
22//! * Automatic device placement with `AutoDevice`
23//! * Mixed-precision operations
24//! * Neural network layers and models
25//! * Gradient computation and training capabilities
26//! * Distributed training and model serialization
27
28use std::any::{Any, TypeId};
29use std::collections::HashMap;
30use std::fmt::{Debug, Display};
31use std::marker::PhantomData;
32use std::sync::{Arc, LazyLock, RwLock};
33use std::time::{Duration, Instant};
34
35use crate::error::{CoreError, CoreResult, ErrorContext};
36
37// Internal submodules
38mod distributed_impl;
39mod gpu_impl;
40#[cfg(feature = "array_protocol_wgpu")]
41pub mod gpu_ndarray;
42#[cfg(feature = "array_protocol_wgpu")]
43mod gpu_ndarray_shaders;
44mod jit_impl;
45mod operations;
46
47// Re-export the array_function_dispatch macro
48pub use crate::array_function_dispatch;
49
50// Public submodules
51pub mod auto_device;
52pub mod distributed_training;
53pub mod grad;
54pub mod mixed_precision;
55pub mod ml_ops;
56pub mod neural;
57#[cfg(feature = "serialization")]
58pub mod serialization;
59pub mod training;
60
61/// Trait for objects that can handle the array protocol.
62///
63/// This is similar to `NumPy`'s `__array_function__` protocol.
64pub trait ArrayProtocol: Any + Send + Sync {
65    /// Implementation of the array protocol.
66    ///
67    /// * `func` - The function being called
68    /// * `types` - The types of all arguments that implement `ArrayProtocol`
69    /// * `args` - The arguments to the function
70    /// * `kwargs` - Named arguments to the function
71    ///
72    /// Returns `Ok(result)` if the operation is successful, or `Err(NotImplemented)`
73    /// if the operation is not implemented for this type.
74    ///
75    /// # Errors
76    ///
77    /// Returns `Err(NotImplemented)` if the operation is not supported by this array type.
78    fn array_function(
79        &self,
80        func: &ArrayFunction,
81        types: &[TypeId],
82        args: &[Box<dyn Any>],
83        kwargs: &HashMap<String, Box<dyn Any>>,
84    ) -> Result<Box<dyn Any>, NotImplemented>;
85
86    /// Get the array as Any for downcasting
87    #[must_use]
88    fn as_any(&self) -> &dyn Any;
89
90    /// Get the shape of the array (default implementation returns empty slice)
91    #[must_use]
92    fn shape(&self) -> &[usize] {
93        &[]
94    }
95
96    /// Get the data type of the array (default implementation returns f64)
97    #[must_use]
98    fn dtype(&self) -> TypeId {
99        TypeId::of::<f64>()
100    }
101
102    /// Whether this concrete array type provides its own [`array_function`](Self::array_function)
103    /// handling for the named operation.
104    ///
105    /// Dispatch uses this to decide whether it's worth delegating to this
106    /// argument's own `array_function` at all. Types that only implement a
107    /// subset of operations (like the plain [`NdarrayWrapper`], whose
108    /// `array_function` only matches a handful of operation names) should
109    /// override this to report `false` for anything outside that subset, so
110    /// callers correctly fall through to another candidate (or to a
111    /// plain-ndarray fallback implementation) instead of receiving a
112    /// spurious `NotImplemented` from this type's dispatch and giving up
113    /// immediately.
114    ///
115    /// The default assumes the type may handle any operation:
116    /// `array_function` itself remains the ultimate authority (returning
117    /// `Err(NotImplemented)` when it truly doesn't apply), so types that
118    /// already do that correctly (or that implement most/all operations)
119    /// don't need to override this.
120    #[must_use]
121    fn supports_op(&self, _opname: &str) -> bool {
122        true
123    }
124
125    /// Clone this array protocol object.
126    #[must_use]
127    fn box_clone(&self) -> Box<dyn ArrayProtocol>;
128}
129
130/// Make `Box<dyn ArrayProtocol>` cloneable via `box_clone`
131impl Clone for Box<dyn ArrayProtocol> {
132    fn clone(&self) -> Self {
133        self.box_clone()
134    }
135}
136
137/// Marker for functions not implemented by a specific type.
138///
139/// This is part of the Array Protocol API design and is used as a marker to indicate
140/// that a function is not implemented by a specific array type. It's different from
141/// the `CoreError::NotImplementedError` enum variant, which is used for error reporting.
142///
143/// When an error is propagated up the call chain, `NotImplemented` is converted
144/// to `OperationError::NotImplemented` and then to `CoreError::NotImplementedError`
145/// for consistent error handling.
146#[derive(Debug, Clone, Copy)]
147pub struct NotImplemented;
148
149impl Display for NotImplemented {
150    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151        write!(f, "NotImplemented")
152    }
153}
154
155/// Type alias for the complex function implementation type
156pub type ArrayFunctionImpl = dyn Fn(&[Box<dyn Any>], &HashMap<String, Box<dyn Any>>) -> CoreResult<Box<dyn Any>>
157    + Send
158    + Sync;
159
160/// A wrapper for functions that can be overridden by the array protocol.
161#[derive(Clone)]
162pub struct ArrayFunction {
163    /// The name of the function, including its module path
164    pub name: &'static str,
165
166    /// The function implementation
167    pub implementation: Arc<ArrayFunctionImpl>,
168}
169
170impl Debug for ArrayFunction {
171    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172        f.debug_struct("ArrayFunction")
173            .field("name", &self.name)
174            .finish_non_exhaustive()
175    }
176}
177
178impl PartialEq for ArrayFunction {
179    fn eq(&self, other: &Self) -> bool {
180        self.name == other.name
181    }
182}
183
184impl Eq for ArrayFunction {}
185
186impl std::hash::Hash for ArrayFunction {
187    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
188        self.name.hash(state);
189    }
190}
191
192impl ArrayFunction {
193    /// Create a new array function with the given name
194    #[must_use]
195    pub fn new(name: &'static str) -> Self {
196        Self {
197            name,
198            // Default implementation that returns NotImplemented
199            implementation: Arc::new(|_args, _kwargs| {
200                Err(CoreError::NotImplementedError(ErrorContext::new(
201                    "Function not implemented".to_string(),
202                )))
203            }),
204        }
205    }
206}
207
208/// Cache entry for function dispatch optimization
209#[derive(Debug, Clone)]
210pub struct DispatchCacheEntry {
211    /// Type signature for the cached result
212    #[allow(dead_code)]
213    type_signature: Vec<TypeId>,
214    /// Which implementation type to try first
215    #[allow(dead_code)]
216    preferred_impl_type: TypeId,
217    /// Cache timestamp for TTL management
218    timestamp: Instant,
219    /// Number of cache hits
220    hit_count: u64,
221}
222
223/// Registry of all array functions with dispatch caching.
224#[derive(Debug)]
225pub struct ArrayFunctionRegistry {
226    /// Map of function names to array functions
227    functions: HashMap<&'static str, ArrayFunction>,
228    /// Dispatch cache for performance optimization
229    dispatch_cache: HashMap<(&'static str, Vec<TypeId>), DispatchCacheEntry>,
230    /// Maximum cache size to prevent unbounded growth
231    max_cache_size: usize,
232    /// Cache TTL for entries (prevents stale cache)
233    cache_ttl: Duration,
234}
235
236impl Default for ArrayFunctionRegistry {
237    fn default() -> Self {
238        Self {
239            functions: HashMap::new(),
240            dispatch_cache: HashMap::new(),
241            max_cache_size: 1000,                // Reasonable default cache size
242            cache_ttl: Duration::from_secs(300), // 5 minutes TTL
243        }
244    }
245}
246
247impl ArrayFunctionRegistry {
248    /// Get the global registry.
249    #[must_use]
250    pub fn global() -> &'static RwLock<Self> {
251        static REGISTRY: LazyLock<RwLock<ArrayFunctionRegistry>> =
252            LazyLock::new(|| RwLock::new(ArrayFunctionRegistry::default()));
253        &REGISTRY
254    }
255
256    /// Register a new array function.
257    pub fn register(&mut self, func: ArrayFunction) {
258        self.functions.insert(func.name, func);
259    }
260
261    /// Get an array function by name.
262    #[must_use]
263    #[allow(dead_code)]
264    pub fn get(&self, name: &str) -> Option<&ArrayFunction> {
265        self.functions.get(name)
266    }
267
268    /// Get all registered functions.
269    #[must_use]
270    pub fn all_functions(&self) -> Vec<&ArrayFunction> {
271        self.functions.values().collect()
272    }
273
274    /// Get cached dispatch entry for optimization
275    #[must_use]
276    pub fn get_cached_dispatch(
277        &self,
278        funcname: &'static str,
279        types: &[TypeId],
280    ) -> Option<&DispatchCacheEntry> {
281        let key = (funcname, types.to_vec());
282        if let Some(entry) = self.dispatch_cache.get(&key) {
283            // Check if cache entry is still valid (TTL check)
284            if entry.timestamp.elapsed() < self.cache_ttl {
285                return Some(entry);
286            }
287        }
288        None
289    }
290
291    /// Cache dispatch result for future optimization
292    pub fn cache_dispatch(
293        &mut self,
294        funcname: &'static str,
295        types: Vec<TypeId>,
296        impl_type: TypeId,
297    ) {
298        // Clean cache if it's getting too large
299        if self.dispatch_cache.len() >= self.max_cache_size {
300            self.cleanup_cache();
301        }
302
303        let key = (funcname, types.clone());
304        let entry = DispatchCacheEntry {
305            type_signature: types,
306            preferred_impl_type: impl_type,
307            timestamp: Instant::now(),
308            hit_count: 0,
309        };
310        self.dispatch_cache.insert(key, entry);
311    }
312
313    /// Update cache hit count for an entry
314    pub fn update_cache_hit(&mut self, funcname: &'static str, types: &[TypeId]) {
315        let key = (funcname, types.to_vec());
316        if let Some(entry) = self.dispatch_cache.get_mut(&key) {
317            entry.hit_count += 1;
318        }
319    }
320
321    /// Clean up expired cache entries
322    fn cleanup_cache(&mut self) {
323        let now = Instant::now();
324        self.dispatch_cache
325            .retain(|_, entry| now.duration_since(entry.timestamp) < self.cache_ttl);
326
327        // If still too large, remove least recently used entries
328        if self.dispatch_cache.len() >= self.max_cache_size {
329            let mut entries: Vec<_> = self
330                .dispatch_cache
331                .iter()
332                .map(|(k, v)| (k.clone(), v.hit_count))
333                .collect();
334            entries.sort_by_key(|(_, hit_count)| *hit_count);
335
336            // Remove bottom 25% of entries by hit count
337            let to_remove = self.dispatch_cache.len() / 4;
338            let keys_to_remove: Vec<_> = entries
339                .iter()
340                .take(to_remove)
341                .map(|(key, _)| key.clone())
342                .collect();
343            for key in keys_to_remove {
344                self.dispatch_cache.remove(&key);
345            }
346        }
347    }
348
349    /// Get cache statistics for monitoring
350    #[must_use]
351    pub fn cache_stats(&self) -> HashMap<String, u64> {
352        let mut stats = HashMap::new();
353        stats.insert("cache_size".to_string(), self.dispatch_cache.len() as u64);
354        stats.insert("max_cache_size".to_string(), self.max_cache_size as u64);
355
356        let total_hits: u64 = self.dispatch_cache.values().map(|e| e.hit_count).sum();
357        stats.insert("total_hits".to_string(), total_hits);
358
359        stats
360    }
361}
362
363/// Helper function to extract all arguments implementing the `ArrayProtocol` trait
364/// that also claim (via [`ArrayProtocol::supports_op`]) to support the named operation.
365///
366/// This is similar to `NumPy`'s `_get_implementing_args` function.
367/// Optimized version with pre-allocated capacity and fast-path for common cases.
368///
369/// `func_name` should be the same operation name later passed to
370/// `array_function`/`ArrayFunction::new` at the call site (e.g.
371/// `"scirs2::array_protocol::operations::subtract"`). Filtering on it here
372/// means an argument whose concrete type doesn't implement this specific
373/// operation is excluded from the candidate list, so callers correctly fall
374/// through to a different implementation (or a plain-ndarray fallback)
375/// instead of delegating to a candidate that's guaranteed to return
376/// `NotImplemented` for this particular `func_name`.
377pub fn get_implementing_args<'a>(
378    func_name: &str,
379    args: &'a [Box<dyn Any>],
380) -> Vec<(TypeId, &'a dyn ArrayProtocol)> {
381    if args.is_empty() {
382        return Vec::new();
383    }
384
385    // Pre-allocate with capacity to avoid reallocation
386    let mut implementing_args = Vec::with_capacity(args.len());
387
388    for arg in args {
389        if let Some(array_protocol_obj) = arg.downcast_ref::<Box<dyn ArrayProtocol>>() {
390            if !array_protocol_obj.supports_op(func_name) {
391                continue;
392            }
393            let type_id = (**array_protocol_obj).type_id();
394            implementing_args.push((type_id, &**array_protocol_obj));
395        }
396    }
397
398    implementing_args
399}
400
401/// Calls the array protocol with the given function and arguments.
402///
403/// * `func` - The array function to call
404/// * `args` - The arguments to the function
405/// * `kwargs` - Named arguments to the function
406///
407/// Returns the result of the function call, or an error if the function
408/// cannot be dispatched to any of the array protocol implementations.
409///
410/// Optimized version with caching and fast-path optimizations.
411#[allow(dead_code)]
412pub fn array_function_dispatch(
413    func: &ArrayFunction,
414    args: &[Box<dyn Any>],
415    kwargs: &HashMap<String, Box<dyn Any>>,
416) -> CoreResult<Box<dyn Any>> {
417    // Fast path for empty args
418    if args.is_empty() {
419        return (func.implementation)(args, kwargs);
420    }
421
422    // Find all arguments implementing ArrayProtocol
423    let implementing_args = get_implementing_args(func.name, args);
424
425    if implementing_args.is_empty() {
426        // No arguments implement ArrayProtocol, use default implementation
427        return (func.implementation)(args, kwargs);
428    }
429
430    // Fast path for single implementing argument
431    if implementing_args.len() == 1 {
432        let (type_id, array_protocol_obj) = implementing_args[0];
433        let types = [type_id];
434        match array_protocol_obj.array_function(func, &types, args, kwargs) {
435            Ok(result) => return Ok(result),
436            Err(NotImplemented) => {
437                return Err(CoreError::DispatchError(ErrorContext::new(format!(
438                    "No implementation found for {} with type {:?}",
439                    func.name, type_id
440                ))));
441            }
442        }
443    }
444
445    // Extract all unique types that implement ArrayProtocol (optimized)
446    let mut unique_types = Vec::with_capacity(implementing_args.len());
447    let mut seen_types = std::collections::HashSet::with_capacity(implementing_args.len());
448
449    for &(type_id, _) in &implementing_args {
450        if seen_types.insert(type_id) {
451            unique_types.push(type_id);
452        }
453    }
454
455    // Try dispatching to each implementation in priority order
456    for (_, array_protocol_obj) in implementing_args {
457        if let Ok(result) = array_protocol_obj.array_function(func, &unique_types, args, kwargs) {
458            return Ok(result);
459        }
460    }
461
462    // If we get here, no implementation was found
463    Err(CoreError::DispatchError(ErrorContext::new(format!(
464        "No implementation found for {} with {} argument types: {:?}",
465        func.name,
466        unique_types.len(),
467        unique_types
468    ))))
469}
470
471/// Decorator for adding array function dispatch capabilities to a function.
472///
473/// This is similar to `NumPy`'s `array_function_dispatch` decorator.
474pub struct ArrayFunctionDecorator<F> {
475    function: F,
476    name: &'static str,
477}
478
479impl<F> ArrayFunctionDecorator<F>
480where
481    F: Send + Sync + 'static,
482{
483    /// Create a new array function decorator.
484    #[must_use]
485    pub fn new(function: F, name: &'static str) -> Self {
486        Self { function, name }
487    }
488
489    /// Register the function with the global registry.
490    pub fn register(self) -> F {
491        let implementation = Arc::new(
492            move |_args: &[Box<dyn Any>], _kwargs: &HashMap<String, Box<dyn Any>>| {
493                // Implementation that converts generic arguments to specific types
494                // and calls the original function
495                // This is a simplified version - in practice, we would need more complex
496                // type conversion
497                Err(CoreError::NotImplementedError(ErrorContext::new(
498                    "ArrayFunctionDecorator: Type conversion in array_function_dispatch is not implemented yet".to_string()
499                )))
500            },
501        );
502
503        let func = ArrayFunction {
504            name: self.name,
505            implementation,
506        };
507
508        // Register the function with the global registry
509        let registry = ArrayFunctionRegistry::global();
510        if let Ok(mut registry) = registry.write() {
511            registry.register(func);
512        } else {
513            eprintln!("Warning: Failed to acquire write lock on ArrayFunctionRegistry, skipping function registration");
514            // Continue without registration - this may result in reduced functionality but avoids crash
515        }
516
517        self.function
518    }
519}
520
521/// Trait for arrays that can support GPU operations.
522pub trait GPUArray: ArrayProtocol {
523    /// Move the array to GPU.
524    fn to_gpu(&self) -> CoreResult<Box<dyn GPUArray>>;
525
526    /// Move the array from GPU to CPU.
527    fn to_cpu(&self) -> CoreResult<Box<dyn ArrayProtocol>>;
528
529    /// Check if the array is on GPU.
530    #[must_use]
531    fn is_on_gpu(&self) -> bool;
532
533    /// Get information about the GPU device that holds this array.
534    #[must_use]
535    fn device_info(&self) -> HashMap<String, String>;
536}
537
538/// Trait for distributed arrays that can span multiple machines.
539pub trait DistributedArray: ArrayProtocol {
540    /// Get information about the distribution of this array.
541    #[must_use]
542    fn distribution_info(&self) -> HashMap<String, String>;
543
544    /// Gather the distributed array to a single node.
545    fn gather(&self) -> CoreResult<Box<dyn ArrayProtocol>>;
546
547    /// Scatter a regular array to a distributed array.
548    fn scatter(&self, chunks: usize) -> CoreResult<Box<dyn DistributedArray>>;
549
550    /// Check if this array is distributed.
551    #[must_use]
552    fn is_distributed(&self) -> bool;
553}
554
555/// JIT (Just-In-Time) compilation support for arrays.
556pub trait JITArray: ArrayProtocol {
557    /// Compile an expression to be evaluated on this array.
558    fn compile(&self, expression: &str) -> CoreResult<Box<dyn JITFunction>>;
559
560    /// Check if JIT compilation is supported for this array type.
561    #[must_use]
562    fn supports_jit(&self) -> bool;
563
564    /// Get information about the JIT compiler being used.
565    #[must_use]
566    fn jit_info(&self) -> HashMap<String, String>;
567}
568
569/// A JIT-compiled function that can be evaluated on arrays.
570pub trait JITFunction: Send + Sync {
571    /// Evaluate the function with the given arguments.
572    fn evaluate(&self, args: &[Box<dyn Any>]) -> CoreResult<Box<dyn Any>>;
573
574    /// Get the source code of the compiled function.
575    #[must_use]
576    fn source(&self) -> String;
577
578    /// Get information about how the function was compiled.
579    #[must_use]
580    fn compile_info(&self) -> HashMap<String, String>;
581
582    /// Clone this JIT function into a `Box<dyn JITFunction>`.
583    #[must_use]
584    fn clone_box(&self) -> Box<dyn JITFunction>;
585}
586
587/// A factory for creating JIT functions for specific array implementations.
588pub trait JITFunctionFactory: Send + Sync {
589    /// Create a new JIT function for the given expression and array type.
590    fn create_jit_function(
591        &self,
592        expression: &str,
593        array_typeid: TypeId,
594    ) -> CoreResult<Box<dyn JITFunction>>;
595
596    /// Check if this factory supports the given array type.
597    #[must_use]
598    fn supports_array_type(&self, array_typeid: TypeId) -> bool;
599}
600
601/// Registry of JIT function factories.
602#[derive(Default)]
603pub struct JITFactoryRegistry {
604    factories: Vec<Box<dyn JITFunctionFactory>>,
605}
606
607impl std::fmt::Debug for JITFactoryRegistry {
608    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
609        write!(
610            f,
611            "JITFactoryRegistry {{ factories: {} }}",
612            self.factories.len()
613        )
614    }
615}
616
617impl JITFactoryRegistry {
618    /// Get the global registry.
619    #[must_use]
620    pub fn global() -> &'static RwLock<Self> {
621        static REGISTRY: LazyLock<RwLock<JITFactoryRegistry>> = LazyLock::new(|| {
622            RwLock::new(JITFactoryRegistry {
623                factories: Vec::new(),
624            })
625        });
626        &REGISTRY
627    }
628
629    /// Register a new JIT function factory.
630    pub fn register(&mut self, factory: Box<dyn JITFunctionFactory>) {
631        self.factories.push(factory);
632    }
633
634    /// Get a JIT function factory that supports the given array type.
635    #[must_use]
636    pub fn get_factory_for_array_type(
637        &self,
638        array_typeid: TypeId,
639    ) -> Option<&dyn JITFunctionFactory> {
640        for factory in &self.factories {
641            if factory.supports_array_type(array_typeid) {
642                return Some(&**factory);
643            }
644        }
645        None
646    }
647}
648
649/// Downcast an `array_function` argument to a concrete `ArrayProtocol` impl,
650/// tolerating either boxing convention seen across this module's callers.
651///
652/// Most callers (the dispatch functions in [`crate::array_protocol::operations`],
653/// which go through [`get_implementing_args`]) erase an opaque `&dyn
654/// ArrayProtocol` via `Box::new(x.box_clone())`, so `arg`'s concrete type is
655/// `Box<dyn ArrayProtocol>` wrapping the real value — there is no other way to
656/// produce a `Box<dyn Any>` from a trait object without already knowing its
657/// concrete type. A few callers instead build `args` themselves from an
658/// already-concrete value (for example
659/// [`mixed_precision`](super::mixed_precision)'s `MixedPrecisionArray`
660/// delegating straight to `NdarrayWrapper::array_function`), boxing the
661/// concrete type directly with no extra indirection. Trying the
662/// double-boxed shape first and falling back to a direct downcast handles
663/// both without requiring every internal caller to agree on one convention.
664fn downcast_array_function_arg<C: 'static>(arg: &dyn Any) -> Option<&C> {
665    if let Some(boxed) = arg.downcast_ref::<Box<dyn ArrayProtocol>>() {
666        return boxed.as_any().downcast_ref::<C>();
667    }
668    arg.downcast_ref::<C>()
669}
670
671/// Downcast an `array_function` argument to an owned, dynamically-dimensioned
672/// array of element type `T`, regardless of whether the concrete wrapper is
673/// `NdarrayWrapper<T, IxDyn>`, `NdarrayWrapper<T, Ix2>`, or `NdarrayWrapper<T,
674/// Ix1>` (tried in that order), and regardless of which boxing convention
675/// wraps it (see `downcast_array_function_arg`). Used as a fallback when
676/// combining two
677/// `NdarrayWrapper` operands whose *concrete* dimension types differ but
678/// whose runtime shapes are still combinable — e.g. a gradient tensor
679/// produced by [`super::grad`]'s `backward()` as `IxDyn` combined with an
680/// `Ix2`-typed value from the forward pass.
681fn downcast_arg_to_ixdyn<T: Clone + Send + Sync + 'static>(
682    arg: &dyn Any,
683) -> Option<crate::ndarray::Array<T, crate::ndarray::IxDyn>> {
684    if let Some(w) = downcast_array_function_arg::<NdarrayWrapper<T, crate::ndarray::IxDyn>>(arg) {
685        return Some(w.as_array().clone());
686    }
687    if let Some(w) = downcast_array_function_arg::<NdarrayWrapper<T, crate::ndarray::Ix2>>(arg) {
688        return Some(w.as_array().clone().into_dyn());
689    }
690    if let Some(w) = downcast_array_function_arg::<NdarrayWrapper<T, crate::ndarray::Ix1>>(arg) {
691        return Some(w.as_array().clone().into_dyn());
692    }
693    None
694}
695
696/// A wrapper for ndarray to implement the ArrayProtocol trait.
697#[derive(Debug, Clone)]
698pub struct NdarrayWrapper<T, D: crate::ndarray::Dimension> {
699    array: crate::ndarray::Array<T, D>,
700    phantom: PhantomData<(T, D)>,
701}
702
703impl<T, D> NdarrayWrapper<T, D>
704where
705    T: Clone + 'static,
706    D: crate::ndarray::Dimension + 'static,
707{
708    /// Create a new ndarray wrapper.
709    #[must_use]
710    pub fn new(array: crate::ndarray::Array<T, D>) -> Self {
711        Self {
712            array,
713            phantom: PhantomData,
714        }
715    }
716
717    /// Get the underlying ndarray.
718    #[must_use]
719    pub const fn as_array(&self) -> &crate::ndarray::Array<T, D> {
720        &self.array
721    }
722
723    /// Convert into the underlying ndarray.
724    #[must_use]
725    pub fn into_array(self) -> crate::ndarray::Array<T, D> {
726        self.array
727    }
728
729    /// Update the underlying array with a new one.
730    pub fn array_2(&mut self, newarray: crate::ndarray::Array<T, D>) {
731        self.array = newarray;
732    }
733}
734
735/// Operation names that [`NdarrayWrapper::array_function`] actually matches
736/// against `func.name` below. Kept as a single source of truth so
737/// `supports_op` can't silently drift out of sync with the `match` arms.
738const NDARRAY_WRAPPER_SUPPORTED_OPS: &[&str] = &[
739    "scirs2::array_protocol::operations::add",
740    "scirs2::array_protocol::operations::multiply",
741    "scirs2::array_protocol::operations::matmul",
742    "scirs2::array_protocol::operations::transpose",
743    "scirs2::array_protocol::operations::sum",
744    "scirs2::array_protocol::operations::reshape",
745];
746
747impl<T, D> ArrayProtocol for NdarrayWrapper<T, D>
748where
749    T: Clone + Send + Sync + 'static,
750    D: crate::ndarray::Dimension + Send + Sync + 'static,
751{
752    fn supports_op(&self, opname: &str) -> bool {
753        NDARRAY_WRAPPER_SUPPORTED_OPS.contains(&opname)
754    }
755
756    fn array_function(
757        &self,
758        func: &ArrayFunction,
759        _types: &[TypeId],
760        args: &[Box<dyn Any>],
761        kwargs: &HashMap<String, Box<dyn Any>>,
762    ) -> Result<Box<dyn Any>, NotImplemented> {
763        match func.name {
764            "scirs2::array_protocol::operations::add" => {
765                // Addition operation for NdarrayWrapper
766                if args.len() < 2 {
767                    return Err(NotImplemented);
768                }
769
770                // See `downcast_array_function_arg`: callers disagree on whether
771                // `args[1]` is boxed as `Box<dyn ArrayProtocol>` (the dispatcher
772                // in `operations.rs`) or as the concrete type directly
773                // (`mixed_precision`'s internal delegation), so try both shapes.
774                let other = downcast_array_function_arg::<NdarrayWrapper<T, D>>(args[1].as_ref());
775
776                if let Some(other) = other {
777                    if let (Some(a), Some(b)) = (
778                        self.as_any().downcast_ref::<NdarrayWrapper<T, D>>(),
779                        other.as_any().downcast_ref::<NdarrayWrapper<T, D>>(),
780                    ) {
781                        // Need to make sure T supports addition
782                        if TypeId::of::<T>() == TypeId::of::<f64>() {
783                            let a_f64 =
784                                unsafe { &*(a as *const _ as *const NdarrayWrapper<f64, D>) };
785                            let b_f64 =
786                                unsafe { &*(b as *const _ as *const NdarrayWrapper<f64, D>) };
787                            let result = a_f64.as_array() + b_f64.as_array();
788                            return Ok(Box::new(
789                                Box::new(NdarrayWrapper::new(result)) as Box<dyn ArrayProtocol>
790                            ));
791                        } else if TypeId::of::<T>() == TypeId::of::<f32>() {
792                            let a_f32 =
793                                unsafe { &*(a as *const _ as *const NdarrayWrapper<f32, D>) };
794                            let b_f32 =
795                                unsafe { &*(b as *const _ as *const NdarrayWrapper<f32, D>) };
796                            let result = a_f32.as_array() + b_f32.as_array();
797                            return Ok(Box::new(
798                                Box::new(NdarrayWrapper::new(result)) as Box<dyn ArrayProtocol>
799                            ));
800                        }
801                    }
802                }
803
804                // Fallback: same element type T, but `other` has a different
805                // concrete dimension type than `self` (e.g. combining a
806                // gradient tensor created as IxDyn with an Ix2/Ix1 value from
807                // the forward pass) — the fast path above requires an exact D
808                // match, so normalize both operands to IxDyn and combine there.
809                if TypeId::of::<T>() == TypeId::of::<f64>() {
810                    if let Some(other_arr) = downcast_arg_to_ixdyn::<f64>(args[1].as_ref()) {
811                        let self_f64 =
812                            unsafe { &*(self as *const _ as *const NdarrayWrapper<f64, D>) };
813                        let result = self_f64.as_array().to_owned().into_dyn() + other_arr;
814                        return Ok(Box::new(
815                            Box::new(NdarrayWrapper::new(result)) as Box<dyn ArrayProtocol>
816                        ));
817                    }
818                } else if TypeId::of::<T>() == TypeId::of::<f32>() {
819                    if let Some(other_arr) = downcast_arg_to_ixdyn::<f32>(args[1].as_ref()) {
820                        let self_f32 =
821                            unsafe { &*(self as *const _ as *const NdarrayWrapper<f32, D>) };
822                        let result = self_f32.as_array().to_owned().into_dyn() + other_arr;
823                        return Ok(Box::new(
824                            Box::new(NdarrayWrapper::new(result)) as Box<dyn ArrayProtocol>
825                        ));
826                    }
827                }
828                Err(NotImplemented)
829            }
830            "scirs2::array_protocol::operations::multiply" => {
831                // Element-wise multiplication for NdarrayWrapper
832                if args.len() < 2 {
833                    return Err(NotImplemented);
834                }
835
836                let other = downcast_array_function_arg::<NdarrayWrapper<T, D>>(args[1].as_ref());
837
838                if let Some(other) = other {
839                    if let (Some(a), Some(b)) = (
840                        self.as_any().downcast_ref::<NdarrayWrapper<T, D>>(),
841                        other.as_any().downcast_ref::<NdarrayWrapper<T, D>>(),
842                    ) {
843                        if TypeId::of::<T>() == TypeId::of::<f64>() {
844                            let a_f64 =
845                                unsafe { &*(a as *const _ as *const NdarrayWrapper<f64, D>) };
846                            let b_f64 =
847                                unsafe { &*(b as *const _ as *const NdarrayWrapper<f64, D>) };
848                            let result = a_f64.as_array() * b_f64.as_array();
849                            return Ok(Box::new(
850                                Box::new(NdarrayWrapper::new(result)) as Box<dyn ArrayProtocol>
851                            ));
852                        } else if TypeId::of::<T>() == TypeId::of::<f32>() {
853                            let a_f32 =
854                                unsafe { &*(a as *const _ as *const NdarrayWrapper<f32, D>) };
855                            let b_f32 =
856                                unsafe { &*(b as *const _ as *const NdarrayWrapper<f32, D>) };
857                            let result = a_f32.as_array() * b_f32.as_array();
858                            return Ok(Box::new(
859                                Box::new(NdarrayWrapper::new(result)) as Box<dyn ArrayProtocol>
860                            ));
861                        }
862                    }
863                }
864
865                // Fallback: same element type T, different concrete D — see
866                // the identical fallback in the "add" arm above.
867                if TypeId::of::<T>() == TypeId::of::<f64>() {
868                    if let Some(other_arr) = downcast_arg_to_ixdyn::<f64>(args[1].as_ref()) {
869                        let self_f64 =
870                            unsafe { &*(self as *const _ as *const NdarrayWrapper<f64, D>) };
871                        let result = self_f64.as_array().to_owned().into_dyn() * other_arr;
872                        return Ok(Box::new(
873                            Box::new(NdarrayWrapper::new(result)) as Box<dyn ArrayProtocol>
874                        ));
875                    }
876                } else if TypeId::of::<T>() == TypeId::of::<f32>() {
877                    if let Some(other_arr) = downcast_arg_to_ixdyn::<f32>(args[1].as_ref()) {
878                        let self_f32 =
879                            unsafe { &*(self as *const _ as *const NdarrayWrapper<f32, D>) };
880                        let result = self_f32.as_array().to_owned().into_dyn() * other_arr;
881                        return Ok(Box::new(
882                            Box::new(NdarrayWrapper::new(result)) as Box<dyn ArrayProtocol>
883                        ));
884                    }
885                }
886                Err(NotImplemented)
887            }
888            "scirs2::array_protocol::operations::matmul" => {
889                // Matrix multiplication for NdarrayWrapper
890                if args.len() < 2 {
891                    return Err(NotImplemented);
892                }
893
894                // We can only handle matrix multiplication for 2D arrays
895                // Check for 2D array using TypeId
896                if TypeId::of::<D>() != TypeId::of::<crate::ndarray::Ix2>() {
897                    return Err(NotImplemented);
898                }
899
900                let other = downcast_array_function_arg::<NdarrayWrapper<T, D>>(args[1].as_ref());
901
902                if let Some(other) = other {
903                    // Since we've already checked TypeId::of::<D>() == TypeId::of::<crate::ndarray::Ix2>()
904                    // We can safely specialize for Ix2 matrices
905
906                    // Handle the case for f64 matrices
907                    if TypeId::of::<T>() == TypeId::of::<f64>() {
908                        // Cast to concrete _types we know how to handle
909                        let a_f64 = unsafe {
910                            &*(self as *const _ as *const NdarrayWrapper<f64, crate::ndarray::Ix2>)
911                        };
912                        let b_f64 = unsafe {
913                            &*(other as *const _ as *const NdarrayWrapper<f64, crate::ndarray::Ix2>)
914                        };
915
916                        // Get dimensions
917                        let ashape = a_f64.as_array().shape();
918                        let bshape = b_f64.as_array().shape();
919
920                        if ashape.len() != 2 || bshape.len() != 2 || ashape[1] != bshape[0] {
921                            return Err(NotImplemented);
922                        }
923
924                        // Use the higher-level dot operation which will be more efficient
925                        // than our manual implementation
926                        let result = a_f64.as_array().dot(b_f64.as_array());
927                        return Ok(Box::new(
928                            Box::new(NdarrayWrapper::new(result)) as Box<dyn ArrayProtocol>
929                        ));
930                    }
931                    // Handle the case for f32 matrices
932                    else if TypeId::of::<T>() == TypeId::of::<f32>() {
933                        // Cast to concrete _types we know how to handle
934                        let a_f32 = unsafe {
935                            &*(self as *const _ as *const NdarrayWrapper<f32, crate::ndarray::Ix2>)
936                        };
937                        let b_f32 = unsafe {
938                            &*(other as *const _ as *const NdarrayWrapper<f32, crate::ndarray::Ix2>)
939                        };
940
941                        // Get dimensions
942                        let ashape = a_f32.as_array().shape();
943                        let bshape = b_f32.as_array().shape();
944
945                        if ashape.len() != 2 || bshape.len() != 2 || ashape[1] != bshape[0] {
946                            return Err(NotImplemented);
947                        }
948
949                        // Use the higher-level dot operation which will be more efficient
950                        // than our manual implementation
951                        let result = a_f32.as_array().dot(b_f32.as_array());
952                        return Ok(Box::new(
953                            Box::new(NdarrayWrapper::new(result)) as Box<dyn ArrayProtocol>
954                        ));
955                    }
956                }
957                // If we get here, we don't know how to handle this case
958                Err(NotImplemented)
959            }
960            "scirs2::array_protocol::operations::transpose" => {
961                // Transpose operation for NdarrayWrapper
962                if TypeId::of::<T>() == TypeId::of::<f64>() {
963                    let a_f64 = unsafe { &*(self as *const _ as *const NdarrayWrapper<f64, D>) };
964                    let result = a_f64.as_array().t().to_owned();
965                    return Ok(Box::new(
966                        Box::new(NdarrayWrapper::new(result)) as Box<dyn ArrayProtocol>
967                    ));
968                } else if TypeId::of::<T>() == TypeId::of::<f32>() {
969                    let a_f32 = unsafe { &*(self as *const _ as *const NdarrayWrapper<f32, D>) };
970                    let result = a_f32.as_array().t().to_owned();
971                    return Ok(Box::new(
972                        Box::new(NdarrayWrapper::new(result)) as Box<dyn ArrayProtocol>
973                    ));
974                }
975                Err(NotImplemented)
976            }
977            "scirs2::array_protocol::operations::sum" => {
978                // Sum operation for NdarrayWrapper
979                let axis_ref = kwargs.get("axis").and_then(|a| a.downcast_ref::<usize>());
980
981                if TypeId::of::<T>() == TypeId::of::<f64>() {
982                    let a_f64 = unsafe { &*(self as *const _ as *const NdarrayWrapper<f64, D>) };
983                    match axis_ref {
984                        Some(&_ax) => {
985                            // Can't use sum_axis without RemoveAxis trait
986                            // Just return the full sum for now
987                            let result = a_f64.as_array().sum();
988                            return Ok(Box::new(result));
989                        }
990                        None => {
991                            let result = a_f64.as_array().sum();
992                            return Ok(Box::new(result));
993                        }
994                    }
995                } else if TypeId::of::<T>() == TypeId::of::<f32>() {
996                    let a_f32 = unsafe { &*(self as *const _ as *const NdarrayWrapper<f32, D>) };
997                    match axis_ref {
998                        Some(&_ax) => {
999                            // Can't use sum_axis without RemoveAxis trait
1000                            // Just return the full sum for now
1001                            let result = a_f32.as_array().sum();
1002                            return Ok(Box::new(result));
1003                        }
1004                        None => {
1005                            let result = a_f32.as_array().sum();
1006                            return Ok(Box::new(result));
1007                        }
1008                    }
1009                }
1010                Err(NotImplemented)
1011            }
1012            "scirs2::array_protocol::operations::reshape" => {
1013                // Reshape operation for NdarrayWrapper
1014                if let Some(shape) = kwargs
1015                    .get("shape")
1016                    .and_then(|s| s.downcast_ref::<Vec<usize>>())
1017                {
1018                    if TypeId::of::<T>() == TypeId::of::<f64>() {
1019                        let a_f64 =
1020                            unsafe { &*(self as *const _ as *const NdarrayWrapper<f64, D>) };
1021                        match a_f64
1022                            .as_array()
1023                            .clone()
1024                            .into_shape_with_order(shape.clone())
1025                        {
1026                            Ok(result) => {
1027                                return Ok(Box::new(
1028                                    Box::new(NdarrayWrapper::new(result)) as Box<dyn ArrayProtocol>
1029                                ))
1030                            }
1031                            Err(_) => return Err(NotImplemented),
1032                        }
1033                    } else if TypeId::of::<T>() == TypeId::of::<f32>() {
1034                        let a_f32 =
1035                            unsafe { &*(self as *const _ as *const NdarrayWrapper<f32, D>) };
1036                        match a_f32
1037                            .as_array()
1038                            .clone()
1039                            .into_shape_with_order(shape.clone())
1040                        {
1041                            Ok(result) => {
1042                                return Ok(Box::new(
1043                                    Box::new(NdarrayWrapper::new(result)) as Box<dyn ArrayProtocol>
1044                                ))
1045                            }
1046                            Err(_) => return Err(NotImplemented),
1047                        }
1048                    }
1049                }
1050                Err(NotImplemented)
1051            }
1052            _ => Err(NotImplemented),
1053        }
1054    }
1055
1056    fn as_any(&self) -> &dyn Any {
1057        self
1058    }
1059
1060    fn shape(&self) -> &[usize] {
1061        self.array.shape()
1062    }
1063
1064    fn dtype(&self) -> TypeId {
1065        TypeId::of::<T>()
1066    }
1067
1068    fn box_clone(&self) -> Box<dyn ArrayProtocol> {
1069        Box::new(self.clone())
1070    }
1071}
1072
1073// Example implementation for a third-party array library:
1074
1075/// A mock distributed array implementation.
1076#[derive(Debug, Clone)]
1077pub struct MockDistributedArray<T: Clone + 'static> {
1078    chunks: Vec<T>,
1079    shape: Vec<usize>,
1080}
1081
1082impl<T: Clone + Send + Sync + 'static> MockDistributedArray<T> {
1083    /// Create a new mock distributed array.
1084    #[must_use]
1085    pub fn new(chunks: Vec<T>, shape: Vec<usize>) -> Self {
1086        Self { chunks, shape }
1087    }
1088}
1089
1090impl<T: Clone + Send + Sync + 'static> ArrayProtocol for MockDistributedArray<T> {
1091    fn array_function(
1092        &self,
1093        func: &ArrayFunction,
1094        _types: &[TypeId],
1095        _args: &[Box<dyn Any>],
1096        _kwargs: &HashMap<String, Box<dyn Any>>,
1097    ) -> Result<Box<dyn Any>, NotImplemented> {
1098        match func.name {
1099            "scirs2::mean" => {
1100                // Example: Implement a mean function for distributed arrays
1101                // In a real implementation, this would use distributed computation
1102
1103                // For simplicity, we'll just return a dummy result
1104                let result = T::clone(&self.chunks[0]);
1105                Ok(Box::new(result))
1106            }
1107            _ => Err(NotImplemented),
1108        }
1109    }
1110
1111    fn as_any(&self) -> &dyn Any {
1112        self
1113    }
1114
1115    fn shape(&self) -> &[usize] {
1116        &self.shape
1117    }
1118
1119    fn box_clone(&self) -> Box<dyn ArrayProtocol> {
1120        Box::new(self.clone())
1121    }
1122}
1123
1124impl<T: Clone + Send + Sync + 'static> DistributedArray for MockDistributedArray<T> {
1125    fn distribution_info(&self) -> HashMap<String, String> {
1126        let mut info = HashMap::new();
1127        info.insert("type".to_string(), "mock_distributed".to_string());
1128        info.insert("chunks".to_string(), self.chunks.len().to_string());
1129        info
1130    }
1131
1132    fn gather(&self) -> CoreResult<Box<dyn ArrayProtocol>> {
1133        // In a real implementation, this would gather data from all nodes
1134        // For now, we just return self boxed as ArrayProtocol
1135        Ok(Box::new(self.clone()) as Box<dyn ArrayProtocol>)
1136    }
1137
1138    fn scatter(&self, _numchunks: usize) -> CoreResult<Box<dyn DistributedArray>> {
1139        // In a real implementation, this would scatter data to multiple nodes
1140        // For now, we just return self boxed as DistributedArray
1141        Ok(Box::new(self.clone()) as Box<dyn DistributedArray>)
1142    }
1143
1144    fn is_distributed(&self) -> bool {
1145        true
1146    }
1147}
1148
1149/// A mock GPU array implementation.
1150#[derive(Debug, Clone)]
1151pub struct MockGPUArray<T: Clone + 'static> {
1152    data: Vec<T>,
1153    shape: Vec<usize>,
1154    device: String,
1155}
1156
1157impl<T: Clone + Send + Sync + 'static> MockGPUArray<T> {
1158    /// Create a new mock GPU array.
1159    #[must_use]
1160    pub fn new(data: Vec<T>, shape: Vec<usize>, device: String) -> Self {
1161        Self {
1162            data,
1163            shape,
1164            device,
1165        }
1166    }
1167}
1168
1169impl<T: Clone + Send + Sync + 'static> ArrayProtocol for MockGPUArray<T> {
1170    fn array_function(
1171        &self,
1172        func: &ArrayFunction,
1173        _types: &[TypeId],
1174        _args: &[Box<dyn Any>],
1175        _kwargs: &HashMap<String, Box<dyn Any>>,
1176    ) -> Result<Box<dyn Any>, NotImplemented> {
1177        match func.name {
1178            "scirs2::matmul" => {
1179                // Example: Implement a GPU-accelerated matrix multiplication
1180                // In a real implementation, this would use GPU computation
1181
1182                // For simplicity, we'll just return a dummy result
1183                let result =
1184                    MockGPUArray::new(self.data.clone(), self.shape.clone(), self.device.clone());
1185                Ok(Box::new(result))
1186            }
1187            _ => Err(NotImplemented),
1188        }
1189    }
1190
1191    fn as_any(&self) -> &dyn Any {
1192        self
1193    }
1194
1195    fn shape(&self) -> &[usize] {
1196        &self.shape
1197    }
1198
1199    fn box_clone(&self) -> Box<dyn ArrayProtocol> {
1200        Box::new(self.clone())
1201    }
1202}
1203
1204impl<T: Clone + Send + Sync + 'static> GPUArray for MockGPUArray<T> {
1205    fn to_gpu(&self) -> CoreResult<Box<dyn GPUArray>> {
1206        // Already on GPU
1207        Ok(Box::new(self.clone()) as Box<dyn GPUArray>)
1208    }
1209
1210    fn to_cpu(&self) -> CoreResult<Box<dyn ArrayProtocol>> {
1211        // In a real implementation, this would transfer data from GPU to CPU
1212        // For now, we just return self boxed as ArrayProtocol
1213        Ok(Box::new(self.clone()) as Box<dyn ArrayProtocol>)
1214    }
1215
1216    fn is_on_gpu(&self) -> bool {
1217        true
1218    }
1219
1220    fn device_info(&self) -> HashMap<String, String> {
1221        let mut info = HashMap::new();
1222        info.insert("device".to_string(), self.device.clone());
1223        info.insert("type".to_string(), "mock_gpu".to_string());
1224        info
1225    }
1226}
1227
1228/// A factory for creating and registering array protocol enabled functions.
1229///
1230/// This provides a convenient way to create functions that can be overridden
1231/// by third-party array implementations.
1232#[derive(Debug)]
1233pub struct ArrayProtocolFunction<F> {
1234    func: F,
1235    name: &'static str,
1236}
1237
1238impl<F> ArrayProtocolFunction<F> {
1239    /// Create a new array protocol function.
1240    #[must_use]
1241    pub fn new(func: F, name: &'static str) -> Self {
1242        Self { func, name }
1243    }
1244}
1245
1246impl<F> ArrayProtocolFunction<F>
1247where
1248    F: Clone + Send + Sync + 'static,
1249{
1250    /// Register this function with the array protocol system.
1251    pub fn register(self) -> F {
1252        let implementation = Arc::new(
1253            move |_args: &[Box<dyn Any>], _kwargs: &HashMap<String, Box<dyn Any>>| {
1254                // This is a placeholder for actual implementation that would:
1255                // 1. Convert generic args to specific types needed by the function
1256                // 2. Call the function with the converted args
1257                // 3. Return the result as a Box<dyn Any>
1258                Err(CoreError::NotImplementedError(ErrorContext::new(
1259                    "ArrayProtocolFunction: Implementation for array protocol functions is not complete".to_string()
1260                )))
1261            },
1262        );
1263
1264        let array_func = ArrayFunction {
1265            name: self.name,
1266            implementation,
1267        };
1268
1269        // Register the function
1270        if let Ok(mut registry) = ArrayFunctionRegistry::global().write() {
1271            registry.register(array_func);
1272        } else {
1273            eprintln!("Warning: Failed to acquire write lock on ArrayFunctionRegistry during array protocol building, skipping function registration");
1274            // Continue without registration - this may result in reduced functionality but avoids crash
1275        }
1276
1277        self.func
1278    }
1279}
1280
1281/// Convenience macro for defining array protocol functions.
1282///
1283/// This macro creates a function and registers it with the array protocol system.
1284/// The function can then be overridden by array types that implement the ArrayProtocol trait.
1285///
1286/// Example usage:
1287/// ```ignore
1288/// use scirs2_core::array_protocol::{ArrayFunction, ArrayFunctionRegistry};
1289/// use std::sync::Arc;
1290/// use std::collections::HashMap;
1291/// use std::any::Any;
1292///
1293/// // Define and register a sum function
1294/// fn register_sum_function() {
1295///     let implementation = Arc::new(
1296///         move |args: &[Box<dyn Any>], kwargs: &HashMap<String, Box<dyn Any>>| {
1297///             if let Some(array) = args.get(0)
1298///                 .and_then(|arg| arg.downcast_ref::<crate::ndarray::Array<f64, crate::ndarray::Ix2>>()) {
1299///                 let sum = array.sum();
1300///                 Ok(Box::new(sum) as Box<dyn Any>)
1301///             } else {
1302///                 Err(scirs2_core::error::CoreError::InvalidArgument(
1303///                     scirs2_core::error::ErrorContext::new(
1304///                         "Expected Array2<f64> as first argument".to_string()
1305///                     )
1306///                 ))
1307///             }
1308///         }
1309///     );
1310///     
1311///     let func = ArrayFunction {
1312///         name: "scirs2::sum",
1313///         implementation,
1314///     };
1315///     
1316///     // Register the function
1317///     if let Ok(mut registry) = ArrayFunctionRegistry::global().write() {
1318///         registry.register(func);
1319///     }
1320/// }
1321/// ```
1322#[macro_export]
1323macro_rules! array_function_def {
1324    (fn $name:ident $(<$($gen:ident),*>)? ($($arg:ident : $arg_ty:ty),*) -> $ret:ty $body:block, $funcname:expr) => {
1325        {
1326            // Define the function
1327            fn $name $(<$($gen),*>)? ($($arg : $arg_ty),*) -> $ret $body
1328
1329            // Return the function so it can be used
1330            $name
1331        }
1332    };
1333}
1334
1335// Re-export distributed array implementation
1336pub use self::distributed_impl::{
1337    ArrayChunk, DistributedBackend, DistributedConfig, DistributedNdarray, DistributionStrategy,
1338};
1339
1340// Re-export GPU array implementation
1341pub use self::gpu_impl::{
1342    kernels as gpu_kernels, GPUArrayBuilder, GPUBackend, GPUConfig, GPUNdarray,
1343};
1344
1345// Re-export JIT compilation implementation
1346pub use self::jit_impl::{
1347    CraneliftFunctionFactory, JITBackend, JITConfig, JITEnabledArray, JITFunctionImpl, JITManager,
1348    LLVMFunctionFactory,
1349};
1350
1351// Re-export array operations
1352pub use self::operations::{
1353    add, apply_elementwise, concatenate, inverse, matmul, multiply, reshape, subtract, sum, svd,
1354    transpose, OperationError,
1355};
1356
1357// Re-export ml_ops
1358pub use self::ml_ops::{
1359    activation, batch_norm, conv2d, cross_entropy, dropout, max_pool2d, self_attention,
1360    ActivationFunc,
1361};
1362
1363/// Initializes the array protocol system.
1364///
1365/// This function initializes the array protocol system by registering the
1366/// default JIT function factories and other components. It should be called
1367/// before using any of the array protocol features.
1368#[allow(dead_code)]
1369pub fn init() {
1370    // Initialize the JIT manager
1371    let mut jit_manager = JITManager::global().write().expect("Operation failed");
1372    jit_manager.initialize();
1373}
1374
1375/// Extra traits for third-party array implementations.
1376pub mod traits {
1377    use super::*;
1378
1379    /// Trait for arrays that support strided access.
1380    pub trait StridedArray: ArrayProtocol {
1381        /// Get the strides of this array.
1382        #[must_use]
1383        fn strides(&self) -> Vec<usize>;
1384
1385        /// Check if this array is contiguous.
1386        #[must_use]
1387        fn is_contiguous(&self) -> bool;
1388
1389        /// Check if this array is Fortran-contiguous (column-major).
1390        #[must_use]
1391        fn is_fortran_contiguous(&self) -> bool;
1392    }
1393
1394    /// Trait for arrays that support zero-copy operations.
1395    pub trait ZeroCopyArray: ArrayProtocol {
1396        /// Create a view of this array.
1397        #[must_use]
1398        fn view(&self) -> Box<dyn ZeroCopyArray>;
1399
1400        /// Create a mutable view of this array.
1401        #[must_use]
1402        fn view_mut(&mut self) -> Box<dyn ZeroCopyArray>;
1403
1404        /// Check if this array is a view.
1405        #[must_use]
1406        fn is_view(&self) -> bool;
1407    }
1408
1409    /// Trait for arrays that support automatic differentiation.
1410    pub trait DifferentiableArray: ArrayProtocol {
1411        /// Compute the gradient of this array with respect to some variables.
1412        fn gradient(
1413            &self,
1414            variables: &[Box<dyn DifferentiableArray>],
1415        ) -> Vec<Box<dyn DifferentiableArray>>;
1416
1417        /// Set whether to record operations for automatic differentiation.
1418        fn set_requiresgrad(&mut self, requiresgrad: bool);
1419
1420        /// Check if this array requires gradient computation.
1421        #[must_use]
1422        fn requiresgrad(&self) -> bool;
1423
1424        /// Get the gradient of this array.
1425        #[must_use]
1426        fn grad(&self) -> Option<Box<dyn DifferentiableArray>>;
1427    }
1428
1429    /// Trait for arrays that support asynchronous operations.
1430    pub trait AsyncArray: ArrayProtocol {
1431        /// Perform an asynchronous operation on this array.
1432        fn async_op<F, R>(&self, op: F) -> impl std::future::Future<Output = CoreResult<R>>
1433        where
1434            F: FnOnce(&Self) -> CoreResult<R> + Send + 'static,
1435            R: Send + 'static;
1436
1437        /// Check if this array supports asynchronous operations.
1438        #[must_use]
1439        fn supports_async(&self) -> bool;
1440    }
1441}
1442
1443#[cfg(test)]
1444mod tests {
1445    use super::*;
1446
1447    #[test]
1448    fn test_array_protocol_registry() {
1449        // Create a function and register it
1450        let implementation = Arc::new(
1451            move |_args: &[Box<dyn Any>], _kwargs: &HashMap<String, Box<dyn Any>>| {
1452                Ok(Box::new(42.0) as Box<dyn Any>)
1453            },
1454        );
1455
1456        let func = ArrayFunction {
1457            name: "scirs2::test::test_func",
1458            implementation,
1459        };
1460
1461        let registry = ArrayFunctionRegistry::global();
1462        {
1463            let mut reg = registry.write().expect("Operation failed");
1464            reg.register(func.clone());
1465        }
1466
1467        // Verify the function was registered
1468        {
1469            let reg = registry.read().expect("Operation failed");
1470            let registered_func = reg
1471                .get("scirs2::test::test_func")
1472                .expect("Operation failed");
1473            assert_eq!(registered_func.name, "scirs2::test::test_func");
1474        }
1475    }
1476
1477    #[test]
1478    fn test_mock_distributed_array() {
1479        let array = MockDistributedArray::new(vec![1.0, 2.0, 3.0], vec![3]);
1480        assert!(array.is_distributed());
1481
1482        let info = array.distribution_info();
1483        assert_eq!(
1484            info.get("type").expect("Operation failed"),
1485            "mock_distributed"
1486        );
1487        assert_eq!(info.get("chunks").expect("Operation failed"), "3");
1488    }
1489
1490    #[test]
1491    fn test_mock_gpu_array() {
1492        let array = MockGPUArray::new(vec![1.0, 2.0, 3.0], vec![3], "cuda:0".to_string());
1493        assert!(array.is_on_gpu());
1494
1495        let info = array.device_info();
1496        assert_eq!(info.get("device").expect("Operation failed"), "cuda:0");
1497        assert_eq!(info.get("type").expect("Operation failed"), "mock_gpu");
1498    }
1499
1500    #[test]
1501    fn test_box_clone() {
1502        // Test Box<dyn ArrayProtocol> cloning for NdarrayWrapper
1503        let array = crate::ndarray::Array2::<f64>::ones((3, 3));
1504        let wrapped = NdarrayWrapper::new(array);
1505        let boxed: Box<dyn ArrayProtocol> = Box::new(wrapped);
1506        let cloned = boxed.clone();
1507
1508        // Verify the clone is correct
1509        assert_eq!(cloned.shape(), &[3, 3]);
1510
1511        // Test Box<dyn ArrayProtocol> cloning for MockDistributedArray
1512        let array = MockDistributedArray::new(vec![1.0, 2.0, 3.0], vec![3]);
1513        let boxed: Box<dyn ArrayProtocol> = Box::new(array);
1514        let cloned = boxed.clone();
1515
1516        // Verify the clone is correct
1517        assert_eq!(cloned.shape(), &[3]);
1518    }
1519}
1520
1521/// Examples of using the array protocol.
1522#[cfg(test)]
1523mod examples {
1524    use super::*;
1525    use ::ndarray::Array2;
1526    use std::any::Any;
1527    use std::collections::HashMap;
1528
1529    /// Example: Create and use a distributed array.
1530    #[test]
1531    fn example_distributed_array() {
1532        // Non-constant data: an all-ones array wouldn't distinguish real
1533        // chunk reconstruction from a stub that fabricates a plausible
1534        // all-ones placeholder (as `to_array` used to, before it was fixed
1535        // to actually reassemble the chunks — see distributed_impl.rs).
1536        let array = Array2::from_shape_fn((10, 5), |(i, j)| (i * 5 + j) as f64);
1537
1538        // Create a distributed array configuration
1539        let config = DistributedConfig {
1540            chunks: 3,
1541            balance: true,
1542            strategy: DistributionStrategy::RowWise,
1543            backend: DistributedBackend::Threaded,
1544        };
1545
1546        // Create a distributed array
1547        let dist_array = DistributedNdarray::from_array(&array, config);
1548
1549        // Check that the array was split correctly
1550        assert_eq!(dist_array.num_chunks(), 3);
1551        assert_eq!(dist_array.shape(), &[10, 5]);
1552
1553        // Convert back to a regular array
1554        let result = dist_array.to_array().expect("Operation failed");
1555
1556        // Check that the result matches the original array. `to_array()`
1557        // returns `Array<f64, IxDyn>` while `array` is `Array2<f64>`, so a
1558        // direct `assert_eq!` doesn't type-check — compare via `into_dyn()`.
1559        assert_eq!(result.shape(), array.shape());
1560        assert_eq!(result, array.into_dyn());
1561    }
1562
1563    /// Example: Create and use a GPU array.
1564    #[test]
1565    fn example_gpu_array() {
1566        // Create a regular array
1567        let array = Array2::<f64>::ones((10, 5));
1568
1569        // Create a GPU array configuration
1570        let config = GPUConfig {
1571            backend: GPUBackend::CUDA,
1572            device_id: 0,
1573            async_ops: true,
1574            mixed_precision: false,
1575            memory_fraction: 0.9,
1576        };
1577
1578        // Create a GPU array
1579        let gpu_array = GPUNdarray::new(array.clone(), config);
1580
1581        // Check that the array was created correctly
1582        assert_eq!(gpu_array.shape(), &[10, 5]);
1583        assert!(gpu_array.is_on_gpu());
1584
1585        // Get device information
1586        let info = gpu_array.device_info();
1587        assert_eq!(info.get("backend").expect("Operation failed"), "CUDA");
1588
1589        // Test box_clone for GPU array
1590        let gpu_box: Box<dyn ArrayProtocol> = Box::new(gpu_array);
1591        let gpu_clone = gpu_box.clone();
1592
1593        // Check the cloned GPU array
1594        assert_eq!(gpu_clone.shape(), &[10, 5]);
1595    }
1596
1597    /// Example: Create and use a JIT-enabled array.
1598    #[test]
1599    fn example_jit_array() {
1600        // Initialize the JIT manager
1601        init();
1602
1603        // Create a regular array
1604        let array = Array2::<f64>::ones((10, 5));
1605        let wrapped = NdarrayWrapper::new(array);
1606
1607        // Create a JIT-enabled array
1608        let jitarray: JITEnabledArray<f64, NdarrayWrapper<f64, crate::ndarray::Ix2>> =
1609            JITEnabledArray::new(wrapped);
1610
1611        // Check if JIT is supported
1612        assert!(jitarray.supports_jit());
1613
1614        // Compile a function
1615        let expression = "x + y";
1616        let jit_function = jitarray.compile(expression).expect("Operation failed");
1617
1618        // Check the function's properties
1619        assert_eq!(jit_function.source(), expression);
1620
1621        // Get JIT information
1622        let info = jitarray.jit_info();
1623        assert_eq!(info.get("supports_jit").expect("Operation failed"), "true");
1624
1625        // Test box_clone for JIT-enabled array
1626        let jit_box: Box<dyn ArrayProtocol> = Box::new(jitarray);
1627        let jit_clone = jit_box.clone();
1628
1629        // Check the cloned JIT array
1630        assert_eq!(jit_clone.shape(), &[10, 5]);
1631    }
1632
1633    /// Example: Test cloning Box<dyn ArrayProtocol>
1634    #[test]
1635    fn example_cloning_array_protocol_objects() {
1636        // Create a GPU array with box_clone support
1637        let array = Array2::<f64>::ones((10, 5));
1638        let config = GPUConfig::default();
1639        let gpu_array = GPUNdarray::new(array.clone(), config);
1640
1641        // Box the array as ArrayProtocol and clone it
1642        let boxed: Box<dyn ArrayProtocol> = Box::new(gpu_array);
1643        let cloned = boxed.clone();
1644
1645        // Verify the clone works correctly
1646        assert_eq!(cloned.shape(), &[10, 5]);
1647
1648        // Create a distributed array and test box_clone
1649        let config = DistributedConfig {
1650            chunks: 3,
1651            balance: true,
1652            strategy: DistributionStrategy::RowWise,
1653            backend: DistributedBackend::Threaded,
1654        };
1655        let dist_array = DistributedNdarray::from_array(&array, config);
1656
1657        // Box the array as ArrayProtocol and clone it
1658        let boxed: Box<dyn ArrayProtocol> = Box::new(dist_array);
1659        let cloned = boxed.clone();
1660
1661        // Verify the clone works correctly
1662        assert_eq!(cloned.shape(), &[10, 5]);
1663    }
1664
1665    /*
1666    // Commented out examples using macros - we'll fix these later
1667
1668    /// Example: Define an array function using the macro.
1669    /// Example: Register and use an array function.
1670    #[test]
1671    fn example_array_function() {
1672        // Create a simple array function (without using macros)
1673        let funcname = "scirs2::example::sum";
1674
1675        // Create an ArrayFunction manually
1676        let implementation = Arc::new(move |args: &[Box<dyn Any>], kwargs: &HashMap<String, Box<dyn Any>>| {
1677            if let Some(array) = args.get(0)
1678                .and_then(|arg| arg.downcast_ref::<Array2<f64>>()) {
1679                let sum = array.sum();
1680                Ok(Box::new(sum))
1681            } else {
1682                Err(CoreError::InvalidArgument(ErrorContext::new(
1683                    "Expected Array2<f64> as first argument".to_string()
1684                )))
1685            }
1686        });
1687
1688        let func = ArrayFunction {
1689            name: funcname,
1690            implementation,
1691        };
1692
1693        // Register the function
1694        let registry = ArrayFunctionRegistry::global();
1695        {
1696            let mut reg = registry.write().expect("Operation failed");
1697            reg.register(func.clone());
1698        }
1699
1700        // Verify the function was registered
1701        {
1702            let reg = registry.read().expect("Operation failed");
1703            let registered_func = reg.get(funcname).expect("Operation failed");
1704            assert_eq!(registered_func.name, funcname);
1705        }
1706    }
1707    */
1708
1709    /// Example: Interoperability between different array types
1710    #[test]
1711    fn example_array_interoperability() {
1712        // Initialize the system
1713        init();
1714
1715        // Create arrays of different types
1716        let cpu_array = Array2::<f64>::ones((5, 5));
1717
1718        // Create a GPU array
1719        let gpu_config = GPUConfig {
1720            backend: GPUBackend::CUDA,
1721            device_id: 0,
1722            async_ops: false,
1723            mixed_precision: false,
1724            memory_fraction: 0.9,
1725        };
1726        let gpu_array = GPUNdarray::new(cpu_array.clone(), gpu_config);
1727
1728        // Create a distributed array
1729        let dist_config = DistributedConfig {
1730            chunks: 2,
1731            balance: true,
1732            strategy: DistributionStrategy::RowWise,
1733            backend: DistributedBackend::Threaded,
1734        };
1735        let dist_array = DistributedNdarray::from_array(&cpu_array, dist_config);
1736
1737        // Simple test of interoperability: convert both to Box<dyn ArrayProtocol>
1738        let gpu_wrapper: Box<dyn ArrayProtocol> = Box::new(gpu_array);
1739        let dist_wrapper: Box<dyn ArrayProtocol> = Box::new(dist_array);
1740
1741        // Verify the clones work correctly
1742        let gpu_clone = gpu_wrapper.clone();
1743        let dist_clone = dist_wrapper.clone();
1744
1745        assert_eq!(gpu_clone.shape(), &[5, 5]);
1746        assert_eq!(dist_clone.shape(), &[5, 5]);
1747    }
1748
1749    /// Example: Advanced usage with custom array type
1750    #[test]
1751    fn example_custom_array_type() {
1752        use std::sync::Arc;
1753
1754        // Define a custom array type
1755        struct MyCustomArray<T> {
1756            data: Vec<T>,
1757            shape: Vec<usize>,
1758        }
1759
1760        impl<T: Clone + 'static> MyCustomArray<T> {
1761            fn new(data: Vec<T>, shape: Vec<usize>) -> Self {
1762                Self { data, shape }
1763            }
1764
1765            // Commented out since it's unused but may be needed in the future
1766            // fn shape(&self) -> &[usize] {
1767            //     &self.shape
1768            // }
1769        }
1770
1771        // Implement ArrayProtocol for the custom array type
1772        impl<T: Clone + Send + Sync + 'static> ArrayProtocol for MyCustomArray<T> {
1773            fn array_function(
1774                &self,
1775                func: &ArrayFunction,
1776                _types: &[TypeId],
1777                _args: &[Box<dyn Any>],
1778                _kwargs: &HashMap<String, Box<dyn Any>>,
1779            ) -> Result<Box<dyn Any>, NotImplemented> {
1780                if func.name == "scirs2::example::custom_sum" {
1781                    // Implement custom sum for our array type
1782                    match std::any::TypeId::of::<T>() {
1783                        tid if tid == std::any::TypeId::of::<f64>() => {
1784                            // For f64 arrays, cast to f64 slice
1785                            let f64_data = unsafe {
1786                                std::slice::from_raw_parts(
1787                                    self.data.as_ptr() as *const f64,
1788                                    self.data.len(),
1789                                )
1790                            };
1791                            let sum = f64_data.iter().sum::<f64>();
1792                            Ok(Box::new(sum))
1793                        }
1794                        tid if tid == std::any::TypeId::of::<f32>() => {
1795                            // For f32 arrays, cast to f32 slice
1796                            let f32_data = unsafe {
1797                                std::slice::from_raw_parts(
1798                                    self.data.as_ptr() as *const f32,
1799                                    self.data.len(),
1800                                )
1801                            };
1802                            let sum = f32_data.iter().sum::<f32>();
1803                            Ok(Box::new(sum))
1804                        }
1805                        _ => Err(NotImplemented),
1806                    }
1807                } else {
1808                    Err(NotImplemented)
1809                }
1810            }
1811
1812            fn as_any(&self) -> &dyn Any {
1813                self
1814            }
1815
1816            fn shape(&self) -> &[usize] {
1817                &self.shape
1818            }
1819
1820            fn box_clone(&self) -> Box<dyn ArrayProtocol> {
1821                Box::new(MyCustomArray {
1822                    data: self.data.clone(),
1823                    shape: self.shape.clone(),
1824                })
1825            }
1826        }
1827
1828        // Create an instance of the custom array type
1829        let custom_array = MyCustomArray::new(vec![1.0, 2.0, 3.0, 4.0], vec![2, 2]);
1830
1831        // Test box_clone functionality
1832        let boxed: Box<dyn ArrayProtocol> = Box::new(custom_array);
1833        let cloned = boxed.clone();
1834
1835        // Verify the clone has the correct shape
1836        assert_eq!(cloned.shape(), &[2, 2]);
1837
1838        // Create an ArrayFunction for testing
1839        let func = ArrayFunction {
1840            name: "scirs2::example::custom_sum",
1841            implementation: Arc::new(move |_args, _kwargs| {
1842                // Dummy implementation
1843                Ok(Box::new(42.0) as Box<dyn Any>)
1844            }),
1845        };
1846
1847        // Test array_function directly
1848        let result = cloned.array_function(
1849            &func,
1850            &[std::any::TypeId::of::<f64>()],
1851            &[],
1852            &HashMap::new(),
1853        );
1854
1855        // Verify we get a result (the sum of 1+2+3+4 = 10)
1856        assert!(result.is_ok());
1857        if let Ok(value) = result {
1858            let sum = *value.downcast_ref::<f64>().expect("Operation failed");
1859            assert_eq!(sum, 10.0);
1860        }
1861    }
1862}
1863/// Make `Box<dyn JITFunction>` cloneable via clone_box
1864impl Clone for Box<dyn JITFunction> {
1865    fn clone(&self) -> Self {
1866        self.clone_box()
1867    }
1868}