use std::cmp::Ordering;
use crate::Axis;
#[doc(hidden)]
pub trait WideningCastFrom<Src>: Sized {
fn widening_cast_from(src: Src) -> Self;
}
impl<T> WideningCastFrom<T> for T {
#[inline(always)]
fn widening_cast_from(src: T) -> Self {
src
}
}
macro_rules! impl_az_widening_casts {
($src:ty => $($dst:ty),+ $(,)?) => {
$(
impl $crate::dist::WideningCastFrom<$src> for $dst {
#[inline(always)]
fn widening_cast_from(src: $src) -> Self {
<$dst as az::CastFrom<$src>>::cast_from(src)
}
}
)+
};
}
impl_az_widening_casts!(f32 => f64, u8, u16, u32);
impl_az_widening_casts!(f64 => f32, u8, u16, u32);
impl_az_widening_casts!(u8 => f32, f64, u16, u32);
impl_az_widening_casts!(u16 => f32, f64, u8, u32);
impl_az_widening_casts!(u32 => f32, f64, u8, u16);
#[cfg(feature = "fixed")]
mod fixed_widening_casts {
use fixed::{
types::extra::{U0, U16, U8},
FixedI32, FixedU16,
};
type FixedI32U16 = FixedI32<U16>;
type FixedI32U0 = FixedI32<U0>;
type FixedU16U8 = FixedU16<U8>;
impl_az_widening_casts!(f32 => FixedI32U16, FixedI32U0, FixedU16U8);
impl_az_widening_casts!(f64 => FixedI32U16, FixedI32U0, FixedU16U8);
impl_az_widening_casts!(u8 => FixedI32U16, FixedI32U0, FixedU16U8);
impl_az_widening_casts!(u16 => FixedI32U16, FixedI32U0, FixedU16U8);
impl_az_widening_casts!(u32 => FixedI32U16, FixedI32U0, FixedU16U8);
impl_az_widening_casts!(FixedI32U16 => f32, f64, u8, u16, u32, FixedI32U0, FixedU16U8);
impl_az_widening_casts!(FixedI32U0 => f32, f64, u8, u16, u32, FixedI32U16, FixedU16U8);
impl_az_widening_casts!(FixedU16U8 => f32, f64, u8, u16, u32, FixedI32U16, FixedI32U0);
#[cfg(feature = "f16")]
mod f16 {
use super::{FixedI32U0, FixedI32U16, FixedU16U8};
use half::f16;
impl_az_widening_casts!(f16 => FixedI32U16, FixedI32U0, FixedU16U8);
impl_az_widening_casts!(FixedI32U16 => f16);
impl_az_widening_casts!(FixedI32U0 => f16);
impl_az_widening_casts!(FixedU16U8 => f16);
}
}
#[cfg(feature = "f16")]
mod f16_widening_casts {
use half::f16;
use num_traits::AsPrimitive;
use super::WideningCastFrom;
macro_rules! impl_f16_widening_casts {
($src:ty => $($dst:ty),+ $(,)?) => {
$(
impl WideningCastFrom<$src> for $dst {
#[inline(always)]
fn widening_cast_from(src: $src) -> Self {
<$src as AsPrimitive<$dst>>::as_(src)
}
}
)+
};
}
impl_f16_widening_casts!(f16 => f32, f64, u8, u16, u32);
impl_f16_widening_casts!(f32 => f16);
impl_f16_widening_casts!(f64 => f16);
impl_f16_widening_casts!(u8 => f16);
impl_f16_widening_casts!(u16 => f16);
impl_f16_widening_casts!(u32 => f16);
}
pub trait DistanceMetricScalar<A: Copy> {
type Output: Axis<Coord = Self::Output>;
fn widen_coord(a: A) -> Self::Output;
#[inline(always)]
fn widen_axis(axis: &[A], out: &mut [Self::Output]) {
assert!(out.len() >= axis.len());
for (dst, &src) in out.iter_mut().zip(axis.iter()) {
*dst = Self::widen_coord(src);
}
}
fn dist1(a: Self::Output, b: Self::Output) -> Self::Output;
#[inline(always)]
fn combine_component(acc: &mut Self::Output, component: Self::Output) {
*acc += component;
}
#[inline(always)]
fn dist1_raw(a: A, b: A) -> Self::Output {
Self::dist1(Self::widen_coord(a), Self::widen_coord(b))
}
#[inline(always)]
fn dist<const K: usize>(a: &[Self::Output; K], b: &[Self::Output; K]) -> Self::Output {
let mut acc = Self::Output::zero();
for dim in 0..K {
Self::combine_component(&mut acc, Self::dist1(a[dim], b[dim]));
}
acc
}
#[inline(always)]
fn dist_raw<const K: usize>(a: &[A; K], b: &[A; K]) -> Self::Output {
let mut acc = Self::Output::zero();
for dim in 0..K {
Self::combine_component(&mut acc, Self::dist1_raw(a[dim], b[dim]));
}
acc
}
#[inline(always)]
fn rect_dist_from_off<const K: usize>(off: &[Self::Output; K]) -> Self::Output {
let mut acc = Self::Output::zero();
for off_val in off.iter().copied() {
Self::combine_component(&mut acc, Self::dist1(off_val, Self::Output::zero()));
}
acc
}
#[inline(always)]
fn rect_dist_after_update<const K: usize>(
rd: Self::Output,
off: &[Self::Output; K],
dim: usize,
new_off: Self::Output,
) -> Self::Output {
let new_dist1 = Self::dist1(new_off, Self::Output::zero());
let old_dist1 = Self::dist1(off[dim], Self::Output::zero());
Self::Output::saturating_add(rd - old_dist1, new_dist1)
}
#[inline(always)]
fn rect_dist_after_axis_update(
rd: Self::Output,
old_off: Self::Output,
new_off: Self::Output,
) -> Self::Output {
let new_dist1 = Self::dist1(new_off, Self::Output::zero());
let old_dist1 = Self::dist1(old_off, Self::Output::zero());
Self::Output::saturating_add(rd - old_dist1, new_dist1)
}
#[inline(always)]
fn cmp(a: Self::Output, b: Self::Output) -> Ordering {
a.partial_cmp(&b).unwrap_or(Ordering::Equal)
}
}
#[cfg(test)]
mod tests {
use super::DistanceMetricScalar;
use std::cmp::Ordering;
struct DummyLessMetric;
impl DistanceMetricScalar<i16> for DummyLessMetric {
type Output = f64;
fn widen_coord(a: i16) -> Self::Output {
a as f64
}
fn dist1(a: Self::Output, b: Self::Output) -> Self::Output {
(a - b).abs()
}
}
#[test]
fn default_widen_axis_bulk_widens() {
let axis = [1i16, -2, 7];
let mut out = [0.0f64; 3];
DummyLessMetric::widen_axis(&axis, &mut out);
assert_eq!(out, [1.0, -2.0, 7.0]);
}
#[test]
fn default_cmp_uses_partial_cmp() {
assert_eq!(DummyLessMetric::cmp(2.0, 5.0), Ordering::Less);
assert_eq!(DummyLessMetric::cmp(5.0, 2.0), Ordering::Greater);
assert_eq!(DummyLessMetric::cmp(3.0, 3.0), Ordering::Equal);
}
}