use crate::ndtri::{CLIP_MAX, CLIP_MIN, ndtri};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutputDistribution {
Uniform,
Normal,
}
fn np_interp(x: f64, xp: &[f64], fp: &[f64]) -> f64 {
debug_assert_eq!(xp.len(), fp.len());
let n = xp.len();
if x <= xp[0] {
return fp[0];
}
if x >= xp[n - 1] {
return fp[n - 1];
}
let idx = xp.partition_point(|&v| v <= x);
let i = idx - 1;
let slope = (fp[i + 1] - fp[i]) / (xp[i + 1] - xp[i]);
slope.mul_add(x - xp[i], fp[i])
}
pub fn transform_col(
col: &mut [f64],
quantiles: &[f64],
references: &[f64],
dist: OutputDistribution,
) {
let lower_bound_x = quantiles[0];
let upper_bound_x = quantiles[quantiles.len() - 1];
let q_rev: Vec<f64> = quantiles.iter().rev().map(|&v| -v).collect();
let r_rev: Vec<f64> = references.iter().rev().map(|&v| -v).collect();
for v in col.iter_mut() {
if v.is_nan() {
continue;
}
let x = *v;
let at_lower = x == lower_bound_x;
let at_upper = x == upper_bound_x;
let fwd = np_interp(x, quantiles, references);
let rev = -np_interp(-x, &q_rev, &r_rev);
let mut y = 0.5 * (fwd + rev);
if at_upper {
y = 1.0;
}
if at_lower {
y = 0.0;
}
if dist == OutputDistribution::Normal {
y = ndtri(y).clamp(CLIP_MIN, CLIP_MAX);
}
*v = y;
}
}
pub fn transform_matrix(
data: &mut [f64],
n_rows: usize,
n_cols: usize,
quantiles_per_col: &[Vec<f64>],
references: &[f64],
dist: OutputDistribution,
) {
for j in 0..n_cols {
let mut col: Vec<f64> = (0..n_rows).map(|i| data[i * n_cols + j]).collect();
transform_col(&mut col, &quantiles_per_col[j], references, dist);
for (i, v) in col.into_iter().enumerate() {
data[i * n_cols + j] = v;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn close(a: f64, b: f64) {
assert!(
(a - b).abs() < 1e-12,
"got={a} want={b} diff={}",
(a - b).abs()
);
}
#[test]
fn np_interp_basic() {
let xp = [0.0, 1.0, 2.0];
let fp = [0.0, 0.5, 1.0];
close(np_interp(0.5, &xp, &fp), 0.25);
close(np_interp(0.0, &xp, &fp), 0.0);
close(np_interp(2.0, &xp, &fp), 1.0);
close(np_interp(-1.0, &xp, &fp), 0.0); close(np_interp(3.0, &xp, &fp), 1.0); }
#[test]
fn uniform_ties_average() {
let quantiles = [1.0, 2.0, 2.0, 2.0, 3.0];
let refs = [0.0, 0.25, 0.5, 0.75, 1.0];
let mut col = [2.0];
transform_col(&mut col, &quantiles, &refs, OutputDistribution::Uniform);
close(col[0], 0.5);
}
#[test]
fn boundary_forced_to_exact() {
let quantiles = [1.0, 2.0, 3.0];
let refs = [0.0, 0.5, 1.0];
let mut col = [1.0, 3.0];
transform_col(&mut col, &quantiles, &refs, OutputDistribution::Uniform);
assert_eq!(col[0], 0.0);
assert_eq!(col[1], 1.0);
}
}