use std::fmt::Display;
use num_traits::{Float, FromPrimitive};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Dimension {
MZ(MZ),
Mass(Mass),
Time(Time),
IonMobility(IonMobility),
Dimensionless(Dimensionless),
}
macro_rules! dim_dispatch {
($d:ident, $f:tt) => {
match $d {
Dimension::MZ(_) => <MZ as CoordinateSystem>::$f(),
Dimension::Mass(_) => <Mass as CoordinateSystem>::$f(),
Dimension::Time(_) => <Time as CoordinateSystem>::$f(),
Dimension::IonMobility(_) => <IonMobility as CoordinateSystem>::$f(),
Dimension::Dimensionless(_) => <Dimensionless as CoordinateSystem>::$f(),
}
};
}
impl Dimension {
pub const fn name(&self) -> &'static str {
match self {
Dimension::MZ(_) => "m/z",
Dimension::Mass(_) => "neutral mass",
Dimension::Time(_) => "time",
Dimension::IonMobility(_) => "ion mobility",
Dimension::Dimensionless(_) => "",
}
}
pub fn minimum_value(&self) -> f64 {
dim_dispatch!(self, minimum_value)
}
pub fn maximum_value(&self) -> f64 {
dim_dispatch!(self, maximum_value)
}
}
impl Display for Dimension {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name())
}
}
#[derive(Default, Debug, Clone, Copy, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct MZ();
impl MZ {
#[inline]
pub fn coordinate<T: CoordinateLike<MZ>>(inst: &T) -> f64 {
CoordinateLike::<MZ>::coordinate(inst)
}
}
#[derive(Default, Debug, Clone, Copy, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Mass();
impl Mass {
#[inline]
pub fn coordinate<T: CoordinateLike<Mass>>(inst: &T) -> f64 {
CoordinateLike::<Mass>::coordinate(inst)
}
}
#[derive(Default, Debug, Clone, Copy, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Time();
impl Time {
#[inline]
pub fn coordinate<T: CoordinateLike<Time>>(inst: &T) -> f64 {
CoordinateLike::<Time>::coordinate(inst)
}
}
#[derive(Default, Debug, Clone, Copy, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct IonMobility();
impl IonMobility {
#[inline]
pub fn coordinate<T: CoordinateLike<IonMobility>>(inst: &T) -> f64 {
CoordinateLike::<IonMobility>::coordinate(inst)
}
}
#[derive(Default, Debug, Clone, Copy, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Dimensionless();
#[allow(unused)]
impl Dimensionless {
#[inline]
pub fn coordinate<T: CoordinateLike<Dimensionless>>(inst: &T) -> f64 {
CoordinateLike::<Dimensionless>::coordinate(inst)
}
}
pub trait CoordinateSystem: Sized {
#[inline]
fn coordinate<T: CoordinateLike<Self>>(inst: &T) -> f64 {
CoordinateLike::<Self>::coordinate(inst)
}
fn coordinate_mut<T: CoordinateLikeMut<Self>>(inst: &mut T) -> &mut f64 {
CoordinateLikeMut::<Self>::coordinate_mut(inst)
}
fn name_of(&self) -> &'static str {
Self::name()
}
fn dimension() -> Dimension;
fn name() -> &'static str {
Self::dimension().name()
}
fn minimum_value() -> f64 {
0.0
}
fn maximum_value() -> f64 {
f64::INFINITY
}
}
impl CoordinateSystem for MZ {
fn dimension() -> Dimension {
Dimension::MZ(Self())
}
}
impl CoordinateSystem for Mass {
fn dimension() -> Dimension {
Dimension::Mass(Self())
}
}
impl CoordinateSystem for Time {
fn dimension() -> Dimension {
Dimension::Time(Self())
}
}
impl CoordinateSystem for IonMobility {
fn dimension() -> Dimension {
Dimension::IonMobility(Self())
}
}
impl CoordinateSystem for Dimensionless {
fn dimension() -> Dimension {
Dimension::Dimensionless(Self())
}
}
pub trait CoordinateLike<T>: PartialOrd {
fn coordinate(&self) -> f64;
}
pub trait CoordinateLikeMut<T>: CoordinateLike<T> {
fn coordinate_mut(&mut self) -> &mut f64;
}
pub trait MassLocated: CoordinateLike<Mass> {
#[inline]
fn neutral_mass(&self) -> f64 {
CoordinateLike::<Mass>::coordinate(self)
}
}
pub trait MZLocated: CoordinateLike<MZ> {
#[inline]
fn mz(&self) -> f64 {
CoordinateLike::<MZ>::coordinate(self)
}
}
pub trait TimeLocated: CoordinateLike<Time> {
#[inline]
fn time(&self) -> f64 {
CoordinateLike::<Time>::coordinate(self)
}
}
pub trait IonMobilityLocated: CoordinateLike<IonMobility> {
#[inline]
fn ion_mobility(&self) -> f64 {
CoordinateLike::<IonMobility>::coordinate(self)
}
}
impl<T: CoordinateLike<C>, C> CoordinateLike<C> for &T {
fn coordinate(&self) -> f64 {
(*self).coordinate()
}
}
impl<T: CoordinateLike<C>, C> CoordinateLike<C> for &mut T {
fn coordinate(&self) -> f64 {
CoordinateLike::<C>::coordinate(*self)
}
}
impl<T: CoordinateLikeMut<C>, C> CoordinateLikeMut<C> for &mut T {
fn coordinate_mut(&mut self) -> &mut f64 {
CoordinateLikeMut::<C>::coordinate_mut(*self)
}
}
impl<T: CoordinateLike<Mass>> MassLocated for T {}
impl<T: CoordinateLike<MZ>> MZLocated for T {}
impl<T: CoordinateLike<Time>> TimeLocated for T {}
impl<T: CoordinateLike<IonMobility>> IonMobilityLocated for T {}
pub type IndexType = u32;
pub trait IndexedCoordinate<T>: CoordinateLike<T> {
fn get_index(&self) -> IndexType;
fn set_index(&mut self, index: IndexType);
}
impl<T: IndexedCoordinate<C>, C> IndexedCoordinate<C> for &T {
fn get_index(&self) -> IndexType {
(*self).get_index()
}
fn set_index(&mut self, _index: IndexType) {}
}
impl<T: IndexedCoordinate<C>, C> IndexedCoordinate<C> for &mut T {
fn get_index(&self) -> IndexType {
(**self).get_index()
}
fn set_index(&mut self, index: IndexType) {
(**self).set_index(index)
}
}
pub(crate) fn _isclose<T>(x: T, y: T, rtol: T, atol: T) -> bool
where
T: Float,
{
(x - y).abs() <= (atol + rtol * y.abs())
}
pub(crate) fn isclose<T>(x: T, y: T) -> bool
where
T: Float + FromPrimitive,
{
_isclose(x, y, T::from_f64(1e-5).unwrap(), T::from_f64(1e-8).unwrap())
}
pub trait HasProximity : PartialEq + PartialOrd + Copy {
fn is_close(&self, other: &Self) -> bool {
self == other
}
}
macro_rules! impl_has_proximity {
($t:ty) => {
impl $crate::coordinate::HasProximity for $t {
fn is_close(&self, other: &Self) -> bool {
isclose(*self, *other)
}
}
};
}
impl_has_proximity!(f32);
impl_has_proximity!(f64);
macro_rules! impl_has_proximity_exact {
($t:ty) => {
impl $crate::coordinate::HasProximity for $t {
fn is_close(&self, other: &Self) -> bool {
self == other
}
}
};
}
impl_has_proximity_exact!(i8);
impl_has_proximity_exact!(i16);
impl_has_proximity_exact!(i32);
impl_has_proximity_exact!(i64);
impl<T: HasProximity> HasProximity for Option<T> {
fn is_close(&self, other: &Self) -> bool {
match (self, other) {
(Some(x), Some(y)) => x.is_close(y),
_ => false,
}
}
}
impl HasProximity for u8 {}
impl HasProximity for u16 {}
impl HasProximity for u32 {}
impl HasProximity for u64 {}
impl HasProximity for usize {}
impl HasProximity for isize {}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_is_close() {
assert!(0.0.is_close(&0.0));
assert!(Some(0.0).is_close(&Some(0.0)));
assert!(!Some(0.0).is_close(&None));
assert!(5.is_close(&5));
}
#[test]
fn test_axes() {
let dims = [Dimension::MZ(MZ()), Dimension::Mass(Mass()), Dimension::Time(Time()), Dimension::IonMobility(IonMobility()), Dimension::Dimensionless(Dimensionless())];
for dim in dims {
match dim {
Dimension::MZ(x) => {
assert_eq!(x.name_of(), "m/z");
assert_eq!(dim.to_string(), "m/z");
assert_eq!(dim.minimum_value(), 0.0);
assert_eq!(dim.maximum_value(), f64::INFINITY);
},
Dimension::Mass(x) => {
assert_eq!(x.name_of(), "neutral mass");
assert_eq!(dim.to_string(), "neutral mass");
assert_eq!(dim.minimum_value(), 0.0);
assert_eq!(dim.maximum_value(), f64::INFINITY);
},
Dimension::IonMobility(x) => {
assert_eq!(x.name_of(), "ion mobility");
assert_eq!(dim.to_string(), "ion mobility");
assert_eq!(dim.minimum_value(), 0.0);
assert_eq!(dim.maximum_value(), f64::INFINITY);
}
Dimension::Time(x) => {
assert_eq!(x.name_of(), "time");
assert_eq!(dim.to_string(), "time");
assert_eq!(dim.minimum_value(), 0.0);
assert_eq!(dim.maximum_value(), f64::INFINITY);
}
Dimension::Dimensionless(x) => {
assert_eq!(x.name_of(), "");
assert_eq!(dim.to_string(), "");
assert_eq!(dim.minimum_value(), 0.0);
assert_eq!(dim.maximum_value(), f64::INFINITY);
}
}
}
}
}