1use crate::{next_fast_len, FFTError, FFTResult};
8use scirs2_core::ndarray::{s, Array1, ArrayBase, ArrayD, Data, Dimension, IxDyn, Slice};
9use scirs2_core::numeric::Complex;
10use scirs2_core::numeric::Zero;
11
12#[derive(Debug, Clone, Copy, PartialEq)]
14pub enum PaddingMode {
15 None,
17 Zero,
19 Constant(f64),
21 Edge,
23 Reflect,
25 Symmetric,
27 Wrap,
29 LinearRamp,
31}
32
33#[derive(Debug, Clone)]
35pub struct AutoPadConfig {
36 pub mode: PaddingMode,
38 pub min_pad: usize,
40 pub max_pad: Option<usize>,
42 pub power_of_2: bool,
44 pub center: bool,
46}
47
48impl Default for AutoPadConfig {
49 fn default() -> Self {
50 Self {
51 mode: PaddingMode::Zero,
52 min_pad: 0,
53 max_pad: None,
54 power_of_2: false,
55 center: false,
56 }
57 }
58}
59
60impl AutoPadConfig {
61 pub fn new(mode: PaddingMode) -> Self {
63 Self {
64 mode,
65 ..Default::default()
66 }
67 }
68
69 pub fn with_min_pad(mut self, minpad: usize) -> Self {
71 self.min_pad = minpad;
72 self
73 }
74
75 pub fn with_max_pad(mut self, maxpad: usize) -> Self {
77 self.max_pad = Some(maxpad);
78 self
79 }
80
81 pub fn with_power_of_2(mut self) -> Self {
83 self.power_of_2 = true;
84 self
85 }
86
87 pub fn with_center(mut self) -> Self {
89 self.center = true;
90 self
91 }
92}
93
94#[allow(dead_code)]
96pub fn auto_pad_1d<T>(x: &Array1<T>, config: &AutoPadConfig) -> FFTResult<Array1<T>>
97where
98 T: Clone + Zero,
99{
100 let n = x.len();
101
102 let target_size = if config.power_of_2 {
104 let min_size = n + config.min_pad;
106 let mut size = 1;
107 while size < min_size {
108 size *= 2;
109 }
110 size
111 } else {
112 next_fast_len(n + config.min_pad, false)
114 };
115
116 let padded_size = if let Some(max_pad) = config.max_pad {
118 target_size.min(n + max_pad)
119 } else {
120 target_size
121 };
122
123 if padded_size == n {
125 return Ok(x.clone());
126 }
127
128 let mut padded = Array1::zeros(padded_size);
130
131 let start_idx = if config.center {
133 (padded_size - n) / 2
134 } else {
135 0
136 };
137
138 padded.slice_mut(s![start_idx..start_idx + n]).assign(x);
140
141 match config.mode {
143 PaddingMode::None | PaddingMode::Zero => {
144 }
146 PaddingMode::Constant(_value) => {
147 let const_val = T::zero(); if start_idx > 0 {
149 padded.slice_mut(s![..start_idx]).fill(const_val.clone());
150 }
151 if start_idx + n < padded_size {
152 padded.slice_mut(s![start_idx + n..]).fill(const_val);
153 }
154 }
155 PaddingMode::Edge => {
156 if start_idx > 0 {
158 let left_val = x[0].clone();
159 padded.slice_mut(s![..start_idx]).fill(left_val);
160 }
161 if start_idx + n < padded_size {
162 let right_val = x[n - 1].clone();
163 padded.slice_mut(s![start_idx + n..]).fill(right_val);
164 }
165 }
166 PaddingMode::Reflect => {
167 for i in 0..start_idx {
169 let offset = start_idx - i - 1;
170 let cycle = 2 * (n - 1);
171 let src_idx = offset % cycle;
172 let src_idx = if src_idx >= n {
173 cycle - src_idx
174 } else {
175 src_idx
176 };
177 padded[i] = x[src_idx].clone();
178 }
179 for i in (start_idx + n)..padded_size {
180 let offset = i - (start_idx + n);
181 let cycle = 2 * (n - 1);
182 let src_idx = n - 1 - (offset % cycle);
183 padded[i] = x[src_idx].clone();
184 }
185 }
186 PaddingMode::Symmetric => {
187 for i in 0..start_idx {
189 let offset = start_idx - i;
190 let cycle = 2 * n;
191 let src_idx = (offset - 1) % cycle;
192 let src_idx = if src_idx >= n {
193 cycle - 1 - src_idx
194 } else {
195 src_idx
196 };
197 padded[i] = x[src_idx].clone();
198 }
199 for i in (start_idx + n)..padded_size {
200 let offset = i - (start_idx + n);
201 let cycle = 2 * n;
202 let src_idx = n - 1 - (offset % cycle);
203 padded[i] = x[src_idx].clone();
204 }
205 }
206 PaddingMode::Wrap => {
207 for i in 0..start_idx {
209 let src_idx = (n - (start_idx - i) % n) % n;
210 padded[i] = x[src_idx].clone();
211 }
212 for i in (start_idx + n)..padded_size {
213 let src_idx = (i - start_idx) % n;
214 padded[i] = x[src_idx].clone();
215 }
216 }
217 PaddingMode::LinearRamp => {
218 if start_idx > 0 {
220 for i in 0..start_idx {
221 padded[i] = T::zero();
223 }
224 }
225 if start_idx + n < padded_size {
226 for i in (start_idx + n)..padded_size {
227 padded[i] = T::zero();
229 }
230 }
231 }
232 }
233
234 Ok(padded)
235}
236
237#[allow(dead_code)]
239pub fn auto_pad_complex(
240 x: &Array1<Complex<f64>>,
241 config: &AutoPadConfig,
242) -> FFTResult<Array1<Complex<f64>>> {
243 let n = x.len();
244
245 let target_size = if config.power_of_2 {
247 let min_size = n + config.min_pad;
248 let mut size = 1;
249 while size < min_size {
250 size *= 2;
251 }
252 size
253 } else {
254 next_fast_len(n + config.min_pad, false)
255 };
256
257 let padded_size = if let Some(max_pad) = config.max_pad {
259 target_size.min(n + max_pad)
260 } else {
261 target_size
262 };
263
264 if padded_size == n {
265 return Ok(x.clone());
266 }
267
268 let mut padded = Array1::zeros(padded_size);
269 let start_idx = if config.center {
270 (padded_size - n) / 2
271 } else {
272 0
273 };
274
275 padded.slice_mut(s![start_idx..start_idx + n]).assign(x);
276
277 match config.mode {
279 PaddingMode::None | PaddingMode::Zero => {}
280 PaddingMode::Constant(value) => {
281 let const_val = Complex::new(value, 0.0);
282 if start_idx > 0 {
283 padded.slice_mut(s![..start_idx]).fill(const_val);
284 }
285 if start_idx + n < padded_size {
286 padded.slice_mut(s![start_idx + n..]).fill(const_val);
287 }
288 }
289 PaddingMode::Edge => {
290 if start_idx > 0 {
291 let left_val = x[0];
292 padded.slice_mut(s![..start_idx]).fill(left_val);
293 }
294 if start_idx + n < padded_size {
295 let right_val = x[n - 1];
296 padded.slice_mut(s![start_idx + n..]).fill(right_val);
297 }
298 }
299 PaddingMode::LinearRamp => {
300 if start_idx > 0 {
302 let edge_val = x[0];
303 for i in 0..start_idx {
304 let t = i as f64 / start_idx as f64;
305 padded[start_idx - 1 - i] = edge_val * t;
306 }
307 }
308 if start_idx + n < padded_size {
309 let edge_val = x[n - 1];
310 let pad_len = padded_size - (start_idx + n);
311 for i in 0..pad_len {
312 let t = 1.0 - (i as f64 / pad_len as f64);
313 padded[start_idx + n + i] = edge_val * t;
314 }
315 }
316 }
317 _ => {
318 return auto_pad_1d(x, config);
320 }
321 }
322
323 Ok(padded)
324}
325
326#[allow(dead_code)]
328pub fn remove_padding_1d<T>(
329 padded: &Array1<T>,
330 original_size: usize,
331 config: &AutoPadConfig,
332) -> Array1<T>
333where
334 T: Clone,
335{
336 let padded_size = padded.len();
337
338 if padded_size == original_size {
339 return padded.clone();
340 }
341
342 let start_idx = if config.center {
343 (padded_size - original_size) / 2
344 } else {
345 0
346 };
347
348 padded
349 .slice(s![start_idx..start_idx + original_size])
350 .to_owned()
351}
352
353#[allow(dead_code)]
367pub fn auto_pad_nd<S, D>(
368 x: &ArrayBase<S, D>,
369 config: &AutoPadConfig,
370 axes: Option<&[usize]>,
371) -> FFTResult<ArrayD<Complex<f64>>>
372where
373 S: Data<Elem = Complex<f64>>,
374 D: Dimension,
375{
376 let shape = x.shape().to_vec();
377 let ndim = shape.len();
378 let default_axes: Vec<usize> = (0..ndim).collect();
379 let axes: Vec<usize> = axes.map(<[usize]>::to_vec).unwrap_or(default_axes);
380
381 for &axis in &axes {
382 if axis >= ndim {
383 return Err(FFTError::ValueError(format!(
384 "Axis {axis} is out of bounds for array of dimension {ndim}"
385 )));
386 }
387 }
388
389 let mut paddedshape = shape.clone();
390 let mut start_idx = vec![0usize; ndim];
392
393 for &axis in &axes {
395 let n = shape[axis];
396 let target_size = if config.power_of_2 {
397 let min_size = n + config.min_pad;
398 let mut size = 1;
399 while size < min_size {
400 size *= 2;
401 }
402 size
403 } else {
404 next_fast_len(n + config.min_pad, false)
405 };
406
407 let padded_axis_len = if let Some(max_pad) = config.max_pad {
408 target_size.min(n + max_pad)
409 } else {
410 target_size
411 };
412 paddedshape[axis] = padded_axis_len;
413 start_idx[axis] = if config.center {
414 (padded_axis_len - n) / 2
415 } else {
416 0
417 };
418 }
419
420 if paddedshape == shape {
422 return Ok(x.to_owned().into_dyn());
423 }
424
425 let mut padded = ArrayD::<Complex<f64>>::zeros(paddedshape.clone());
426
427 {
431 let mut core = padded.slice_each_axis_mut(|desc| {
432 let axis = desc.axis.index();
433 let s = start_idx[axis];
434 Slice::from(s..s + shape[axis])
435 });
436 core.assign(&x.view().into_dyn());
437 }
438
439 if !matches!(config.mode, PaddingMode::None | PaddingMode::Zero) {
443 let mut ordered_axes = axes.clone();
444 ordered_axes.sort_unstable();
445 ordered_axes.dedup();
446 for axis in ordered_axes {
447 let n = shape[axis];
448 let total = paddedshape[axis];
449 if n == 0 || total == n {
450 continue;
451 }
452 fill_axis_border(&mut padded, axis, start_idx[axis], n, total, config.mode);
453 }
454 }
455
456 Ok(padded)
457}
458
459fn other_axis_index_combinations(shape: &[usize], axis: usize) -> Vec<Vec<usize>> {
465 let ndim = shape.len();
466 let mut combos: Vec<Vec<usize>> = vec![vec![0; ndim]];
467 for (dim, &size) in shape.iter().enumerate() {
468 if dim == axis {
469 continue;
470 }
471 let mut expanded = Vec::with_capacity(combos.len() * size.max(1));
472 for combo in &combos {
473 for v in 0..size {
474 let mut next = combo.clone();
475 next[dim] = v;
476 expanded.push(next);
477 }
478 }
479 combos = expanded;
480 }
481 combos
482}
483
484fn reflect_source_index(p: isize, n: usize) -> usize {
489 if n <= 1 {
490 return 0;
491 }
492 let period = 2 * (n as isize - 1);
493 let m = p.rem_euclid(period);
494 if m < n as isize {
495 m as usize
496 } else {
497 (period - m) as usize
498 }
499}
500
501fn symmetric_source_index(p: isize, n: usize) -> usize {
504 if n == 0 {
505 return 0;
506 }
507 let period = 2 * n as isize;
508 let m = p.rem_euclid(period);
509 if m < n as isize {
510 m as usize
511 } else {
512 (period - 1 - m) as usize
513 }
514}
515
516fn wrap_source_index(p: isize, n: usize) -> usize {
518 if n == 0 {
519 return 0;
520 }
521 p.rem_euclid(n as isize) as usize
522}
523
524fn fill_axis_border(
530 padded: &mut ArrayD<Complex<f64>>,
531 axis: usize,
532 start: usize,
533 n: usize,
534 total: usize,
535 mode: PaddingMode,
536) {
537 let shape = padded.shape().to_vec();
538 let end_value = Complex::new(0.0, 0.0);
539
540 for mut indices in other_axis_index_combinations(&shape, axis) {
541 let mut core = Vec::with_capacity(n);
546 for i in 0..n {
547 indices[axis] = start + i;
548 core.push(padded[IxDyn(&indices)]);
549 }
550
551 match mode {
552 PaddingMode::None | PaddingMode::Zero => {}
553 PaddingMode::Constant(value) => {
554 let fill = Complex::new(value, 0.0);
555 for i in 0..start {
556 indices[axis] = i;
557 padded[IxDyn(&indices)] = fill;
558 }
559 for i in (start + n)..total {
560 indices[axis] = i;
561 padded[IxDyn(&indices)] = fill;
562 }
563 }
564 PaddingMode::Edge => {
565 let left = core[0];
566 let right = core[n - 1];
567 for i in 0..start {
568 indices[axis] = i;
569 padded[IxDyn(&indices)] = left;
570 }
571 for i in (start + n)..total {
572 indices[axis] = i;
573 padded[IxDyn(&indices)] = right;
574 }
575 }
576 PaddingMode::Reflect => {
577 for i in 0..start {
578 let src = reflect_source_index(i as isize - start as isize, n);
579 indices[axis] = i;
580 padded[IxDyn(&indices)] = core[src];
581 }
582 for i in (start + n)..total {
583 let src = reflect_source_index(i as isize - start as isize, n);
584 indices[axis] = i;
585 padded[IxDyn(&indices)] = core[src];
586 }
587 }
588 PaddingMode::Symmetric => {
589 for i in 0..start {
590 let src = symmetric_source_index(i as isize - start as isize, n);
591 indices[axis] = i;
592 padded[IxDyn(&indices)] = core[src];
593 }
594 for i in (start + n)..total {
595 let src = symmetric_source_index(i as isize - start as isize, n);
596 indices[axis] = i;
597 padded[IxDyn(&indices)] = core[src];
598 }
599 }
600 PaddingMode::Wrap => {
601 for i in 0..start {
602 let src = wrap_source_index(i as isize - start as isize, n);
603 indices[axis] = i;
604 padded[IxDyn(&indices)] = core[src];
605 }
606 for i in (start + n)..total {
607 let src = wrap_source_index(i as isize - start as isize, n);
608 indices[axis] = i;
609 padded[IxDyn(&indices)] = core[src];
610 }
611 }
612 PaddingMode::LinearRamp => {
613 let left_edge = core[0];
618 if start > 0 {
619 let w = start as f64;
620 for i in 0..start {
621 let k = (start - 1 - i) as f64;
622 let val = left_edge - (left_edge - end_value) * ((k + 1.0) / w);
623 indices[axis] = i;
624 padded[IxDyn(&indices)] = val;
625 }
626 }
627 let right_edge = core[n - 1];
628 let right_len = total - (start + n);
629 if right_len > 0 {
630 let w = right_len as f64;
631 for i in (start + n)..total {
632 let k = (i - (start + n)) as f64;
633 let val = right_edge - (right_edge - end_value) * ((k + 1.0) / w);
634 indices[axis] = i;
635 padded[IxDyn(&indices)] = val;
636 }
637 }
638 }
639 }
640 }
641}
642
643#[cfg(test)]
644mod tests {
645 use super::*;
646 use approx::assert_abs_diff_eq;
647
648 #[test]
649 fn test_auto_pad_zero() {
650 let x = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0]);
651 let config = AutoPadConfig::new(PaddingMode::Zero);
652
653 let padded =
654 auto_pad_complex(&x.mapv(|v| Complex::new(v, 0.0)), &config).expect("Operation failed");
655
656 assert!(padded.len() >= x.len());
658
659 for i in 0..x.len() {
661 assert_abs_diff_eq!(padded[i].re, x[i], epsilon = 1e-10);
662 }
663 }
664
665 #[test]
666 fn test_auto_pad_power_of_2() {
667 let x = Array1::from_vec(vec![1.0; 5]);
668 let config = AutoPadConfig::new(PaddingMode::Zero).with_power_of_2();
669
670 let padded =
671 auto_pad_complex(&x.mapv(|v| Complex::new(v, 0.0)), &config).expect("Operation failed");
672
673 assert_eq!(padded.len(), 8);
675 }
676
677 #[test]
678 fn test_remove_padding() {
679 let padded = Array1::from_vec(vec![0.0, 1.0, 2.0, 3.0, 0.0, 0.0]);
680 let config = AutoPadConfig::new(PaddingMode::Zero);
681
682 let unpadded = remove_padding_1d(&padded, 4, &config);
683 assert_eq!(unpadded.len(), 4);
684 assert_eq!(
685 unpadded.as_slice().expect("Operation failed"),
686 &[0.0, 1.0, 2.0, 3.0]
687 );
688 }
689
690 #[test]
691 fn test_auto_pad_center() {
692 let x = Array1::from_vec(vec![1.0, 2.0, 3.0]);
693 let config = AutoPadConfig::new(PaddingMode::Zero)
694 .with_center()
695 .with_min_pad(3);
696
697 let padded =
698 auto_pad_complex(&x.mapv(|v| Complex::new(v, 0.0)), &config).expect("Operation failed");
699
700 assert!(padded.len() >= 6);
702 let start = (padded.len() - 3) / 2;
703 assert_abs_diff_eq!(padded[start].re, 1.0, epsilon = 1e-10);
704 assert_abs_diff_eq!(padded[start + 1].re, 2.0, epsilon = 1e-10);
705 assert_abs_diff_eq!(padded[start + 2].re, 3.0, epsilon = 1e-10);
706 }
707
708 fn complex_vec(vals: &[f64]) -> Array1<Complex<f64>> {
709 Array1::from_vec(vals.iter().map(|&v| Complex::new(v, 0.0)).collect())
710 }
711
712 fn assert_real_close(actual: &ArrayD<Complex<f64>>, expected: &[f64], eps: f64) {
713 assert_eq!(actual.len(), expected.len());
714 for (a, e) in actual.iter().zip(expected.iter()) {
715 assert_abs_diff_eq!(a.re, e, epsilon = eps);
716 assert_abs_diff_eq!(a.im, 0.0, epsilon = eps);
717 }
718 }
719
720 #[test]
726 fn test_auto_pad_nd_1d_noncentered_all_modes() {
727 let x = complex_vec(&[1.0, 2.0, 3.0, 4.0, 5.0]);
731 let cases: [(PaddingMode, [f64; 8]); 6] = [
732 (
733 PaddingMode::Constant(9.0),
734 [1.0, 2.0, 3.0, 4.0, 5.0, 9.0, 9.0, 9.0],
735 ),
736 (PaddingMode::Edge, [1.0, 2.0, 3.0, 4.0, 5.0, 5.0, 5.0, 5.0]),
737 (
738 PaddingMode::Reflect,
739 [1.0, 2.0, 3.0, 4.0, 5.0, 4.0, 3.0, 2.0],
740 ),
741 (
742 PaddingMode::Symmetric,
743 [1.0, 2.0, 3.0, 4.0, 5.0, 5.0, 4.0, 3.0],
744 ),
745 (PaddingMode::Wrap, [1.0, 2.0, 3.0, 4.0, 5.0, 1.0, 2.0, 3.0]),
746 (
747 PaddingMode::LinearRamp,
748 [
749 1.0,
750 2.0,
751 3.0,
752 4.0,
753 5.0,
754 3.333_333_333_333_333_5,
755 1.666_666_666_666_666_7,
756 0.0,
757 ],
758 ),
759 ];
760 for (mode, expected) in cases {
761 let config = AutoPadConfig::new(mode).with_power_of_2();
762 let result = auto_pad_nd(&x, &config, None).expect("auto_pad_nd failed");
763 assert_eq!(result.shape(), &[8]);
764 assert_real_close(&result, &expected, 1e-9);
765 }
766 }
767
768 #[test]
769 fn test_auto_pad_nd_1d_centered_all_modes() {
770 let x = complex_vec(&[1.0, 2.0, 3.0]);
774 let cases: [(PaddingMode, [f64; 8]); 6] = [
775 (PaddingMode::Edge, [1.0, 1.0, 1.0, 2.0, 3.0, 3.0, 3.0, 3.0]),
776 (
777 PaddingMode::Reflect,
778 [3.0, 2.0, 1.0, 2.0, 3.0, 2.0, 1.0, 2.0],
779 ),
780 (
781 PaddingMode::Symmetric,
782 [2.0, 1.0, 1.0, 2.0, 3.0, 3.0, 2.0, 1.0],
783 ),
784 (PaddingMode::Wrap, [2.0, 3.0, 1.0, 2.0, 3.0, 1.0, 2.0, 3.0]),
785 (
786 PaddingMode::LinearRamp,
787 [0.0, 0.5, 1.0, 2.0, 3.0, 2.0, 1.0, 0.0],
788 ),
789 (
790 PaddingMode::Constant(-4.0),
791 [-4.0, -4.0, 1.0, 2.0, 3.0, -4.0, -4.0, -4.0],
792 ),
793 ];
794 for (mode, expected) in cases {
795 let config = AutoPadConfig::new(mode)
796 .with_power_of_2()
797 .with_min_pad(5)
798 .with_center();
799 let result = auto_pad_nd(&x, &config, None).expect("auto_pad_nd failed");
800 assert_eq!(result.shape(), &[8]);
801 assert_real_close(&result, &expected, 1e-9);
802 }
803 }
804
805 #[test]
806 fn test_auto_pad_nd_invalid_axis_is_an_error() {
807 let x = complex_vec(&[1.0, 2.0, 3.0]);
808 let config = AutoPadConfig::new(PaddingMode::Zero);
809 let err = auto_pad_nd(&x, &config, Some(&[5])).unwrap_err();
810 assert!(matches!(err, FFTError::ValueError(_)));
811 }
812
813 fn complex_2d(
815 rows: usize,
816 cols: usize,
817 vals: &[f64],
818 ) -> scirs2_core::ndarray::Array2<Complex<f64>> {
819 scirs2_core::ndarray::Array2::from_shape_vec(
820 (rows, cols),
821 vals.iter().map(|&v| Complex::new(v, 0.0)).collect(),
822 )
823 .expect("valid shape")
824 }
825
826 #[test]
827 fn test_auto_pad_nd_2d_corners_match_numpy() {
828 #[rustfmt::skip]
836 let input = [
837 1.0, 2.0, 3.0, 4.0, 5.0,
838 6.0, 7.0, 8.0, 9.0, 10.0,
839 11.0, 12.0, 13.0, 14.0, 15.0,
840 ];
841 let x = complex_2d(3, 5, &input);
842
843 let cases: [(PaddingMode, [f64; 32]); 6] = [
844 (
845 PaddingMode::Edge,
846 [
847 1.0, 2.0, 3.0, 4.0, 5.0, 5.0, 5.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 10.0, 10.0,
848 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 15.0, 15.0, 15.0, 11.0, 12.0, 13.0, 14.0,
849 15.0, 15.0, 15.0, 15.0,
850 ],
851 ),
852 (
853 PaddingMode::Reflect,
854 [
855 1.0, 2.0, 3.0, 4.0, 5.0, 4.0, 3.0, 2.0, 6.0, 7.0, 8.0, 9.0, 10.0, 9.0, 8.0,
856 7.0, 11.0, 12.0, 13.0, 14.0, 15.0, 14.0, 13.0, 12.0, 6.0, 7.0, 8.0, 9.0, 10.0,
857 9.0, 8.0, 7.0,
858 ],
859 ),
860 (
861 PaddingMode::Symmetric,
862 [
863 1.0, 2.0, 3.0, 4.0, 5.0, 5.0, 4.0, 3.0, 6.0, 7.0, 8.0, 9.0, 10.0, 10.0, 9.0,
864 8.0, 11.0, 12.0, 13.0, 14.0, 15.0, 15.0, 14.0, 13.0, 11.0, 12.0, 13.0, 14.0,
865 15.0, 15.0, 14.0, 13.0,
866 ],
867 ),
868 (
869 PaddingMode::Wrap,
870 [
871 1.0, 2.0, 3.0, 4.0, 5.0, 1.0, 2.0, 3.0, 6.0, 7.0, 8.0, 9.0, 10.0, 6.0, 7.0,
872 8.0, 11.0, 12.0, 13.0, 14.0, 15.0, 11.0, 12.0, 13.0, 1.0, 2.0, 3.0, 4.0, 5.0,
873 1.0, 2.0, 3.0,
874 ],
875 ),
876 (
877 PaddingMode::Constant(7.0),
878 [
879 1.0, 2.0, 3.0, 4.0, 5.0, 7.0, 7.0, 7.0, 6.0, 7.0, 8.0, 9.0, 10.0, 7.0, 7.0,
880 7.0, 11.0, 12.0, 13.0, 14.0, 15.0, 7.0, 7.0, 7.0, 7.0, 7.0, 7.0, 7.0, 7.0, 7.0,
881 7.0, 7.0,
882 ],
883 ),
884 (
885 PaddingMode::LinearRamp,
886 [
887 1.0,
888 2.0,
889 3.0,
890 4.0,
891 5.0,
892 3.333_333_333_333_333_5,
893 1.666_666_666_666_666_7,
894 0.0,
895 6.0,
896 7.0,
897 8.0,
898 9.0,
899 10.0,
900 6.666_666_666_666_667,
901 3.333_333_333_333_333_5,
902 0.0,
903 11.0,
904 12.0,
905 13.0,
906 14.0,
907 15.0,
908 10.0,
909 5.0,
910 0.0,
911 0.0,
912 0.0,
913 0.0,
914 0.0,
915 0.0,
916 0.0,
917 0.0,
918 0.0,
919 ],
920 ),
921 ];
922
923 for (mode, expected) in cases {
924 let config = AutoPadConfig::new(mode).with_power_of_2();
925 let result = auto_pad_nd(&x, &config, None).expect("auto_pad_nd failed");
926 assert_eq!(result.shape(), &[4, 8]);
927 assert_real_close(&result, &expected, 1e-9);
928 }
929 }
930
931 #[test]
932 fn test_auto_pad_nd_3d_reflect_matches_numpy() {
933 let input: Vec<f64> = (0..24).map(|v| v as f64).collect();
942 let x = ArrayD::from_shape_vec(vec![2, 3, 4], input)
943 .expect("valid shape")
944 .mapv(|v| Complex::new(v, 0.0));
945
946 let config = AutoPadConfig::new(PaddingMode::Reflect)
947 .with_power_of_2()
948 .with_min_pad(1)
949 .with_center();
950 let result = auto_pad_nd(&x, &config, None).expect("auto_pad_nd failed");
951 assert_eq!(result.shape(), &[4, 4, 8]);
952
953 #[rustfmt::skip]
954 let expected = [
955 14.0, 13.0, 12.0, 13.0, 14.0, 15.0, 14.0, 13.0, 18.0, 17.0, 16.0, 17.0, 18.0, 19.0, 18.0, 17.0,
956 22.0, 21.0, 20.0, 21.0, 22.0, 23.0, 22.0, 21.0, 18.0, 17.0, 16.0, 17.0, 18.0, 19.0, 18.0, 17.0, 2.0,
957 1.0, 0.0, 1.0, 2.0, 3.0, 2.0, 1.0, 6.0, 5.0, 4.0, 5.0, 6.0, 7.0, 6.0, 5.0, 10.0, 9.0, 8.0, 9.0,
958 10.0, 11.0, 10.0, 9.0, 6.0, 5.0, 4.0, 5.0, 6.0, 7.0, 6.0, 5.0, 14.0, 13.0, 12.0, 13.0, 14.0, 15.0,
959 14.0, 13.0, 18.0, 17.0, 16.0, 17.0, 18.0, 19.0, 18.0, 17.0, 22.0, 21.0, 20.0, 21.0, 22.0, 23.0,
960 22.0, 21.0, 18.0, 17.0, 16.0, 17.0, 18.0, 19.0, 18.0, 17.0, 2.0, 1.0, 0.0, 1.0, 2.0, 3.0, 2.0, 1.0,
961 6.0, 5.0, 4.0, 5.0, 6.0, 7.0, 6.0, 5.0, 10.0, 9.0, 8.0, 9.0, 10.0, 11.0, 10.0, 9.0, 6.0, 5.0, 4.0,
962 5.0, 6.0, 7.0, 6.0, 5.0,
963 ];
964 assert_real_close(&result, &expected, 1e-9);
965 }
966
967 #[test]
968 fn test_auto_pad_nd_no_padding_needed_returns_input() {
969 let x = complex_vec(&[1.0, 2.0, 3.0, 4.0]);
975 let config = AutoPadConfig::new(PaddingMode::Reflect).with_power_of_2();
976 let result = auto_pad_nd(&x, &config, None).expect("auto_pad_nd failed");
977 assert_eq!(result.shape(), &[4]);
978 assert_real_close(&result, &[1.0, 2.0, 3.0, 4.0], 1e-9);
979 }
980}