1use ::ndarray::{
7 Array, ArrayBase, ArrayView, ArrayViewMut, Data, DataMut, Dimension, Ix1, IxDyn, RawData,
8};
9use once_cell::sync::Lazy;
10use std::collections::HashMap;
11use std::sync::RwLock;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum UFuncKind {
16 Unary,
18 Binary,
20 Reduction,
22}
23
24pub trait UFunc: Send + Sync {
26 fn name(&self) -> &str;
28
29 fn kind(&self) -> UFuncKind;
31
32 fn apply(
34 &self,
35 inputs: &[ArrayView<f64, IxDyn>],
36 output: &mut ArrayViewMut<f64, IxDyn>,
37 ) -> Result<(), &'static str>;
38
39 fn use_simd(&self) -> bool {
41 #[cfg(feature = "simd")]
42 return true;
43
44 #[cfg(not(feature = "simd"))]
45 return false;
46 }
47
48 fn use_parallel(&self) -> bool {
50 #[cfg(feature = "parallel")]
51 return true;
52
53 #[cfg(not(feature = "parallel"))]
54 return false;
55 }
56}
57
58static UFUNC_REGISTRY: Lazy<RwLock<HashMap<String, Box<dyn UFunc>>>> =
60 Lazy::new(|| RwLock::new(HashMap::new()));
61
62#[allow(dead_code)]
64pub fn register_ufunc(ufunc: Box<dyn UFunc>) -> Result<(), &'static str> {
65 let name = ufunc.name().to_string();
66
67 let mut registry = UFUNC_REGISTRY.write().expect("Operation failed");
68
69 if registry.contains_key(&name) {
70 return Err("UFunc with this name already exists");
71 }
72
73 registry.insert(name, ufunc);
74 Ok(())
75}
76
77#[allow(dead_code)]
79pub fn get_ufunc(name: &str) -> Option<Box<dyn UFunc>> {
80 let registry = UFUNC_REGISTRY.read().expect("Operation failed");
81
82 registry.get(name).map(|ufunc| {
83 let ufunc_clone: Box<dyn UFunc> = Box::new(UFuncWrapper {
85 name: ufunc.name().to_string(),
86 kind: ufunc.kind(),
87 });
88
89 ufunc_clone
90 })
91}
92
93struct UFuncWrapper {
95 name: String,
96 kind: UFuncKind,
97}
98
99impl UFunc for UFuncWrapper {
100 fn name(&self) -> &str {
101 &self.name
102 }
103
104 fn kind(&self) -> UFuncKind {
105 self.kind
106 }
107
108 fn apply(
109 &self,
110 inputs: &[ArrayView<f64, IxDyn>],
111 output: &mut ArrayViewMut<f64, IxDyn>,
112 ) -> Result<(), &'static str> {
113 let registry = UFUNC_REGISTRY.read().expect("Operation failed");
116
117 if let Some(real_ufunc) = registry.get(&self.name) {
118 real_ufunc.apply(inputs, output)
119 } else {
120 Err("UFunc not found in registry")
121 }
122 }
123}
124
125#[allow(dead_code)]
127pub fn apply_unary<T, F, O, S1, S2, D>(
128 input: &ArrayBase<S1, D>,
129 output: &mut ArrayBase<S2, D>,
130 op: F,
131) -> Result<(), &'static str>
132where
133 S1: Data<Elem = T>,
134 S2: Data<Elem = O> + DataMut,
135 T: Clone + Send + Sync,
136 O: Clone + Send + Sync,
137 F: Fn(&T) -> O + Send + Sync,
138 D: Dimension,
139{
140 if input.shape() != output.shape() {
142 return Err("Output shape must match input shape for unary operations");
143 }
144
145 #[cfg(feature = "parallel")]
147 {
148 use crate::parallel_ops::*;
149 let input_slice = input.as_slice().expect("Operation failed");
152 let output_slice = output.as_slice_mut().expect("Operation failed");
153
154 output_slice
155 .par_iter_mut()
156 .enumerate()
157 .for_each(|(i, out)| {
158 let in_val = unsafe { input_slice.get_unchecked(i) };
159 *out = op(in_val);
160 });
161 }
162
163 #[cfg(not(feature = "parallel"))]
164 {
165 output.iter_mut().zip(input.iter()).for_each(|(out, inp)| {
166 *out = op(inp);
167 });
168 }
169
170 Ok(())
171}
172
173#[allow(dead_code)]
175pub fn apply_binary<T, F, O, S1, S2, S3, D>(
176 input1: &ArrayBase<S1, D>,
177 input2: &ArrayBase<S2, D>,
178 output: &mut ArrayBase<S3, D>,
179 op: F,
180) -> Result<(), &'static str>
181where
182 S1: Data<Elem = T>,
183 S2: Data<Elem = T>,
184 S3: Data<Elem = O> + DataMut,
185 T: Clone + Send + Sync,
186 O: Clone + Send + Sync,
187 F: Fn(&T, &T) -> O + Send + Sync,
188 D: Dimension,
189{
190 if input1.shape() != output.shape() || input2.shape() != output.shape() {
195 return Err("All arrays must have the same shape for binary operations");
196 }
197
198 #[cfg(feature = "parallel")]
200 {
201 use crate::parallel_ops::*;
202
203 let input1_slice = input1.as_slice().expect("Operation failed");
204 let input2_slice = input2.as_slice().expect("Operation failed");
205 let output_slice = output.as_slice_mut().expect("Operation failed");
206
207 output_slice
208 .par_iter_mut()
209 .enumerate()
210 .for_each(|(i, out)| {
211 let in1 = unsafe { input1_slice.get_unchecked(i) };
212 let in2 = unsafe { input2_slice.get_unchecked(i) };
213 *out = op(in1, in2);
214 });
215 }
216
217 #[cfg(not(feature = "parallel"))]
218 {
219 output
220 .iter_mut()
221 .zip(input1.iter().zip(input2.iter()))
222 .for_each(|(out, (in1, in2))| {
223 *out = op(in1, in2);
224 });
225 }
226
227 Ok(())
228}
229
230#[allow(dead_code)]
232pub fn apply_reduction<T, F, S1, S2, D>(
233 input: &ArrayBase<S1, D>,
234 output: &mut ArrayBase<S2, Ix1>,
235 axis: Option<usize>,
236 initial: Option<T>,
237 op: F,
238) -> Result<(), &'static str>
239where
240 S1: Data<Elem = T>,
241 S2: Data<Elem = T> + DataMut,
242 T: Clone + Send + Sync,
243 F: Fn(T, &T) -> T + Send + Sync,
244 D: Dimension,
245{
246 match axis {
250 Some(ax) => {
251 if ax >= input.ndim() {
253 return Err("Axis index out of bounds");
254 }
255
256 let axis_size = input.len_of(crate::ndarray::Axis(ax));
257 let othershape = input
258 .shape()
259 .iter()
260 .enumerate()
261 .filter_map(|(i, &s)| if i != ax { Some(s) } else { None })
262 .collect::<Vec<_>>();
263
264 if output.shape() != othershape.as_slice() {
266 return Err("Output shape does not match the expected shape for reduction");
267 }
268
269 if input.ndim() != 2 {
272 return Err("This simplified implementation only supports 2D arrays");
273 }
274
275 let (rows, cols) = (input.shape()[0], input.shape()[1]);
276
277 if let Some(input_slice) = input.as_slice() {
279 if ax == 0 {
280 for j in 0..cols {
282 let mut acc = initial.clone().unwrap_or_else(|| {
283 input_slice[j].clone()
285 });
286 let start_i = if initial.is_some() { 0 } else { 1 };
287 for i in start_i..rows {
288 let val = &input_slice[i * cols + j];
290 acc = op(acc, val);
291 }
292 output[j] = acc;
293 }
294 } else {
295 for i in 0..rows {
297 let mut acc = initial.clone().unwrap_or_else(|| {
298 input_slice[i * cols].clone()
300 });
301 let start_j = if initial.is_some() { 0 } else { 1 };
302 for j in start_j..cols {
303 let val = &input_slice[i * cols + j];
305 acc = op(acc, val);
306 }
307 output[i] = acc;
308 }
309 }
310 } else {
311 return Err("Input array is not contiguous");
312 }
313 }
314 None => {
315 if output.len() != 1 {
317 return Err("Output array must have length 1 for full reduction");
318 }
319
320 let mut iter = input.iter();
321 let mut acc = initial
322 .clone()
323 .unwrap_or_else(|| iter.next().expect("Operation failed").clone());
324
325 for val in iter {
326 acc = op(acc, val);
327 }
328
329 output[0] = acc;
330 }
331 }
332
333 Ok(())
334}
335
336#[cfg(test)]
337mod tests {
338 use super::*;
339 use ndarray::{array, Array1, Array2};
340
341 struct TestUnaryUFunc;
343
344 impl UFunc for TestUnaryUFunc {
345 fn name(&self) -> &str {
346 "test_unary"
347 }
348
349 fn kind(&self) -> UFuncKind {
350 UFuncKind::Unary
351 }
352
353 fn apply(
354 &self,
355 inputs: &[ArrayView<f64, IxDyn>],
356 output: &mut ArrayViewMut<f64, IxDyn>,
357 ) -> Result<(), &'static str> {
358 if inputs.len() != 1 {
359 return Err("Unary ufunc requires exactly one input array");
360 }
361
362 let input = &inputs[0];
364 for (inp, out) in input.iter().zip(output.iter_mut()) {
365 *out = inp * inp;
366 }
367 Ok(())
368 }
369 }
370
371 struct TestBinaryUFunc;
373
374 impl UFunc for TestBinaryUFunc {
375 fn name(&self) -> &str {
376 "test_binary"
377 }
378
379 fn kind(&self) -> UFuncKind {
380 UFuncKind::Binary
381 }
382
383 fn apply(
384 &self,
385 inputs: &[ArrayView<f64, IxDyn>],
386 output: &mut ArrayViewMut<f64, IxDyn>,
387 ) -> Result<(), &'static str> {
388 if inputs.len() != 2 {
389 return Err("Binary ufunc requires exactly two input arrays");
390 }
391
392 let input1 = &inputs[0];
394 let input2 = &inputs[1];
395 for ((a, b), out) in input1.iter().zip(input2.iter()).zip(output.iter_mut()) {
396 *out = a + b;
397 }
398 Ok(())
399 }
400 }
401
402 #[test]
403 fn test_ufunc_registry() {
404 let ufunc = Box::new(TestUnaryUFunc);
406 register_ufunc(ufunc).expect("Operation failed");
407
408 let ufunc = get_ufunc("test_unary").expect("Operation failed");
410 assert_eq!(ufunc.name(), "test_unary");
411 assert_eq!(ufunc.kind(), UFuncKind::Unary);
412 }
413
414 #[test]
415 fn test_apply_unary() {
416 let input = array![1.0, 2.0, 3.0, 4.0];
417 let mut output = Array1::<f64>::zeros(4);
418
419 apply_unary(&input, &mut output, |&x: &f64| x * x).expect("Operation failed");
420
421 assert_eq!(output, array![1.0, 4.0, 9.0, 16.0]);
422 }
423
424 #[test]
425 fn test_apply_binary() {
426 let input1 = array![1.0, 2.0, 3.0, 4.0];
427 let input2 = array![5.0, 6.0, 7.0, 8.0];
428 let mut output = Array1::<f64>::zeros(4);
429
430 apply_binary(&input1, &input2, &mut output, |&x: &f64, &y: &f64| x + y)
431 .expect("Operation failed");
432
433 assert_eq!(output, array![6.0, 8.0, 10.0, 12.0]);
434 }
435
436 #[test]
437 fn test_apply_reduction() {
438 let input = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]];
439
440 let mut output = Array1::<f64>::zeros(3);
442 apply_reduction(&input, &mut output, Some(0), Some(0.0), |acc, &x| acc + x)
443 .expect("Operation failed");
444 assert_eq!(output, array![5.0, 7.0, 9.0]);
445
446 let mut output = Array1::<f64>::zeros(2);
448 apply_reduction(&input, &mut output, Some(1), Some(0.0), |acc, &x| acc + x)
449 .expect("Operation failed");
450 assert_eq!(output, array![6.0, 15.0]);
451
452 let mut output = Array1::<f64>::zeros(1);
454 apply_reduction(&input, &mut output, None, Some(0.0), |acc, &x| acc + x)
455 .expect("Operation failed");
456 assert_eq!(output, array![21.0]);
457 }
458}