use crate::error::Error;
use crate::parallel_gates::{cheap_map_f64_parallel_threshold, scan_f64_parallel_min_elems};
use ndarray::{Array, ArrayBase, ArrayViewMut1, Axis, Data, Dimension};
use rayon::prelude::{IntoParallelRefMutIterator, ParallelIterator};
const NORM_CONSTANT_THRESHOLD: f64 = 10.0 * f64::EPSILON;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NormalizationAxis {
Row,
Column,
Global,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum NormalizationOrder {
L1,
L2,
Max,
Lp(f64),
}
pub fn normalize<S, D>(
data: &ArrayBase<S, D>,
axis: NormalizationAxis,
order: NormalizationOrder,
) -> Result<Array<f64, D>, Error>
where
S: Data<Elem = f64>,
D: Dimension,
{
if data.is_empty() {
return Err(Error::empty_input("Cannot normalize empty array"));
}
if data.iter().any(|&x| !x.is_finite()) {
return Err(Error::non_finite("input data"));
}
if matches!(order, NormalizationOrder::Lp(p) if p <= 0.0 || !p.is_finite()) {
return Err(Error::invalid_parameter(
"p",
"Lp norm parameter must be positive and finite",
));
}
let mut result = data.to_owned();
axis.apply(&mut result, order)?;
Ok(result)
}
fn normalize_lane(lane: &mut ArrayViewMut1<f64>, norm: f64) {
if norm >= NORM_CONSTANT_THRESHOLD {
lane.mapv_inplace(|x| x / norm);
}
}
fn normalize_global<D>(data: &mut Array<f64, D>, order: NormalizationOrder) -> Result<(), Error>
where
D: Dimension,
{
let norm = order.norm(data.iter().copied())?;
if norm >= NORM_CONSTANT_THRESHOLD {
if data.len() >= cheap_map_f64_parallel_threshold() {
data.par_mapv_inplace(|x| x / norm);
} else {
data.mapv_inplace(|x| x / norm);
}
}
Ok(())
}
fn normalize_lanes<D>(
data: &mut Array<f64, D>,
axis_from_end: usize,
order: NormalizationOrder,
operation_name: &str,
) -> Result<(), Error>
where
D: Dimension,
{
let ndim = data.ndim();
let data_len = data.len();
if ndim < 2 {
return Err(Error::invalid_input(format!(
"{} requires at least 2 dimensions",
operation_name
)));
}
let axis = Axis(ndim - axis_from_end);
let mut lanes: Vec<ArrayViewMut1<f64>> = data.lanes_mut(axis).into_iter().collect();
let process = |lane: &mut ArrayViewMut1<f64>| -> Result<(), Error> {
let norm = order.norm(lane.iter().copied())?;
normalize_lane(lane, norm);
Ok(())
};
if data_len >= scan_f64_parallel_min_elems() {
lanes.par_iter_mut().try_for_each(process)
} else {
lanes.iter_mut().try_for_each(process)
}
}
impl NormalizationOrder {
fn norm<I>(&self, values: I) -> Result<f64, Error>
where
I: Iterator<Item = f64>,
{
match *self {
NormalizationOrder::L1 => {
let norm: f64 = values.map(|x| x.abs()).sum();
if norm.is_finite() {
Ok(norm)
} else {
Err(Error::non_finite(
"L1 norm computation resulted in non-finite value",
))
}
}
NormalizationOrder::L2 => {
let norm_squared: f64 = values.map(|x| x * x).sum();
if norm_squared.is_finite() && norm_squared >= 0.0 {
Ok(norm_squared.sqrt())
} else {
Err(Error::non_finite(
"L2 norm computation resulted in non-finite value",
))
}
}
NormalizationOrder::Max => {
let norm = values.map(|x| x.abs()).fold(f64::NEG_INFINITY, f64::max);
if norm.is_finite() && norm >= 0.0 {
Ok(norm)
} else if norm == f64::NEG_INFINITY {
Ok(0.0)
} else {
Err(Error::non_finite(
"Max norm computation resulted in non-finite value",
))
}
}
NormalizationOrder::Lp(p) => {
let sum: f64 = values.map(|x| x.abs().powf(p)).sum();
if sum.is_finite() && sum >= 0.0 {
Ok(sum.powf(1.0 / p))
} else {
Err(Error::non_finite(format!(
"Lp norm (p={}) computation resulted in non-finite value",
p
)))
}
}
}
}
}
impl NormalizationAxis {
fn apply<D>(&self, data: &mut Array<f64, D>, order: NormalizationOrder) -> Result<(), Error>
where
D: Dimension,
{
match self {
NormalizationAxis::Global => normalize_global(data, order),
NormalizationAxis::Row => normalize_lanes(data, 1, order, "Row normalization"),
NormalizationAxis::Column => normalize_lanes(data, 2, order, "Column normalization"),
}
}
}