use numrs2::array::Array;
use numrs2::array_ops::manipulation::pad;
const EPS: f64 = 1e-9;
fn assert_vec_close(actual: &[f64], expected: &[f64], ctx: &str) {
assert_eq!(actual.len(), expected.len(), "{ctx}: length mismatch");
for (i, (&a, &e)) in actual.iter().zip(expected.iter()).enumerate() {
assert!(
(a - e).abs() < EPS,
"{ctx} at index {i}: expected {e}, got {a} (full actual={actual:?}, expected={expected:?})"
);
}
}
#[test]
fn test_constant_mode_unchanged_behavior() {
let a = Array::from_vec(vec![1, 2, 3]);
let result =
pad(&a, &[(2, 3)], "constant", Some((0, 0)), None, None).expect("pad should succeed");
assert_eq!(result.to_vec(), vec![0, 0, 1, 2, 3, 0, 0, 0]);
}
#[test]
fn test_constant_mode_asymmetric_values() {
let a = Array::from_vec(vec![1, 2, 3, 4, 5]);
let result =
pad(&a, &[(2, 3)], "constant", Some((4, 6)), None, None).expect("pad should succeed");
assert_eq!(result.to_vec(), vec![4, 4, 1, 2, 3, 4, 5, 6, 6, 6]);
}
#[test]
fn test_edge_mode_2d_corners() {
let a = Array::from_vec(vec![1.0, 2.0, 3.0, 4.0]).reshape(&[2, 2]);
let result = pad(&a, &[(1, 1), (1, 1)], "edge", None, None, None).expect("pad should succeed");
let expected = [
1.0, 1.0, 2.0, 2.0, 1.0, 1.0, 2.0, 2.0, 3.0, 3.0, 4.0, 4.0, 3.0, 3.0, 4.0, 4.0,
];
assert_vec_close(&result.to_vec(), &expected, "edge_2d");
}
const STAT_INPUT: [f64; 6] = [1.0, 2.0, 3.0, 4.0, 5.0, 9.0];
#[test]
fn test_mean_mode_2d_corner_cascade() {
let a = Array::from_vec(STAT_INPUT.to_vec()).reshape(&[2, 3]);
let result = pad(&a, &[(1, 1), (1, 1)], "mean", None, None, None).expect("pad should succeed");
let expected = [
4.0, 2.5, 3.5, 6.0, 4.0, 2.0, 1.0, 2.0, 3.0, 2.0, 6.0, 4.0, 5.0, 9.0, 6.0, 4.0, 2.5, 3.5,
6.0, 4.0,
];
assert_eq!(result.shape(), vec![4, 5]);
assert_vec_close(&result.to_vec(), &expected, "mean_2d");
}
#[test]
fn test_maximum_mode_2d_corner_cascade() {
let a = Array::from_vec(STAT_INPUT.to_vec()).reshape(&[2, 3]);
let result =
pad(&a, &[(1, 1), (1, 1)], "maximum", None, None, None).expect("pad should succeed");
let expected = [
9.0, 4.0, 5.0, 9.0, 9.0, 3.0, 1.0, 2.0, 3.0, 3.0, 9.0, 4.0, 5.0, 9.0, 9.0, 9.0, 4.0, 5.0,
9.0, 9.0,
];
assert_vec_close(&result.to_vec(), &expected, "maximum_2d");
}
#[test]
fn test_median_mode_2d_corner_cascade() {
let a = Array::from_vec(STAT_INPUT.to_vec()).reshape(&[2, 3]);
let result =
pad(&a, &[(1, 1), (1, 1)], "median", None, None, None).expect("pad should succeed");
let expected = [
3.5, 2.5, 3.5, 6.0, 3.5, 2.0, 1.0, 2.0, 3.0, 2.0, 5.0, 4.0, 5.0, 9.0, 5.0, 3.5, 2.5, 3.5,
6.0, 3.5,
];
assert_vec_close(&result.to_vec(), &expected, "median_2d");
}
#[test]
fn test_minimum_mode_2d_corner_cascade() {
let a = Array::from_vec(STAT_INPUT.to_vec()).reshape(&[2, 3]);
let result =
pad(&a, &[(1, 1), (1, 1)], "minimum", None, None, None).expect("pad should succeed");
let expected = [
1.0, 1.0, 2.0, 3.0, 1.0, 1.0, 1.0, 2.0, 3.0, 1.0, 4.0, 4.0, 5.0, 9.0, 4.0, 1.0, 1.0, 2.0,
3.0, 1.0,
];
assert_vec_close(&result.to_vec(), &expected, "minimum_2d");
}
#[test]
fn test_stat_modes_propagate_nan_regardless_of_position() {
let a = Array::from_vec(vec![1.0, 5.0, f64::NAN, 3.0]);
let expected_nan_at = [true, false, false, true, false, true];
let expected_finite = [f64::NAN, 1.0, 5.0, f64::NAN, 3.0, f64::NAN];
for mode in ["maximum", "minimum", "median", "mean"] {
let result =
pad(&a, &[(1, 1)], mode, None, None, None).unwrap_or_else(|e| panic!("{mode}: {e}"));
let data = result.to_vec();
assert_eq!(data.len(), 6, "{mode}: length");
for (i, &v) in data.iter().enumerate() {
if expected_nan_at[i] {
assert!(v.is_nan(), "{mode} at index {i}: expected NaN, got {v}");
} else {
assert!(
(v - expected_finite[i]).abs() < EPS,
"{mode} at index {i}: expected {}, got {v}",
expected_finite[i]
);
}
}
}
}
#[test]
fn test_maximum_minimum_nan_not_only_when_first() {
let a = Array::from_vec(vec![1.0, 3.0, f64::NAN]);
for mode in ["maximum", "minimum"] {
let result =
pad(&a, &[(1, 1)], mode, None, None, None).unwrap_or_else(|e| panic!("{mode}: {e}"));
let data = result.to_vec();
assert_eq!(data.len(), 5, "{mode}: length");
assert!(
data[0].is_nan(),
"{mode} padding (before): expected NaN, got {}",
data[0]
);
assert!(
(data[1] - 1.0).abs() < EPS,
"{mode} original[0]: expected 1.0, got {}",
data[1]
);
assert!(
(data[2] - 3.0).abs() < EPS,
"{mode} original[1]: expected 3.0, got {}",
data[2]
);
assert!(
data[3].is_nan(),
"{mode} original[2] (NaN passthrough): expected NaN, got {}",
data[3]
);
assert!(
data[4].is_nan(),
"{mode} padding (after): expected NaN, got {}",
data[4]
);
}
}
#[test]
fn test_linear_ramp_1d_default_end_value_zero() {
let a = Array::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
let result = pad(&a, &[(2, 3)], "linear_ramp", None, None, None).expect("pad should succeed");
let expected = [
0.0,
0.5,
1.0,
2.0,
3.0,
4.0,
5.0,
3.3333333333333335,
1.6666666666666667,
0.0,
];
assert_vec_close(&result.to_vec(), &expected, "linear_ramp_1d");
}
#[test]
fn test_linear_ramp_1d_asymmetric_end_values() {
let a = Array::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
let result = pad(&a, &[(2, 3)], "linear_ramp", None, Some((5.0, -4.0)), None)
.expect("pad should succeed");
let expected = [5.0, 3.0, 1.0, 2.0, 3.0, 4.0, 5.0, 2.0, -1.0, -4.0];
assert_vec_close(&result.to_vec(), &expected, "linear_ramp_asymmetric");
}
#[test]
fn test_linear_ramp_2d() {
let a = Array::from_vec(vec![1.0, 2.0, 3.0, 4.0]).reshape(&[2, 2]);
let result = pad(
&a,
&[(1, 1), (2, 2)],
"linear_ramp",
None,
Some((0.0, 0.0)),
None,
)
.expect("pad should succeed");
let expected = [
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 1.0, 2.0, 1.0, 0.0, 0.0, 1.5, 3.0, 4.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
];
assert_eq!(result.shape(), vec![4, 6]);
assert_vec_close(&result.to_vec(), &expected, "linear_ramp_2d");
}
#[test]
fn test_reflect_even_basic() {
let a = Array::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
let result = pad(&a, &[(2, 3)], "reflect", None, None, None).expect("pad should succeed");
assert_vec_close(
&result.to_vec(),
&[3.0, 2.0, 1.0, 2.0, 3.0, 4.0, 5.0, 4.0, 3.0, 2.0],
"reflect_even",
);
}
#[test]
fn test_symmetric_even_basic() {
let a = Array::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
let result = pad(&a, &[(2, 3)], "symmetric", None, None, None).expect("pad should succeed");
assert_vec_close(
&result.to_vec(),
&[2.0, 1.0, 1.0, 2.0, 3.0, 4.0, 5.0, 5.0, 4.0, 3.0],
"symmetric_even",
);
}
#[test]
fn test_reflect_odd_multi_period() {
let a = Array::from_vec(vec![1.0, 2.0, 3.0]);
let result =
pad(&a, &[(7, 7)], "reflect", None, None, Some("odd")).expect("pad should succeed");
let expected: Vec<f64> = (-6..=10).map(|v| v as f64).collect();
assert_vec_close(&result.to_vec(), &expected, "reflect_odd_multiperiod");
}
#[test]
fn test_reflect_even_multi_period() {
let a = Array::from_vec(vec![1.0, 2.0, 3.0]);
let result =
pad(&a, &[(7, 7)], "reflect", None, None, Some("even")).expect("pad should succeed");
let expected = [
2.0, 3.0, 2.0, 1.0, 2.0, 3.0, 2.0, 1.0, 2.0, 3.0, 2.0, 1.0, 2.0, 3.0, 2.0, 1.0, 2.0,
];
assert_vec_close(&result.to_vec(), &expected, "reflect_even_multiperiod");
}
#[test]
fn test_symmetric_odd_multi_period() {
let a = Array::from_vec(vec![1.0, 2.0, 3.0]);
let result =
pad(&a, &[(7, 7)], "symmetric", None, None, Some("odd")).expect("pad should succeed");
let expected = [
-3.0, -3.0, -2.0, -1.0, -1.0, 0.0, 1.0, 1.0, 2.0, 3.0, 3.0, 4.0, 5.0, 5.0, 6.0, 7.0, 7.0,
];
assert_vec_close(&result.to_vec(), &expected, "symmetric_odd_multiperiod");
}
#[test]
fn test_symmetric_even_multi_period() {
let a = Array::from_vec(vec![1.0, 2.0, 3.0]);
let result = pad(&a, &[(7, 7)], "symmetric", None, None, None).expect("pad should succeed");
let expected = [
1.0, 1.0, 2.0, 3.0, 3.0, 2.0, 1.0, 1.0, 2.0, 3.0, 3.0, 2.0, 1.0, 1.0, 2.0, 3.0, 3.0,
];
assert_vec_close(&result.to_vec(), &expected, "symmetric_even_multiperiod");
}
#[test]
fn test_reflect_symmetric_axis_size_one_degenerates_to_edge() {
let a = Array::from_vec(vec![5.0]);
for (mode, reflect_type) in [
("reflect", None),
("symmetric", None),
("reflect", Some("odd")),
("symmetric", Some("odd")),
] {
let result = pad(&a, &[(3, 3)], mode, None, None, reflect_type).unwrap_or_else(|e| {
panic!("mode {mode} (reflect_type {reflect_type:?}) should succeed: {e}")
});
assert_vec_close(
&result.to_vec(),
&[5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0],
&format!("axis_size_one_{mode}_{reflect_type:?}"),
);
}
}
#[test]
fn test_reflect_type_only_validated_for_reflect_and_symmetric_modes() {
let a = Array::from_vec(vec![1, 2, 3]);
assert!(pad(
&a,
&[(1, 1)],
"constant",
None,
None,
Some("not_even_or_odd")
)
.is_ok());
assert!(pad(
&a,
&[(1, 1)],
"reflect",
None,
None,
Some("not_even_or_odd")
)
.is_err());
}
#[test]
fn test_wrap_basic() {
let a = Array::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
let result = pad(&a, &[(2, 3)], "wrap", None, None, None).expect("pad should succeed");
assert_vec_close(
&result.to_vec(),
&[4.0, 5.0, 1.0, 2.0, 3.0, 4.0, 5.0, 1.0, 2.0, 3.0],
"wrap_basic",
);
}
#[test]
fn test_wrap_before_multiple_of_axis_size() {
let a = Array::from_vec(vec![1.0, 2.0, 3.0]);
let result = pad(&a, &[(3, 0)], "wrap", None, None, None).expect("pad should succeed");
assert_vec_close(
&result.to_vec(),
&[1.0, 2.0, 3.0, 1.0, 2.0, 3.0],
"wrap_multiple",
);
}
#[test]
fn test_wrap_multi_period() {
let a = Array::from_vec(vec![1.0, 2.0, 3.0]);
let result = pad(&a, &[(7, 5)], "wrap", None, None, None).expect("pad should succeed");
let expected = [
3.0, 1.0, 2.0, 3.0, 1.0, 2.0, 3.0, 1.0, 2.0, 3.0, 1.0, 2.0, 3.0, 1.0, 2.0,
];
assert_vec_close(&result.to_vec(), &expected, "wrap_multiperiod");
}
#[test]
fn test_empty_mode_shape_and_original_preserved() {
let a = Array::from_vec(vec![1, 2, 3]);
let result = pad(&a, &[(2, 2)], "empty", None, None, None).expect("pad should succeed");
assert_eq!(result.shape(), vec![7]);
let data = result.to_vec();
assert_eq!(&data[2..5], &[1, 2, 3]);
}
#[test]
fn test_unknown_mode_is_rejected() {
let a = Array::from_vec(vec![1, 2, 3]);
assert!(pad(&a, &[(1, 1)], "not_a_real_mode", None, None, None).is_err());
}
#[test]
fn test_mismatched_pad_width_length_is_rejected() {
let a = Array::from_vec(vec![1, 2, 3, 4]).reshape(&[2, 2]);
assert!(pad(&a, &[(1, 1)], "constant", None, None, None).is_err());
}
#[test]
fn test_empty_axis_rejected_for_non_constant_non_empty_modes() {
let a: Array<f64> =
Array::from_vec_shape(vec![], &[0, 3]).expect("zero-size array should build");
assert!(pad(&a, &[(1, 0), (0, 0)], "edge", None, None, None).is_err());
assert!(pad(&a, &[(1, 0), (0, 0)], "constant", None, None, None).is_ok());
}