scirs2_core/ndarray_ext/
manipulation.rs

1//! Array manipulation operations similar to `NumPy`'s array manipulation routines
2//!
3//! This module provides functions for manipulating arrays, including flip, roll,
4//! tile, repeat, and other operations, designed to mirror `NumPy`'s functionality.
5
6use crate::ndarray_ext::reduction;
7use crate::numeric::Float;
8use crate::simd_ops::SimdUnifiedOps;
9use ::ndarray::{Array, ArrayView, Ix1, Ix2};
10use num_traits::Zero;
11
12/// Result type for gradient function
13pub type GradientResult<T> = Result<(Array<T, Ix2>, Array<T, Ix2>), &'static str>;
14
15/// Flip a 2D array along one or more axes
16///
17/// # Arguments
18///
19/// * `array` - The input 2D array
20/// * `flip_axis_0` - Whether to flip along axis 0 (rows)
21/// * `flip_axis_0` - Whether to flip along axis 1 (columns)
22///
23/// # Returns
24///
25/// A new array with axes flipped as specified
26///
27/// # Examples
28///
29/// ```
30/// use ::ndarray::array;
31/// use scirs2_core::ndarray_ext::manipulation::flip_2d;
32///
33/// let a = array![[1, 2, 3], [4, 5, 6]];
34///
35/// // Flip along rows
36/// let flipped_rows = flip_2d(a.view(), true, false);
37/// assert_eq!(flipped_rows, array![[4, 5, 6], [1, 2, 3]]);
38///
39/// // Flip along columns
40/// let flipped_cols = flip_2d(a.view(), false, true);
41/// assert_eq!(flipped_cols, array![[3, 2, 1], [6, 5, 4]]);
42///
43/// // Flip along both axes
44/// let flipped_both = flip_2d(a.view(), true, true);
45/// assert_eq!(flipped_both, array![[6, 5, 4], [3, 2, 1]]);
46/// ```
47#[allow(dead_code)]
48pub fn flip_2d<T>(array: ArrayView<T, Ix2>, flip_axis_0: bool, flipaxis_1: bool) -> Array<T, Ix2>
49where
50    T: Clone + Zero,
51{
52    let (rows, cols) = (array.shape()[0], array.shape()[1]);
53    let mut result = Array::<T, Ix2>::zeros((rows, cols));
54
55    for i in 0..rows {
56        for j in 0..cols {
57            let src_i = if flip_axis_0 { rows - 1 - i } else { i };
58            let src_j = if flipaxis_1 { cols - 1 - j } else { j };
59
60            result[[i, j]] = array[[src_i, src_j]].clone();
61        }
62    }
63
64    result
65}
66
67/// Roll array elements along one or both axes
68///
69/// # Arguments
70///
71/// * `array` - The input 2D array
72/// * `shift_axis_0` - Number of places to shift along axis 0 (can be negative)
73/// * `shift_axis_1` - Number of places to shift along axis 1 (can be negative)
74///
75/// # Returns
76///
77/// A new array with elements rolled as specified
78///
79/// # Examples
80///
81/// ```
82/// use ::ndarray::array;
83/// use scirs2_core::ndarray_ext::manipulation::roll_2d;
84///
85/// let a = array![[1, 2, 3], [4, 5, 6]];
86///
87/// // Roll along rows by 1
88/// let rolled_rows = roll_2d(a.view(), 1, 0);
89/// assert_eq!(rolled_rows, array![[4, 5, 6], [1, 2, 3]]);
90///
91/// // Roll along columns by -1
92/// let rolled_cols = roll_2d(a.view(), 0, -1);
93/// assert_eq!(rolled_cols, array![[2, 3, 1], [5, 6, 4]]);
94/// ```
95#[allow(dead_code)]
96pub fn roll_2d<T>(
97    array: ArrayView<T, Ix2>,
98    shift_axis_0: isize,
99    shift_axis_1: isize,
100) -> Array<T, Ix2>
101where
102    T: Clone + Zero,
103{
104    let (rows, cols) = (array.shape()[0], array.shape()[1]);
105
106    // Handle case where no shifting is needed
107    if shift_axis_0 == 0 && shift_axis_1 == 0 {
108        return array.to_owned();
109    }
110
111    // Calculate effective shifts (handle negative shifts and wrap around)
112    let effective_shift_0 = if rows == 0 {
113        0
114    } else {
115        ((shift_axis_0 % rows as isize) + rows as isize) % rows as isize
116    };
117    let effective_shift_1 = if cols == 0 {
118        0
119    } else {
120        ((shift_axis_1 % cols as isize) + cols as isize) % cols as isize
121    };
122
123    let mut result = Array::<T, Ix2>::zeros((rows, cols));
124
125    for i in 0..rows {
126        for j in 0..cols {
127            // Calculate source indices with wrapping
128            let src_i = (i as isize + rows as isize - effective_shift_0) % rows as isize;
129            let src_j = (j as isize + cols as isize - effective_shift_1) % cols as isize;
130
131            result[[i, j]] = array[[src_i as usize, src_j as usize]].clone();
132        }
133    }
134
135    result
136}
137
138/// Repeat an array by tiling it in multiple dimensions
139///
140/// # Arguments
141///
142/// * `array` - The input 2D array
143/// * `reps_axis_0` - Number of times to repeat the array along axis 0
144/// * `reps_axis_1` - Number of times to repeat the array along axis 1
145///
146/// # Returns
147///
148/// A new array formed by repeating the input array
149///
150/// # Examples
151///
152/// ```
153/// use ::ndarray::array;
154/// use scirs2_core::ndarray_ext::manipulation::tile_2d;
155///
156/// let a = array![[1, 2], [3, 4]];
157///
158/// // Tile array to repeat it 2 times along axis 0 and 3 times along axis 1
159/// let tiled = tile_2d(a.view(), 2, 3);
160/// assert_eq!(tiled.shape(), &[4, 6]);
161/// assert_eq!(tiled,
162///     array![
163///         [1, 2, 1, 2, 1, 2],
164///         [3, 4, 3, 4, 3, 4],
165///         [1, 2, 1, 2, 1, 2],
166///         [3, 4, 3, 4, 3, 4]
167///     ]
168/// );
169/// ```
170#[allow(dead_code)]
171pub fn tile_2d<T>(array: ArrayView<T, Ix2>, reps_axis_0: usize, repsaxis_1: usize) -> Array<T, Ix2>
172where
173    T: Clone + Default + Zero,
174{
175    let (rows, cols) = (array.shape()[0], array.shape()[1]);
176
177    // New dimensions after tiling
178    let new_rows = rows * reps_axis_0;
179    let new_cols = cols * repsaxis_1;
180
181    // Edge case - zero repetitions
182    if reps_axis_0 == 0 || repsaxis_1 == 0 {
183        return Array::<T, Ix2>::default((0, 0));
184    }
185
186    // Edge case - one repetition
187    if reps_axis_0 == 1 && repsaxis_1 == 1 {
188        return array.to_owned();
189    }
190
191    let mut result = Array::<T, Ix2>::zeros((new_rows, new_cols));
192
193    // Fill the result with repeated copies of the array
194    for i in 0..new_rows {
195        for j in 0..new_cols {
196            let src_i = i % rows;
197            let src_j = j % cols;
198
199            result[[i, j]] = array[[src_i, src_j]].clone();
200        }
201    }
202
203    result
204}
205
206/// Repeat array elements by duplicating values
207///
208/// # Arguments
209///
210/// * `array` - The input 2D array
211/// * `repeats_axis_0` - Number of times to repeat each element along axis 0
212/// * `repeats_axis_1` - Number of times to repeat each element along axis 1
213///
214/// # Returns
215///
216/// A new array with elements repeated as specified
217///
218/// # Examples
219///
220/// ```
221/// use ::ndarray::array;
222/// use scirs2_core::ndarray_ext::manipulation::repeat_2d;
223///
224/// let a = array![[1, 2], [3, 4]];
225///
226/// // Repeat array elements 2 times along axis 0 and 3 times along axis 1
227/// let repeated = repeat_2d(a.view(), 2, 3);
228/// assert_eq!(repeated.shape(), &[4, 6]);
229/// assert_eq!(repeated,
230///     array![
231///         [1, 1, 1, 2, 2, 2],
232///         [1, 1, 1, 2, 2, 2],
233///         [3, 3, 3, 4, 4, 4],
234///         [3, 3, 3, 4, 4, 4]
235///     ]
236/// );
237/// ```
238#[allow(dead_code)]
239pub fn repeat_2d<T>(
240    array: ArrayView<T, Ix2>,
241    repeats_axis_0: usize,
242    repeats_axis_1: usize,
243) -> Array<T, Ix2>
244where
245    T: Clone + Default + Zero,
246{
247    let (rows, cols) = (array.shape()[0], array.shape()[1]);
248
249    // New dimensions after repeating
250    let new_rows = rows * repeats_axis_0;
251    let new_cols = cols * repeats_axis_1;
252
253    // Edge case - zero repetitions
254    if repeats_axis_0 == 0 || repeats_axis_1 == 0 {
255        return Array::<T, Ix2>::default((0, 0));
256    }
257
258    // Edge case - one repetition
259    if repeats_axis_0 == 1 && repeats_axis_1 == 1 {
260        return array.to_owned();
261    }
262
263    let mut result = Array::<T, Ix2>::zeros((new_rows, new_cols));
264
265    // Fill the result with repeated elements
266    for i in 0..rows {
267        for j in 0..cols {
268            for i_rep in 0..repeats_axis_0 {
269                for j_rep in 0..repeats_axis_1 {
270                    let dest_i = i * repeats_axis_0 + i_rep;
271                    let dest_j = j * repeats_axis_1 + j_rep;
272
273                    result[[dest_i, dest_j]] = array[[i, j]].clone();
274                }
275            }
276        }
277    }
278
279    result
280}
281
282/// Swap rows or columns in a 2D array
283///
284/// # Arguments
285///
286/// * `array` - The input 2D array
287/// * `index1` - First index to swap
288/// * `index2` - Second index to swap
289/// * `axis` - Axis along which to swap (0 for rows, 1 for columns)
290///
291/// # Returns
292///
293/// A new array with specified rows or columns swapped
294///
295/// # Examples
296///
297/// ```
298/// use ::ndarray::array;
299/// use scirs2_core::ndarray_ext::manipulation::swap_axes_2d;
300///
301/// let a = array![[1, 2, 3], [4, 5, 6], [7, 8, 9]];
302///
303/// // Swap rows 0 and 2
304/// let swapped_rows = swap_axes_2d(a.view(), 0, 2, 0).unwrap();
305/// assert_eq!(swapped_rows, array![[7, 8, 9], [4, 5, 6], [1, 2, 3]]);
306///
307/// // Swap columns 0 and 1
308/// let swapped_cols = swap_axes_2d(a.view(), 0, 1, 1).unwrap();
309/// assert_eq!(swapped_cols, array![[2, 1, 3], [5, 4, 6], [8, 7, 9]]);
310/// ```
311#[allow(dead_code)]
312pub fn swap_axes_2d<T>(
313    array: ArrayView<T, Ix2>,
314    index1: usize,
315    index2: usize,
316    axis: usize,
317) -> Result<Array<T, Ix2>, &'static str>
318where
319    T: Clone,
320{
321    let (rows, cols) = (array.shape()[0], array.shape()[1]);
322
323    if axis > 1 {
324        return Err("Axis must be 0 or 1 for 2D arrays");
325    }
326
327    // Check indices are in bounds
328    let axis_len = if axis == 0 { rows } else { cols };
329    if index1 >= axis_len || index2 >= axis_len {
330        return Err("Indices out of bounds");
331    }
332
333    // If indices are the same, just clone the array
334    if index1 == index2 {
335        return Ok(array.to_owned());
336    }
337
338    let mut result = array.to_owned();
339
340    match axis {
341        0 => {
342            // Swap rows
343            for j in 0..cols {
344                let temp = result[[index1, j]].clone();
345                result[[index1, j]] = result[[index2, j]].clone();
346                result[[index2, j]] = temp;
347            }
348        }
349        1 => {
350            // Swap columns
351            for i in 0..rows {
352                let temp = result[[i, index1]].clone();
353                result[[i, index1]] = result[[i, index2]].clone();
354                result[[i, index2]] = temp;
355            }
356        }
357        _ => unreachable!(),
358    }
359
360    Ok(result)
361}
362
363/// Pad a 2D array with a constant value
364///
365/// # Arguments
366///
367/// * `array` - The input 2D array
368/// * `pad_width` - A tuple of tuples specifying the number of values padded
369///   to the edges of each axis: ((before_axis_0, after_axis_0), (before_axis_1, after_axis_1))
370/// * `pad_value` - The value to set the padded elements
371///
372/// # Returns
373///
374/// A new array with padded borders
375///
376/// # Examples
377///
378/// ```
379/// use ::ndarray::array;
380/// use scirs2_core::ndarray_ext::manipulation::pad_2d;
381///
382/// let a = array![[1, 2], [3, 4]];
383///
384/// // Pad with 1 row before, 2 rows after, 1 column before, and 0 columns after
385/// let padded = pad_2d(a.view(), ((1, 2), (1, 0)), 0);
386/// assert_eq!(padded.shape(), &[5, 3]);
387/// assert_eq!(padded,
388///     array![
389///         [0, 0, 0],
390///         [0, 1, 2],
391///         [0, 3, 4],
392///         [0, 0, 0],
393///         [0, 0, 0]
394///     ]
395/// );
396/// ```
397#[allow(dead_code)]
398pub fn pad_2d<T>(
399    array: ArrayView<T, Ix2>,
400    pad_width: ((usize, usize), (usize, usize)),
401    pad_value: T,
402) -> Array<T, Ix2>
403where
404    T: Clone,
405{
406    let (rows, cols) = (array.shape()[0], array.shape()[1]);
407    let ((before_0, after_0), (before_1, after_1)) = pad_width;
408
409    // Calculate new dimensions
410    let new_rows = rows + before_0 + after_0;
411    let new_cols = cols + before_1 + after_1;
412
413    // Create the result array filled with the padding value
414    let mut result = Array::<T, Ix2>::from_elem((new_rows, new_cols), pad_value);
415
416    // Copy the original array into the padded array
417    for i in 0..rows {
418        for j in 0..cols {
419            result[[i + before_0, j + before_1]] = array[[i, j]].clone();
420        }
421    }
422
423    result
424}
425
426/// Concatenate 2D arrays along a specified axis
427///
428/// # Arguments
429///
430/// * `arrays` - A slice of 2D arrays to concatenate
431/// * `axis` - The axis along which to concatenate (0 for rows, 1 for columns)
432///
433/// # Returns
434///
435/// A new array containing the concatenated arrays
436///
437/// # Examples
438///
439/// ```
440/// use ::ndarray::array;
441/// use scirs2_core::ndarray_ext::manipulation::concatenate_2d;
442///
443/// let a = array![[1, 2], [3, 4]];
444/// let b = array![[5, 6], [7, 8]];
445///
446/// // Concatenate along rows (vertically)
447/// let vertical = concatenate_2d(&[a.view(), b.view()], 0).unwrap();
448/// assert_eq!(vertical.shape(), &[4, 2]);
449/// assert_eq!(vertical, array![[1, 2], [3, 4], [5, 6], [7, 8]]);
450///
451/// // Concatenate along columns (horizontally)
452/// let horizontal = concatenate_2d(&[a.view(), b.view()], 1).unwrap();
453/// assert_eq!(horizontal.shape(), &[2, 4]);
454/// assert_eq!(horizontal, array![[1, 2, 5, 6], [3, 4, 7, 8]]);
455/// ```
456#[allow(dead_code)]
457pub fn concatenate_2d<T>(
458    arrays: &[ArrayView<T, Ix2>],
459    axis: usize,
460) -> Result<Array<T, Ix2>, &'static str>
461where
462    T: Clone + Zero,
463{
464    if arrays.is_empty() {
465        return Err("No arrays provided for concatenation");
466    }
467
468    if axis > 1 {
469        return Err("Axis must be 0 or 1 for 2D arrays");
470    }
471
472    // Get the shape of the first array as a reference
473    let firstshape = arrays[0].shape();
474
475    // Calculate the total shape after concatenation
476    let mut totalshape = [firstshape[0], firstshape[1]];
477    for array in arrays.iter().skip(1) {
478        let currentshape = array.shape();
479
480        // Ensure all arrays have compatible shapes
481        if axis == 0 && currentshape[1] != firstshape[1] {
482            return Err("All arrays must have the same number of columns for axis=0 concatenation");
483        } else if axis == 1 && currentshape[0] != firstshape[0] {
484            return Err("All arrays must have the same number of rows for axis=1 concatenation");
485        }
486
487        totalshape[axis] += currentshape[axis];
488    }
489
490    // Create the result array
491    let mut result = Array::<T, Ix2>::zeros((totalshape[0], totalshape[1]));
492
493    // Fill the result array with data from the input arrays
494    match axis {
495        0 => {
496            // Concatenate along axis 0 (vertically)
497            let mut row_offset = 0;
498            for array in arrays {
499                let rows = array.shape()[0];
500                let cols = array.shape()[1];
501
502                for i in 0..rows {
503                    for j in 0..cols {
504                        result[[row_offset + i, j]] = array[[i, j]].clone();
505                    }
506                }
507
508                row_offset += rows;
509            }
510        }
511        1 => {
512            // Concatenate along axis 1 (horizontally)
513            let mut col_offset = 0;
514            for array in arrays {
515                let rows = array.shape()[0];
516                let cols = array.shape()[1];
517
518                for i in 0..rows {
519                    for j in 0..cols {
520                        result[[i, col_offset + j]] = array[[i, j]].clone();
521                    }
522                }
523
524                col_offset += cols;
525            }
526        }
527        _ => unreachable!(),
528    }
529
530    Ok(result)
531}
532
533/// Stack a sequence of 1D arrays into a 2D array
534///
535/// # Arguments
536///
537/// * `arrays` - A slice of 1D arrays to stack
538///
539/// # Returns
540///
541/// A 2D array where each row contains an input array
542///
543/// # Examples
544///
545/// ```
546/// use ::ndarray::array;
547/// use scirs2_core::ndarray_ext::manipulation::vstack_1d;
548///
549/// let a = array![1, 2, 3];
550/// let b = array![4, 5, 6];
551/// let c = array![7, 8, 9];
552///
553/// let stacked = vstack_1d(&[a.view(), b.view(), c.view()]).unwrap();
554/// assert_eq!(stacked.shape(), &[3, 3]);
555/// assert_eq!(stacked, array![[1, 2, 3], [4, 5, 6], [7, 8, 9]]);
556/// ```
557#[allow(dead_code)]
558pub fn vstack_1d<T>(arrays: &[ArrayView<T, Ix1>]) -> Result<Array<T, Ix2>, &'static str>
559where
560    T: Clone + Zero,
561{
562    if arrays.is_empty() {
563        return Err("No arrays provided for stacking");
564    }
565
566    // All arrays must have the same length
567    let expected_len = arrays[0].len();
568    for (_i, array) in arrays.iter().enumerate().skip(1) {
569        if array.len() != expected_len {
570            return Err("Arrays must have consistent lengths for stacking");
571        }
572    }
573
574    // Create the result array
575    let rows = arrays.len();
576    let cols = expected_len;
577    let mut result = Array::<T, Ix2>::zeros((rows, cols));
578
579    // Fill the result array
580    for (i, array) in arrays.iter().enumerate() {
581        for (j, val) in array.iter().enumerate() {
582            result[[i, j]] = val.clone();
583        }
584    }
585
586    Ok(result)
587}
588
589/// Stack a sequence of 1D arrays horizontally (as columns) into a 2D array
590///
591/// # Arguments
592///
593/// * `arrays` - A slice of 1D arrays to stack
594///
595/// # Returns
596///
597/// A 2D array where each column contains an input array
598///
599/// # Examples
600///
601/// ```
602/// use ::ndarray::array;
603/// use scirs2_core::ndarray_ext::manipulation::hstack_1d;
604///
605/// let a = array![1, 2, 3];
606/// let b = array![4, 5, 6];
607///
608/// let stacked = hstack_1d(&[a.view(), b.view()]).unwrap();
609/// assert_eq!(stacked.shape(), &[3, 2]);
610/// assert_eq!(stacked, array![[1, 4], [2, 5], [3, 6]]);
611/// ```
612#[allow(dead_code)]
613pub fn hstack_1d<T>(arrays: &[ArrayView<T, Ix1>]) -> Result<Array<T, Ix2>, &'static str>
614where
615    T: Clone + Zero,
616{
617    if arrays.is_empty() {
618        return Err("No arrays provided for stacking");
619    }
620
621    // All arrays must have the same length
622    let expected_len = arrays[0].len();
623    for (_i, array) in arrays.iter().enumerate().skip(1) {
624        if array.len() != expected_len {
625            return Err("Arrays must have consistent lengths for stacking");
626        }
627    }
628
629    // Create the result array
630    let rows = expected_len;
631    let cols = arrays.len();
632    let mut result = Array::<T, Ix2>::zeros((rows, cols));
633
634    // Fill the result array
635    for (j, array) in arrays.iter().enumerate() {
636        for (i, val) in array.iter().enumerate() {
637            result[[i, j]] = val.clone();
638        }
639    }
640
641    Ok(result)
642}
643
644/// Remove a dimension of size 1 from a 2D array, resulting in a 1D array
645///
646/// # Arguments
647///
648/// * `array` - The input 2D array
649/// * `axis` - The axis to squeeze (0 for rows, 1 for columns)
650///
651/// # Returns
652///
653/// A 1D array with the specified dimension removed
654///
655/// # Examples
656///
657/// ```
658/// use ::ndarray::array;
659/// use scirs2_core::ndarray_ext::manipulation::squeeze_2d;
660///
661/// let a = array![[1, 2, 3]];  // 1x3 array (1 row, 3 columns)
662/// let b = array![[1], [2], [3]];  // 3x1 array (3 rows, 1 column)
663///
664/// // Squeeze out the row dimension (axis 0) from a
665/// let squeezed_a = squeeze_2d(a.view(), 0).unwrap();
666/// assert_eq!(squeezed_a.shape(), &[3]);
667/// assert_eq!(squeezed_a, array![1, 2, 3]);
668///
669/// // Squeeze out the column dimension (axis 1) from b
670/// let squeezed_b = squeeze_2d(b.view(), 1).unwrap();
671/// assert_eq!(squeezed_b.shape(), &[3]);
672/// assert_eq!(squeezed_b, array![1, 2, 3]);
673/// ```
674#[allow(dead_code)]
675pub fn squeeze_2d<T>(array: ArrayView<T, Ix2>, axis: usize) -> Result<Array<T, Ix1>, &'static str>
676where
677    T: Clone + Zero,
678{
679    let (rows, cols) = (array.shape()[0], array.shape()[1]);
680
681    match axis {
682        0 => {
683            // Squeeze out row dimension
684            if rows != 1 {
685                return Err("Cannot squeeze array with more than 1 row along axis 0");
686            }
687
688            let mut result = Array::<T, Ix1>::zeros(cols);
689            for j in 0..cols {
690                result[j] = array[[0, j]].clone();
691            }
692
693            Ok(result)
694        }
695        1 => {
696            // Squeeze out column dimension
697            if cols != 1 {
698                return Err("Cannot squeeze array with more than 1 column along axis 1");
699            }
700
701            let mut result = Array::<T, Ix1>::zeros(rows);
702            for i in 0..rows {
703                result[i] = array[[i, 0]].clone();
704            }
705
706            Ok(result)
707        }
708        _ => Err("Axis must be 0 or 1 for 2D arrays"),
709    }
710}
711
712/// Create a meshgrid from 1D coordinate arrays
713///
714/// # Arguments
715///
716/// * `x` - 1D array of x coordinates
717/// * `y` - 1D array of y coordinates
718///
719/// # Returns
720///
721/// A tuple of two 2D arrays (X, Y) where X and Y are copies of the input arrays
722/// repeated to form a meshgrid
723///
724/// # Examples
725///
726/// ```
727/// use ::ndarray::array;
728/// use scirs2_core::ndarray_ext::manipulation::meshgrid;
729///
730/// let x = array![1, 2, 3];
731/// let y = array![4, 5];
732/// let (x_grid, y_grid) = meshgrid(x.view(), y.view()).unwrap();
733/// assert_eq!(x_grid.shape(), &[2, 3]);
734/// assert_eq!(y_grid.shape(), &[2, 3]);
735/// assert_eq!(x_grid, array![[1, 2, 3], [1, 2, 3]]);
736/// assert_eq!(y_grid, array![[4, 4, 4], [5, 5, 5]]);
737/// ```
738#[allow(dead_code)]
739pub fn meshgrid<T>(x: ArrayView<T, Ix1>, y: ArrayView<T, Ix1>) -> GradientResult<T>
740where
741    T: Clone + Zero,
742{
743    let nx = x.len();
744    let ny = y.len();
745
746    if nx == 0 || ny == 0 {
747        return Err("Input arrays must not be empty");
748    }
749
750    // Create output arrays
751    let mut x_grid = Array::<T, Ix2>::zeros((ny, nx));
752    let mut y_grid = Array::<T, Ix2>::zeros((ny, nx));
753
754    // Fill the meshgrid
755    for i in 0..ny {
756        for j in 0..nx {
757            x_grid[[i, j]] = x[j].clone();
758            y_grid[[i, j]] = y[i].clone();
759        }
760    }
761
762    Ok((x_grid, y_grid))
763}
764
765/// Find unique elements in an array
766///
767/// # Arguments
768///
769/// * `array` - The input 1D array
770///
771/// # Returns
772///
773/// A 1D array containing the unique elements of the input array, sorted
774///
775/// # Examples
776///
777/// ```
778/// use ::ndarray::array;
779/// use scirs2_core::ndarray_ext::manipulation::unique;
780///
781/// let a = array![3, 1, 2, 2, 3, 4, 1];
782/// let result = unique(a.view()).unwrap();
783/// assert_eq!(result, array![1, 2, 3, 4]);
784/// ```
785#[allow(dead_code)]
786pub fn unique<T>(array: ArrayView<T, Ix1>) -> Result<Array<T, Ix1>, &'static str>
787where
788    T: Clone + Ord,
789{
790    if array.is_empty() {
791        return Err("Input array must not be empty");
792    }
793
794    // Clone elements to a Vec and sort
795    let mut values: Vec<T> = array.iter().cloned().collect();
796    values.sort();
797
798    // Remove duplicates
799    values.dedup();
800
801    // Convert to ndarray
802    Ok(Array::from_vec(values))
803}
804
805/// Return the indices of the minimum values along the specified axis
806///
807/// # Arguments
808///
809/// * `array` - The input 2D array
810/// * `axis` - The axis along which to find the minimum values (0 for rows, 1 for columns, None for flattened array)
811///
812/// # Returns
813///
814/// A 1D array containing the indices of the minimum values
815///
816/// # Examples
817///
818/// ```
819/// use ::ndarray::array;
820/// use scirs2_core::ndarray_ext::manipulation::argmin;
821///
822/// let a = array![[5, 2, 3], [4, 1, 6]];
823///
824/// // Find indices of minimum values along axis 0 (columns)
825/// let result = argmin(a.view(), Some(0)).unwrap();
826/// assert_eq!(result, array![1, 1, 0]); // The indices of min values in each column
827///
828/// // Find indices of minimum values along axis 1 (rows)
829/// let result = argmin(a.view(), Some(1)).unwrap();
830/// assert_eq!(result, array![1, 1]); // The indices of min values in each row
831///
832/// // Find index of minimum value in flattened array
833/// let result = argmin(a.view(), None).unwrap();
834/// assert_eq!(result[0], 4); // The index of the minimum value in the flattened array (row 1, col 1)
835/// ```
836#[allow(dead_code)]
837pub fn argmin<T>(
838    array: ArrayView<T, Ix2>,
839    axis: Option<usize>,
840) -> Result<Array<usize, Ix1>, &'static str>
841where
842    T: Clone + PartialOrd,
843{
844    let (rows, cols) = (array.shape()[0], array.shape()[1]);
845
846    if rows == 0 || cols == 0 {
847        return Err("Input array must not be empty");
848    }
849
850    match axis {
851        Some(0) => {
852            // Find min indices along axis 0 (for each column)
853            let mut indices = Array::<usize, Ix1>::zeros(cols);
854
855            for j in 0..cols {
856                let mut min_idx = 0;
857                let mut min_val = &array[[0, j]];
858
859                for i in 1..rows {
860                    if &array[[i, j]] < min_val {
861                        min_idx = i;
862                        min_val = &array[[i, j]];
863                    }
864                }
865
866                indices[j] = min_idx;
867            }
868
869            Ok(indices)
870        }
871        Some(1) => {
872            // Find min indices along axis 1 (for each row)
873            let mut indices = Array::<usize, Ix1>::zeros(rows);
874
875            for i in 0..rows {
876                let mut min_idx = 0;
877                let mut min_val = &array[[i, 0]];
878
879                for j in 1..cols {
880                    if &array[[i, j]] < min_val {
881                        min_idx = j;
882                        min_val = &array[[i, j]];
883                    }
884                }
885
886                indices[i] = min_idx;
887            }
888
889            Ok(indices)
890        }
891        Some(_) => Err("Axis must be 0 or 1 for 2D arrays"),
892        None => {
893            // Find min index in flattened array
894            let mut min_idx = 0;
895            let mut min_val = &array[[0, 0]];
896
897            for i in 0..rows {
898                for j in 0..cols {
899                    if &array[[i, j]] < min_val {
900                        min_idx = i * cols + j;
901                        min_val = &array[[i, j]];
902                    }
903                }
904            }
905
906            Ok(Array::from_vec(vec![min_idx]))
907        }
908    }
909}
910
911/// Return the indices of the minimum values along the specified axis with SIMD acceleration.
912///
913/// This is a SIMD-accelerated version of `argmin` that provides significant performance
914/// improvements for large arrays (typically 2-3x faster for f32 operations).
915///
916/// # Arguments
917///
918/// * `array` - The input 2D array
919/// * `axis` - The axis along which to find the minimum values (0 for rows, 1 for columns, None for flattened array)
920///
921/// # Returns
922///
923/// A 1D array containing the indices of the minimum values
924///
925/// # Performance
926///
927/// - For axis=None (flattened): Uses SIMD for f32/f64 types, ~2-3x faster
928/// - For axis=Some(0/1): Currently uses scalar implementation
929///
930/// # Examples
931///
932/// ```
933/// use scirs2_core::ndarray::array;
934/// use scirs2_core::ndarray_ext::manipulation::argmin_simd;
935///
936/// let a = array![[5.0f32, 2.0, 3.0], [4.0, 1.0, 6.0]];
937///
938/// // Find index of minimum value in flattened array (SIMD-accelerated)
939/// let result = argmin_simd(a.view(), None).unwrap();
940/// assert_eq!(result[0], 4); // Index of 1.0 in flattened array
941/// ```
942#[allow(dead_code)]
943pub fn argmin_simd<T>(
944    array: ArrayView<T, Ix2>,
945    axis: Option<usize>,
946) -> Result<Array<usize, Ix1>, &'static str>
947where
948    T: Clone + PartialOrd + Float + SimdUnifiedOps,
949{
950    let (rows, cols) = (array.shape()[0], array.shape()[1]);
951
952    if rows == 0 || cols == 0 {
953        return Err("Input array must not be empty");
954    }
955
956    match axis {
957        Some(0) => {
958            // Find min indices along axis 0 (for each column)
959            let mut indices = Array::<usize, Ix1>::zeros(cols);
960
961            for j in 0..cols {
962                // Extract column as contiguous 1D array for SIMD
963                let col = array.column(j);
964                if col.is_standard_layout() {
965                    // Can use SIMD on contiguous column
966                    if let Some(idx) = reduction::argmin_simd(&col) {
967                        indices[j] = idx;
968                    }
969                } else {
970                    // Fallback to scalar for non-contiguous
971                    let mut min_idx = 0;
972                    let mut min_val = &array[[0, j]];
973
974                    for i in 1..rows {
975                        if &array[[i, j]] < min_val {
976                            min_idx = i;
977                            min_val = &array[[i, j]];
978                        }
979                    }
980
981                    indices[j] = min_idx;
982                }
983            }
984
985            Ok(indices)
986        }
987        Some(1) => {
988            // Find min indices along axis 1 (for each row)
989            let mut indices = Array::<usize, Ix1>::zeros(rows);
990
991            for i in 0..rows {
992                // Extract row as contiguous 1D array for SIMD
993                let row = array.row(i);
994                if row.is_standard_layout() {
995                    // Can use SIMD on contiguous row
996                    if let Some(idx) = reduction::argmin_simd(&row) {
997                        indices[i] = idx;
998                    }
999                } else {
1000                    // Fallback to scalar for non-contiguous
1001                    let mut min_idx = 0;
1002                    let mut min_val = &array[[i, 0]];
1003
1004                    for j in 1..cols {
1005                        if &array[[i, j]] < min_val {
1006                            min_idx = j;
1007                            min_val = &array[[i, j]];
1008                        }
1009                    }
1010
1011                    indices[i] = min_idx;
1012                }
1013            }
1014
1015            Ok(indices)
1016        }
1017        Some(_) => Err("Axis must be 0 or 1 for 2D arrays"),
1018        None => {
1019            // Find min index in flattened array with SIMD
1020            // Flatten to 1D for SIMD processing
1021            let flattened = array.as_slice();
1022
1023            if let Some(slice) = flattened {
1024                // Contiguous memory - can use SIMD directly
1025                let view = crate::ndarray::ArrayView1::from(slice);
1026                if let Some(idx) = reduction::argmin_simd(&view) {
1027                    return Ok(Array::from_vec(vec![idx]));
1028                }
1029            }
1030
1031            // Fallback to scalar for non-contiguous arrays
1032            let mut min_idx = 0;
1033            let mut min_val = &array[[0, 0]];
1034
1035            for i in 0..rows {
1036                for j in 0..cols {
1037                    if &array[[i, j]] < min_val {
1038                        min_idx = i * cols + j;
1039                        min_val = &array[[i, j]];
1040                    }
1041                }
1042            }
1043
1044            Ok(Array::from_vec(vec![min_idx]))
1045        }
1046    }
1047}
1048
1049/// Return the indices of the maximum values along the specified axis
1050///
1051/// # Arguments
1052///
1053/// * `array` - The input 2D array
1054/// * `axis` - The axis along which to find the maximum values (0 for rows, 1 for columns, None for flattened array)
1055///
1056/// # Returns
1057///
1058/// A 1D array containing the indices of the maximum values
1059///
1060/// # Examples
1061///
1062/// ```
1063/// use ::ndarray::array;
1064/// use scirs2_core::ndarray_ext::manipulation::argmax;
1065///
1066/// let a = array![[5, 2, 3], [4, 1, 6]];
1067///
1068/// // Find indices of maximum values along axis 0 (columns)
1069/// let result = argmax(a.view(), Some(0)).unwrap();
1070/// assert_eq!(result, array![0, 0, 1]); // The indices of max values in each column
1071///
1072/// // Find indices of maximum values along axis 1 (rows)
1073/// let result = argmax(a.view(), Some(1)).unwrap();
1074/// assert_eq!(result, array![0, 2]); // The indices of max values in each row
1075///
1076/// // Find index of maximum value in flattened array
1077/// let result = argmax(a.view(), None).unwrap();
1078/// assert_eq!(result[0], 5); // The index of the maximum value in the flattened array (row 1, col 2)
1079/// ```
1080#[allow(dead_code)]
1081pub fn argmax<T>(
1082    array: ArrayView<T, Ix2>,
1083    axis: Option<usize>,
1084) -> Result<Array<usize, Ix1>, &'static str>
1085where
1086    T: Clone + PartialOrd,
1087{
1088    let (rows, cols) = (array.shape()[0], array.shape()[1]);
1089
1090    if rows == 0 || cols == 0 {
1091        return Err("Input array must not be empty");
1092    }
1093
1094    match axis {
1095        Some(0) => {
1096            // Find max indices along axis 0 (for each column)
1097            let mut indices = Array::<usize, Ix1>::zeros(cols);
1098
1099            for j in 0..cols {
1100                let mut max_idx = 0;
1101                let mut max_val = &array[[0, j]];
1102
1103                for i in 1..rows {
1104                    if &array[[i, j]] > max_val {
1105                        max_idx = i;
1106                        max_val = &array[[i, j]];
1107                    }
1108                }
1109
1110                indices[j] = max_idx;
1111            }
1112
1113            Ok(indices)
1114        }
1115        Some(1) => {
1116            // Find max indices along axis 1 (for each row)
1117            let mut indices = Array::<usize, Ix1>::zeros(rows);
1118
1119            for i in 0..rows {
1120                let mut max_idx = 0;
1121                let mut max_val = &array[[i, 0]];
1122
1123                for j in 1..cols {
1124                    if &array[[i, j]] > max_val {
1125                        max_idx = j;
1126                        max_val = &array[[i, j]];
1127                    }
1128                }
1129
1130                indices[i] = max_idx;
1131            }
1132
1133            Ok(indices)
1134        }
1135        Some(_) => Err("Axis must be 0 or 1 for 2D arrays"),
1136        None => {
1137            // Find max index in flattened array
1138            let mut max_idx = 0;
1139            let mut max_val = &array[[0, 0]];
1140
1141            for i in 0..rows {
1142                for j in 0..cols {
1143                    if &array[[i, j]] > max_val {
1144                        max_idx = i * cols + j;
1145                        max_val = &array[[i, j]];
1146                    }
1147                }
1148            }
1149
1150            Ok(Array::from_vec(vec![max_idx]))
1151        }
1152    }
1153}
1154
1155/// Return the indices of the maximum values along the specified axis with SIMD acceleration.
1156///
1157/// This is a SIMD-accelerated version of `argmax` that provides significant performance
1158/// improvements for large arrays (typically 2-3x faster for f32 operations).
1159///
1160/// # Arguments
1161///
1162/// * `array` - The input 2D array
1163/// * `axis` - The axis along which to find the maximum values (0 for rows, 1 for columns, None for flattened array)
1164///
1165/// # Returns
1166///
1167/// A 1D array containing the indices of the maximum values
1168///
1169/// # Performance
1170///
1171/// - For axis=None (flattened): Uses SIMD for f32/f64 types, ~2-3x faster
1172/// - For axis=Some(0/1): Uses SIMD when rows/columns are contiguous in memory
1173///
1174/// # Examples
1175///
1176/// ```
1177/// use scirs2_core::ndarray::array;
1178/// use scirs2_core::ndarray_ext::manipulation::argmax_simd;
1179///
1180/// let a = array![[5.0f32, 2.0, 3.0], [4.0, 1.0, 6.0]];
1181///
1182/// // Find index of maximum value in flattened array (SIMD-accelerated)
1183/// let result = argmax_simd(a.view(), None).unwrap();
1184/// assert_eq!(result[0], 5); // Index of 6.0 in flattened array
1185/// ```
1186#[allow(dead_code)]
1187pub fn argmax_simd<T>(
1188    array: ArrayView<T, Ix2>,
1189    axis: Option<usize>,
1190) -> Result<Array<usize, Ix1>, &'static str>
1191where
1192    T: Clone + PartialOrd + Float + SimdUnifiedOps,
1193{
1194    let (rows, cols) = (array.shape()[0], array.shape()[1]);
1195
1196    if rows == 0 || cols == 0 {
1197        return Err("Input array must not be empty");
1198    }
1199
1200    match axis {
1201        Some(0) => {
1202            // Find max indices along axis 0 (for each column)
1203            let mut indices = Array::<usize, Ix1>::zeros(cols);
1204
1205            for j in 0..cols {
1206                // Extract column as contiguous 1D array for SIMD
1207                let col = array.column(j);
1208                if col.is_standard_layout() {
1209                    // Can use SIMD on contiguous column
1210                    if let Some(idx) = reduction::argmax_simd(&col) {
1211                        indices[j] = idx;
1212                    }
1213                } else {
1214                    // Fallback to scalar for non-contiguous
1215                    let mut max_idx = 0;
1216                    let mut max_val = &array[[0, j]];
1217
1218                    for i in 1..rows {
1219                        if &array[[i, j]] > max_val {
1220                            max_idx = i;
1221                            max_val = &array[[i, j]];
1222                        }
1223                    }
1224
1225                    indices[j] = max_idx;
1226                }
1227            }
1228
1229            Ok(indices)
1230        }
1231        Some(1) => {
1232            // Find max indices along axis 1 (for each row)
1233            let mut indices = Array::<usize, Ix1>::zeros(rows);
1234
1235            for i in 0..rows {
1236                // Extract row as contiguous 1D array for SIMD
1237                let row = array.row(i);
1238                if row.is_standard_layout() {
1239                    // Can use SIMD on contiguous row
1240                    if let Some(idx) = reduction::argmax_simd(&row) {
1241                        indices[i] = idx;
1242                    }
1243                } else {
1244                    // Fallback to scalar for non-contiguous
1245                    let mut max_idx = 0;
1246                    let mut max_val = &array[[i, 0]];
1247
1248                    for j in 1..cols {
1249                        if &array[[i, j]] > max_val {
1250                            max_idx = j;
1251                            max_val = &array[[i, j]];
1252                        }
1253                    }
1254
1255                    indices[i] = max_idx;
1256                }
1257            }
1258
1259            Ok(indices)
1260        }
1261        Some(_) => Err("Axis must be 0 or 1 for 2D arrays"),
1262        None => {
1263            // Find max index in flattened array with SIMD
1264            // Flatten to 1D for SIMD processing
1265            let flattened = array.as_slice();
1266
1267            if let Some(slice) = flattened {
1268                // Contiguous memory - can use SIMD directly
1269                let view = crate::ndarray::ArrayView1::from(slice);
1270                if let Some(idx) = reduction::argmax_simd(&view) {
1271                    return Ok(Array::from_vec(vec![idx]));
1272                }
1273            }
1274
1275            // Fallback to scalar for non-contiguous arrays
1276            let mut max_idx = 0;
1277            let mut max_val = &array[[0, 0]];
1278
1279            for i in 0..rows {
1280                for j in 0..cols {
1281                    if &array[[i, j]] > max_val {
1282                        max_idx = i * cols + j;
1283                        max_val = &array[[i, j]];
1284                    }
1285                }
1286            }
1287
1288            Ok(Array::from_vec(vec![max_idx]))
1289        }
1290    }
1291}
1292
1293/// Calculate the gradient of an array
1294///
1295/// # Arguments
1296///
1297/// * `array` - The input 2D array
1298/// * `spacing` - Optional tuple of spacings for each axis
1299///
1300/// # Returns
1301///
1302/// A tuple of arrays (grad_y, grad_x) containing the gradient along each axis
1303///
1304/// # Examples
1305///
1306/// ```
1307/// use ::ndarray::array;
1308/// use scirs2_core::ndarray_ext::manipulation::gradient;
1309///
1310/// let a = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]];
1311///
1312/// // Calculate gradient with default spacing
1313/// let (grad_y, grad_x) = gradient(a.view(), None).unwrap();
1314/// // Vertical gradient (y-direction)
1315/// assert_eq!(grad_y.shape(), &[2, 3]);
1316/// // Horizontal gradient (x-direction)
1317/// assert_eq!(grad_x.shape(), &[2, 3]);
1318/// ```
1319#[allow(dead_code)]
1320pub fn gradient<T>(array: ArrayView<T, Ix2>, spacing: Option<(T, T)>) -> GradientResult<T>
1321where
1322    T: Clone + num_traits::Float,
1323{
1324    let (rows, cols) = (array.shape()[0], array.shape()[1]);
1325
1326    if rows == 0 || cols == 0 {
1327        return Err("Input array must not be empty");
1328    }
1329
1330    // Get spacing values (default to 1.0)
1331    let (dy, dx) = spacing.unwrap_or((T::one(), T::one()));
1332
1333    // Create output arrays for gradients
1334    let mut grad_y = Array::<T, Ix2>::zeros((rows, cols));
1335    let mut grad_x = Array::<T, Ix2>::zeros((rows, cols));
1336
1337    // Calculate gradient along y axis (rows)
1338    if rows == 1 {
1339        // Single row, gradient is zero
1340        // (already initialized with zeros)
1341    } else {
1342        // First row: forward difference
1343        for j in 0..cols {
1344            grad_y[[0, j]] = (array[[1, j]] - array[[0, j]]) / dy;
1345        }
1346
1347        // Middle rows: central difference
1348        for i in 1..rows - 1 {
1349            for j in 0..cols {
1350                grad_y[[i, j]] = (array[[i + 1, j]] - array[[i.saturating_sub(1), j]]) / (dy + dy);
1351            }
1352        }
1353
1354        // Last row: backward difference
1355        for j in 0..cols {
1356            grad_y[[rows - 1, j]] = (array[[rows - 1, j]] - array[[rows - 2, j]]) / dy;
1357        }
1358    }
1359
1360    // Calculate gradient along x axis (columns)
1361    if cols == 1 {
1362        // Single column, gradient is zero
1363        // (already initialized with zeros)
1364    } else {
1365        for i in 0..rows {
1366            // First column: forward difference
1367            grad_x[[i, 0]] = (array[[i, 1]] - array[[i, 0]]) / dx;
1368
1369            // Middle columns: central difference
1370            for j in 1..cols - 1 {
1371                grad_x[[i, j]] = (array[[i, j + 1]] - array[[i, j.saturating_sub(1)]]) / (dx + dx);
1372            }
1373
1374            // Last column: backward difference
1375            grad_x[[i, cols - 1]] = (array[[i, cols - 1]] - array[[i, cols - 2]]) / dx;
1376        }
1377    }
1378
1379    Ok((grad_y, grad_x))
1380}
1381
1382#[cfg(test)]
1383mod tests {
1384    use super::*;
1385    use ::ndarray::array;
1386    use approx::assert_abs_diff_eq;
1387
1388    #[test]
1389    fn test_flip_2d() {
1390        let a = array![[1, 2, 3], [4, 5, 6]];
1391
1392        // Test flipping along axis 0 (rows)
1393        let flipped_rows = flip_2d(a.view(), true, false);
1394        assert_eq!(flipped_rows, array![[4, 5, 6], [1, 2, 3]]);
1395
1396        // Test flipping along axis 1 (columns)
1397        let flipped_cols = flip_2d(a.view(), false, true);
1398        assert_eq!(flipped_cols, array![[3, 2, 1], [6, 5, 4]]);
1399
1400        // Test flipping along both axes
1401        let flipped_both = flip_2d(a.view(), true, true);
1402        assert_eq!(flipped_both, array![[6, 5, 4], [3, 2, 1]]);
1403    }
1404
1405    #[test]
1406    fn test_roll_2d() {
1407        let a = array![[1, 2, 3], [4, 5, 6]];
1408
1409        // Test rolling along axis 0 (rows)
1410        let rolled_rows = roll_2d(a.view(), 1, 0);
1411        assert_eq!(rolled_rows, array![[4, 5, 6], [1, 2, 3]]);
1412
1413        // Test rolling along axis 1 (columns)
1414        let rolled_cols = roll_2d(a.view(), 0, 1);
1415        assert_eq!(rolled_cols, array![[3, 1, 2], [6, 4, 5]]);
1416
1417        // Test negative rolling
1418        let rolled_neg = roll_2d(a.view(), 0, -1);
1419        assert_eq!(rolled_neg, array![[2, 3, 1], [5, 6, 4]]);
1420
1421        // Test rolling by zero (should return the original array)
1422        let rolled_zero = roll_2d(a.view(), 0, 0);
1423        assert_eq!(rolled_zero, a);
1424    }
1425
1426    #[test]
1427    fn test_tile_2d() {
1428        let a = array![[1, 2], [3, 4]];
1429
1430        // Test tiling along both axes
1431        let tiled = tile_2d(a.view(), 2, 3);
1432        assert_eq!(tiled.shape(), &[4, 6]);
1433        assert_eq!(
1434            tiled,
1435            array![
1436                [1, 2, 1, 2, 1, 2],
1437                [3, 4, 3, 4, 3, 4],
1438                [1, 2, 1, 2, 1, 2],
1439                [3, 4, 3, 4, 3, 4]
1440            ]
1441        );
1442
1443        // Test tiling along axis 0 only
1444        let tiled_axis_0 = tile_2d(a.view(), 2, 1);
1445        assert_eq!(tiled_axis_0.shape(), &[4, 2]);
1446        assert_eq!(tiled_axis_0, array![[1, 2], [3, 4], [1, 2], [3, 4]]);
1447
1448        // Test tiling a single element
1449        let single = array![[5]];
1450        let tiled_single = tile_2d(single.view(), 2, 2);
1451        assert_eq!(tiled_single.shape(), &[2, 2]);
1452        assert_eq!(tiled_single, array![[5, 5], [5, 5]]);
1453    }
1454
1455    #[test]
1456    fn test_repeat_2d() {
1457        let a = array![[1, 2], [3, 4]];
1458
1459        // Test repeating along both axes
1460        let repeated = repeat_2d(a.view(), 2, 3);
1461        assert_eq!(repeated.shape(), &[4, 6]);
1462        assert_eq!(
1463            repeated,
1464            array![
1465                [1, 1, 1, 2, 2, 2],
1466                [1, 1, 1, 2, 2, 2],
1467                [3, 3, 3, 4, 4, 4],
1468                [3, 3, 3, 4, 4, 4]
1469            ]
1470        );
1471
1472        // Test repeating along axis 1 only
1473        let repeated_axis_1 = repeat_2d(a.view(), 1, 2);
1474        assert_eq!(repeated_axis_1.shape(), &[2, 4]);
1475        assert_eq!(repeated_axis_1, array![[1, 1, 2, 2], [3, 3, 4, 4]]);
1476    }
1477
1478    #[test]
1479    fn test_swap_axes_2d() {
1480        let a = array![[1, 2, 3], [4, 5, 6], [7, 8, 9]];
1481
1482        // Test swapping rows
1483        let swapped_rows = swap_axes_2d(a.view(), 0, 2, 0).unwrap();
1484        assert_eq!(swapped_rows, array![[7, 8, 9], [4, 5, 6], [1, 2, 3]]);
1485
1486        // Test swapping columns
1487        let swapped_cols = swap_axes_2d(a.view(), 0, 2, 1).unwrap();
1488        assert_eq!(swapped_cols, array![[3, 2, 1], [6, 5, 4], [9, 8, 7]]);
1489
1490        // Test swapping same indices (should return a clone of the original)
1491        let swapped_same = swap_axes_2d(a.view(), 1, 1, 0).unwrap();
1492        assert_eq!(swapped_same, a);
1493
1494        // Test invalid axis
1495        assert!(swap_axes_2d(a.view(), 0, 1, 2).is_err());
1496
1497        // Test out of bounds indices
1498        assert!(swap_axes_2d(a.view(), 0, 3, 0).is_err());
1499    }
1500
1501    #[test]
1502    fn test_pad_2d() {
1503        let a = array![[1, 2], [3, 4]];
1504
1505        // Test padding on all sides
1506        let padded_all = pad_2d(a.view(), ((1, 1), (1, 1)), 0);
1507        assert_eq!(padded_all.shape(), &[4, 4]);
1508        assert_eq!(
1509            padded_all,
1510            array![[0, 0, 0, 0], [0, 1, 2, 0], [0, 3, 4, 0], [0, 0, 0, 0]]
1511        );
1512
1513        // Test uneven padding
1514        let padded_uneven = pad_2d(a.view(), ((2, 0), (0, 1)), 9);
1515        assert_eq!(padded_uneven.shape(), &[4, 3]);
1516        assert_eq!(
1517            padded_uneven,
1518            array![[9, 9, 9], [9, 9, 9], [1, 2, 9], [3, 4, 9]]
1519        );
1520    }
1521
1522    #[test]
1523    fn test_concatenate_2d() {
1524        let a = array![[1, 2], [3, 4]];
1525        let b = array![[5, 6], [7, 8]];
1526
1527        // Test concatenating along axis 0 (vertically)
1528        let vertical = concatenate_2d(&[a.view(), b.view()], 0).unwrap();
1529        assert_eq!(vertical.shape(), &[4, 2]);
1530        assert_eq!(vertical, array![[1, 2], [3, 4], [5, 6], [7, 8]]);
1531
1532        // Test concatenating along axis 1 (horizontally)
1533        let horizontal = concatenate_2d(&[a.view(), b.view()], 1).unwrap();
1534        assert_eq!(horizontal.shape(), &[2, 4]);
1535        assert_eq!(horizontal, array![[1, 2, 5, 6], [3, 4, 7, 8]]);
1536
1537        // Test concatenating with incompatible shapes
1538        let c = array![[9, 10, 11]];
1539        assert!(concatenate_2d(&[a.view(), c.view()], 0).is_err());
1540
1541        // Test concatenating empty array list
1542        let empty: [ArrayView<i32, Ix2>; 0] = [];
1543        assert!(concatenate_2d(&empty, 0).is_err());
1544
1545        // Test invalid axis
1546        assert!(concatenate_2d(&[a.view(), b.view()], 2).is_err());
1547    }
1548
1549    #[test]
1550    fn test_vstack_1d() {
1551        let a = array![1, 2, 3];
1552        let b = array![4, 5, 6];
1553        let c = array![7, 8, 9];
1554
1555        // Test stacking multiple arrays
1556        let stacked = vstack_1d(&[a.view(), b.view(), c.view()]).unwrap();
1557        assert_eq!(stacked.shape(), &[3, 3]);
1558        assert_eq!(stacked, array![[1, 2, 3], [4, 5, 6], [7, 8, 9]]);
1559
1560        // Test stacking empty list
1561        let empty: [ArrayView<i32, Ix1>; 0] = [];
1562        assert!(vstack_1d(&empty).is_err());
1563
1564        // Test inconsistent lengths
1565        let d = array![10, 11];
1566        assert!(vstack_1d(&[a.view(), d.view()]).is_err());
1567    }
1568
1569    #[test]
1570    fn test_hstack_1d() {
1571        let a = array![1, 2, 3];
1572        let b = array![4, 5, 6];
1573
1574        // Test stacking multiple arrays
1575        let stacked = hstack_1d(&[a.view(), b.view()]).unwrap();
1576        assert_eq!(stacked.shape(), &[3, 2]);
1577        assert_eq!(stacked, array![[1, 4], [2, 5], [3, 6]]);
1578
1579        // Test stacking empty list
1580        let empty: [ArrayView<i32, Ix1>; 0] = [];
1581        assert!(hstack_1d(&empty).is_err());
1582
1583        // Test inconsistent lengths
1584        let c = array![7, 8];
1585        assert!(hstack_1d(&[a.view(), c.view()]).is_err());
1586    }
1587
1588    #[test]
1589    fn test_squeeze_2d() {
1590        let a = array![[1, 2, 3]]; // 1x3 array
1591        let b = array![[1], [2], [3]]; // 3x1 array
1592
1593        // Test squeezing axis 0
1594        let squeezed_a = squeeze_2d(a.view(), 0).unwrap();
1595        assert_eq!(squeezed_a.shape(), &[3]);
1596        assert_eq!(squeezed_a, array![1, 2, 3]);
1597
1598        // Test squeezing axis 1
1599        let squeezed_b = squeeze_2d(b.view(), 1).unwrap();
1600        assert_eq!(squeezed_b.shape(), &[3]);
1601        assert_eq!(squeezed_b, array![1, 2, 3]);
1602
1603        // Test squeezing on an axis with size > 1 (should fail)
1604        let c = array![[1, 2], [3, 4]]; // 2x2 array
1605        assert!(squeeze_2d(c.view(), 0).is_err());
1606        assert!(squeeze_2d(c.view(), 1).is_err());
1607
1608        // Test invalid axis
1609        assert!(squeeze_2d(a.view(), 2).is_err());
1610    }
1611
1612    #[test]
1613    fn test_meshgrid() {
1614        let x = array![1, 2, 3];
1615        let y = array![4, 5];
1616
1617        let (x_grid, y_grid) = meshgrid(x.view(), y.view()).unwrap();
1618        assert_eq!(x_grid.shape(), &[2, 3]);
1619        assert_eq!(y_grid.shape(), &[2, 3]);
1620        assert_eq!(x_grid, array![[1, 2, 3], [1, 2, 3]]);
1621        assert_eq!(y_grid, array![[4, 4, 4], [5, 5, 5]]);
1622
1623        // Test empty arrays
1624        let empty = array![];
1625        assert!(meshgrid(x.view(), empty.view()).is_err());
1626        assert!(meshgrid(empty.view(), y.view()).is_err());
1627    }
1628
1629    #[test]
1630    fn test_unique() {
1631        let a = array![3, 1, 2, 2, 3, 4, 1];
1632        let result = unique(a.view()).unwrap();
1633        assert_eq!(result, array![1, 2, 3, 4]);
1634
1635        // Test empty array
1636        let empty: Array<i32, Ix1> = array![];
1637        assert!(unique(empty.view()).is_err());
1638    }
1639
1640    #[test]
1641    fn test_argmin() {
1642        let a = array![[5, 2, 3], [4, 1, 6]];
1643
1644        // Test along axis 0
1645        let result = argmin(a.view(), Some(0)).unwrap();
1646        assert_eq!(result, array![1, 1, 0]);
1647
1648        // Test along axis 1
1649        let result = argmin(a.view(), Some(1)).unwrap();
1650        assert_eq!(result, array![1, 1]);
1651
1652        // Test flattened array
1653        let result = argmin(a.view(), None).unwrap();
1654        assert_eq!(result[0], 4); // Index of 1 in the flattened array (row 1, col 1)
1655
1656        // Test invalid axis
1657        assert!(argmin(a.view(), Some(2)).is_err());
1658
1659        // Test empty array
1660        let empty: Array<i32, Ix2> = Array::zeros((0, 0));
1661        assert!(argmin(empty.view(), Some(0)).is_err());
1662    }
1663
1664    #[test]
1665    fn test_argmax() {
1666        let a = array![[5, 2, 3], [4, 1, 6]];
1667
1668        // Test along axis 0
1669        let result = argmax(a.view(), Some(0)).unwrap();
1670        assert_eq!(result, array![0, 0, 1]);
1671
1672        // Test along axis 1
1673        let result = argmax(a.view(), Some(1)).unwrap();
1674        assert_eq!(result, array![0, 2]);
1675
1676        // Test flattened array
1677        let result = argmax(a.view(), None).unwrap();
1678        assert_eq!(result[0], 5); // Index of 6 in the flattened array (row 1, col 2)
1679
1680        // Test invalid axis
1681        assert!(argmax(a.view(), Some(2)).is_err());
1682
1683        // Test empty array
1684        let empty: Array<i32, Ix2> = Array::zeros((0, 0));
1685        assert!(argmax(empty.view(), Some(0)).is_err());
1686    }
1687
1688    #[test]
1689    fn test_gradient() {
1690        let a = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]];
1691
1692        // Calculate gradient with default spacing
1693        let (grad_y, grad_x) = gradient(a.view(), None).unwrap();
1694
1695        // Verify shapes
1696        assert_eq!(grad_y.shape(), &[2, 3]);
1697        assert_eq!(grad_x.shape(), &[2, 3]);
1698
1699        // Check gradient values
1700        // Vertical gradient (y-direction)
1701        assert_abs_diff_eq!(grad_y[[0, 0]], 3.0, epsilon = 1e-10);
1702        assert_abs_diff_eq!(grad_y[[0, 1]], 3.0, epsilon = 1e-10);
1703        assert_abs_diff_eq!(grad_y[[0, 2]], 3.0, epsilon = 1e-10);
1704        assert_abs_diff_eq!(grad_y[[1, 0]], 3.0, epsilon = 1e-10);
1705        assert_abs_diff_eq!(grad_y[[1, 1]], 3.0, epsilon = 1e-10);
1706        assert_abs_diff_eq!(grad_y[[1, 2]], 3.0, epsilon = 1e-10);
1707
1708        // Horizontal gradient (x-direction)
1709        assert_abs_diff_eq!(grad_x[[0, 0]], 1.0, epsilon = 1e-10);
1710        assert_abs_diff_eq!(grad_x[[0, 1]], 1.0, epsilon = 1e-10);
1711        assert_abs_diff_eq!(grad_x[[0, 2]], 1.0, epsilon = 1e-10);
1712        assert_abs_diff_eq!(grad_x[[1, 0]], 1.0, epsilon = 1e-10);
1713        assert_abs_diff_eq!(grad_x[[1, 1]], 1.0, epsilon = 1e-10);
1714        assert_abs_diff_eq!(grad_x[[1, 2]], 1.0, epsilon = 1e-10);
1715
1716        // Test with custom spacing
1717        let (grad_y, grad_x) = gradient(a.view(), Some((2.0, 0.5))).unwrap();
1718
1719        // Vertical gradient (y-direction) with spacing = 2.0
1720        assert_abs_diff_eq!(grad_y[[0, 0]], 1.5, epsilon = 1e-10); // 3.0 / 2.0
1721        assert_abs_diff_eq!(grad_y[[0, 1]], 1.5, epsilon = 1e-10);
1722        assert_abs_diff_eq!(grad_y[[0, 2]], 1.5, epsilon = 1e-10);
1723
1724        // Horizontal gradient (x-direction) with spacing = 0.5
1725        assert_abs_diff_eq!(grad_x[[0, 0]], 2.0, epsilon = 1e-10); // 1.0 / 0.5
1726        assert_abs_diff_eq!(grad_x[[0, 1]], 2.0, epsilon = 1e-10);
1727        assert_abs_diff_eq!(grad_x[[0, 2]], 2.0, epsilon = 1e-10);
1728
1729        // Test empty array
1730        let empty: Array<f32, Ix2> = Array::zeros((0, 0));
1731        assert!(gradient(empty.view(), None).is_err());
1732    }
1733}