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}