use ferrolearn_core::traits::Transform;
use ferrolearn_preprocess::FunctionTransformer;
use ndarray::{Array2, array};
#[test]
#[allow(
clippy::approx_constant,
reason = "exact live-sklearn-1.5.2 oracle value ln(2)=log1p(1); R-CHAR-3, not the std constant"
)]
fn guard_log1p_matches_sklearn_oracle() {
let expected: [[f64; 2]; 2] = [
[0.0, 0.693_147_180_559_945_3],
[1.098_612_288_668_109_6, 1.386_294_361_119_890_6],
];
let ft = FunctionTransformer::<f64>::new(|v: f64| v.ln_1p());
let x = array![[0.0, 1.0], [2.0, 3.0]];
let out = ft.transform(&x).unwrap();
assert_eq!(out.shape(), x.shape(), "shape must be preserved");
for ((r, c), &want) in expected
.iter()
.enumerate()
.flat_map(|(i, row)| row.iter().enumerate().map(move |(j, v)| ((i, j), v)))
{
assert_eq!(
out[[r, c]].to_bits(),
want.to_bits(),
"log1p mismatch at [{r},{c}]: ferro={} sklearn={}",
out[[r, c]],
want
);
}
}
#[test]
fn guard_expm1_matches_sklearn_oracle() {
let expected: [[f64; 2]; 2] = [
[0.0, 1.718_281_828_459_045],
[6.389_056_098_930_65, 19.085_536_923_187_668],
];
let ft = FunctionTransformer::<f64>::new(|v: f64| v.exp_m1());
let x = array![[0.0, 1.0], [2.0, 3.0]];
let out = ft.transform(&x).unwrap();
for ((r, c), &want) in expected
.iter()
.enumerate()
.flat_map(|(i, row)| row.iter().enumerate().map(move |(j, v)| ((i, j), v)))
{
assert_eq!(
out[[r, c]].to_bits(),
want.to_bits(),
"expm1 mismatch at [{r},{c}]: ferro={} sklearn={}",
out[[r, c]],
want
);
}
}
#[test]
fn guard_sqrt_matches_sklearn_oracle() {
let expected: [[f64; 2]; 2] = [[1.0, 2.0], [3.0, 4.0]];
let ft = FunctionTransformer::<f64>::new(|v: f64| v.sqrt());
let x = array![[1.0, 4.0], [9.0, 16.0]];
let out = ft.transform(&x).unwrap();
for ((r, c), &want) in expected
.iter()
.enumerate()
.flat_map(|(i, row)| row.iter().enumerate().map(move |(j, v)| ((i, j), v)))
{
assert_eq!(
out[[r, c]].to_bits(),
want.to_bits(),
"sqrt mismatch at [{r},{c}]: ferro={} sklearn={}",
out[[r, c]],
want
);
}
}
#[test]
#[allow(
clippy::approx_constant,
reason = "exact live-sklearn-1.5.2 oracle value ln(2)=log(2); R-CHAR-3, not the std constant"
)]
fn guard_log_nan_inf_propagation_matches_sklearn_oracle() {
let ft = FunctionTransformer::<f64>::new(|v: f64| v.ln());
let x = array![[0.0, 1.0], [-1.0, 2.0]];
let out = ft.transform(&x).unwrap();
assert!(
out[[0, 0]].is_infinite() && out[[0, 0]].is_sign_negative(),
"expected -inf at [0,0], got {}",
out[[0, 0]]
);
assert_eq!(out[[0, 1]].to_bits(), 0.0_f64.to_bits());
assert!(
out[[1, 0]].is_nan(),
"expected NaN at [1,0], got {}",
out[[1, 0]]
);
assert_eq!(
out[[1, 1]].to_bits(),
0.693_147_180_559_945_3_f64.to_bits(),
"ln(2) mismatch: {}",
out[[1, 1]]
);
}
#[test]
fn guard_empty_matrix_shape_matches_sklearn_oracle() {
let expected_shape = [0_usize, 3];
let ft = FunctionTransformer::<f64>::new(|v: f64| v.ln_1p());
let x: Array2<f64> = Array2::zeros((0, 3));
let out = ft.transform(&x).unwrap();
assert_eq!(out.shape(), &expected_shape);
}