mzpeaks/coordinate/
dim.rs

1use std::fmt::Display;
2
3use num_traits::{Float, FromPrimitive};
4
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7
8
9/// An enum over the different coordinate planes
10#[derive(Debug, PartialEq, Clone, Copy)]
11#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
12pub enum Dimension {
13    MZ(MZ),
14    Mass(Mass),
15    Time(Time),
16    IonMobility(IonMobility),
17    Dimensionless(Dimensionless),
18}
19
20macro_rules! dim_dispatch {
21    ($d:ident, $f:tt) => {
22        match $d {
23            Dimension::MZ(_) => <MZ as CoordinateSystem>::$f(),
24            Dimension::Mass(_) => <Mass as CoordinateSystem>::$f(),
25            Dimension::Time(_) => <Time as CoordinateSystem>::$f(),
26            Dimension::IonMobility(_) => <IonMobility as CoordinateSystem>::$f(),
27            Dimension::Dimensionless(_) => <Dimensionless as CoordinateSystem>::$f(),
28        }
29    };
30}
31
32impl Dimension {
33    pub const fn name(&self) -> &'static str {
34        match self {
35            Dimension::MZ(_) => "m/z",
36            Dimension::Mass(_) => "neutral mass",
37            Dimension::Time(_) => "time",
38            Dimension::IonMobility(_) => "ion mobility",
39            Dimension::Dimensionless(_) => "",
40        }
41    }
42
43    pub fn minimum_value(&self) -> f64 {
44        dim_dispatch!(self, minimum_value)
45    }
46
47    pub fn maximum_value(&self) -> f64 {
48        dim_dispatch!(self, maximum_value)
49    }
50}
51
52impl Display for Dimension {
53    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54        write!(f, "{}", self.name())
55    }
56}
57
58#[derive(Default, Debug, Clone, Copy, PartialEq, PartialOrd)]
59#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
60/// The Mass To Charge Ratio (m/z) coordinate system
61pub struct MZ();
62
63impl MZ {
64    /// Access the m/z of the coordinate type
65    #[inline]
66    pub fn coordinate<T: CoordinateLike<MZ>>(inst: &T) -> f64 {
67        CoordinateLike::<MZ>::coordinate(inst)
68    }
69}
70
71#[derive(Default, Debug, Clone, Copy, PartialEq, PartialOrd)]
72#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
73/// The Mass coordinate system
74pub struct Mass();
75
76impl Mass {
77    /// Access the neutral mass of the coordinate type
78    #[inline]
79    pub fn coordinate<T: CoordinateLike<Mass>>(inst: &T) -> f64 {
80        CoordinateLike::<Mass>::coordinate(inst)
81    }
82}
83
84#[derive(Default, Debug, Clone, Copy, PartialEq, PartialOrd)]
85#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
86/// The Event Time coordinate system
87pub struct Time();
88impl Time {
89    /// Access the elapsed time of the coordinate type
90    #[inline]
91    pub fn coordinate<T: CoordinateLike<Time>>(inst: &T) -> f64 {
92        CoordinateLike::<Time>::coordinate(inst)
93    }
94}
95
96#[derive(Default, Debug, Clone, Copy, PartialEq, PartialOrd)]
97#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
98/// The Ion Mobility Time coordinate system
99pub struct IonMobility();
100impl IonMobility {
101    /// Access the ion mobility time unit of the coordinate type
102    #[inline]
103    pub fn coordinate<T: CoordinateLike<IonMobility>>(inst: &T) -> f64 {
104        CoordinateLike::<IonMobility>::coordinate(inst)
105    }
106}
107
108#[derive(Default, Debug, Clone, Copy, PartialEq, PartialOrd)]
109#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
110pub struct Dimensionless();
111
112#[allow(unused)]
113impl Dimensionless {
114    /// Access some unitless position coordinate type
115    #[inline]
116    pub fn coordinate<T: CoordinateLike<Dimensionless>>(inst: &T) -> f64 {
117        CoordinateLike::<Dimensionless>::coordinate(inst)
118    }
119}
120
121/// Describe a coordinate system as an object itself rather than as a type parameter
122pub trait CoordinateSystem: Sized {
123    #[inline]
124    fn coordinate<T: CoordinateLike<Self>>(inst: &T) -> f64 {
125        CoordinateLike::<Self>::coordinate(inst)
126    }
127
128    fn coordinate_mut<T: CoordinateLikeMut<Self>>(inst: &mut T) -> &mut f64 {
129        CoordinateLikeMut::<Self>::coordinate_mut(inst)
130    }
131
132    fn name_of(&self) -> &'static str {
133        Self::name()
134    }
135
136    fn dimension() -> Dimension;
137
138    fn name() -> &'static str {
139        Self::dimension().name()
140    }
141
142    fn minimum_value() -> f64 {
143        0.0
144    }
145
146    fn maximum_value() -> f64 {
147        f64::INFINITY
148    }
149}
150
151impl CoordinateSystem for MZ {
152    fn dimension() -> Dimension {
153        Dimension::MZ(Self())
154    }
155}
156impl CoordinateSystem for Mass {
157    fn dimension() -> Dimension {
158        Dimension::Mass(Self())
159    }
160}
161impl CoordinateSystem for Time {
162    fn dimension() -> Dimension {
163        Dimension::Time(Self())
164    }
165}
166impl CoordinateSystem for IonMobility {
167    fn dimension() -> Dimension {
168        Dimension::IonMobility(Self())
169    }
170}
171impl CoordinateSystem for Dimensionless {
172    fn dimension() -> Dimension {
173        Dimension::Dimensionless(Self())
174    }
175}
176
177/// Denote a type has a coordinate value on coordinate system `T`
178pub trait CoordinateLike<T>: PartialOrd {
179    /// The trait method for accessing the coordinate of the object on coordinate
180    /// system `T`
181    fn coordinate(&self) -> f64;
182}
183
184/// A [`CoordinateLike`] structure whose coordinate is mutable
185pub trait CoordinateLikeMut<T>: CoordinateLike<T> {
186    fn coordinate_mut(&mut self) -> &mut f64;
187}
188
189/// A named coordinate system membership for neutral mass
190pub trait MassLocated: CoordinateLike<Mass> {
191    #[inline]
192    fn neutral_mass(&self) -> f64 {
193        CoordinateLike::<Mass>::coordinate(self)
194    }
195}
196
197/// A named coordinate system membership for m/z
198pub trait MZLocated: CoordinateLike<MZ> {
199    #[inline]
200    fn mz(&self) -> f64 {
201        CoordinateLike::<MZ>::coordinate(self)
202    }
203}
204
205pub trait TimeLocated: CoordinateLike<Time> {
206    #[inline]
207    fn time(&self) -> f64 {
208        CoordinateLike::<Time>::coordinate(self)
209    }
210}
211
212pub trait IonMobilityLocated: CoordinateLike<IonMobility> {
213    #[inline]
214    fn ion_mobility(&self) -> f64 {
215        CoordinateLike::<IonMobility>::coordinate(self)
216    }
217}
218
219impl<T: CoordinateLike<C>, C> CoordinateLike<C> for &T {
220    fn coordinate(&self) -> f64 {
221        (*self).coordinate()
222    }
223}
224
225impl<T: CoordinateLike<C>, C> CoordinateLike<C> for &mut T {
226    fn coordinate(&self) -> f64 {
227        CoordinateLike::<C>::coordinate(*self)
228    }
229}
230
231impl<T: CoordinateLikeMut<C>, C> CoordinateLikeMut<C> for &mut T {
232    fn coordinate_mut(&mut self) -> &mut f64 {
233        CoordinateLikeMut::<C>::coordinate_mut(*self)
234    }
235}
236
237impl<T: CoordinateLike<Mass>> MassLocated for T {}
238impl<T: CoordinateLike<MZ>> MZLocated for T {}
239
240impl<T: CoordinateLike<Time>> TimeLocated for T {}
241impl<T: CoordinateLike<IonMobility>> IonMobilityLocated for T {}
242
243/// A type alias for the index in an [`IndexedCoordinate`] structure
244pub type IndexType = u32;
245
246/// Indicate that an object may be indexed by coordinate system `T`
247pub trait IndexedCoordinate<T>: CoordinateLike<T> {
248    fn get_index(&self) -> IndexType;
249    fn set_index(&mut self, index: IndexType);
250}
251
252impl<T: IndexedCoordinate<C>, C> IndexedCoordinate<C> for &T {
253    fn get_index(&self) -> IndexType {
254        (*self).get_index()
255    }
256
257    fn set_index(&mut self, _index: IndexType) {}
258}
259
260impl<T: IndexedCoordinate<C>, C> IndexedCoordinate<C> for &mut T {
261    fn get_index(&self) -> IndexType {
262        (**self).get_index()
263    }
264
265    fn set_index(&mut self, index: IndexType) {
266        (**self).set_index(index)
267    }
268}
269
270pub(crate) fn _isclose<T>(x: T, y: T, rtol: T, atol: T) -> bool
271where
272    T: Float,
273{
274    (x - y).abs() <= (atol + rtol * y.abs())
275}
276
277pub(crate) fn isclose<T>(x: T, y: T) -> bool
278where
279    T: Float + FromPrimitive,
280{
281    _isclose(x, y, T::from_f64(1e-5).unwrap(), T::from_f64(1e-8).unwrap())
282}
283
284pub trait HasProximity : PartialEq + PartialOrd + Copy {
285    fn is_close(&self, other: &Self) -> bool {
286        self == other
287    }
288}
289
290macro_rules! impl_has_proximity {
291    ($t:ty) => {
292        impl $crate::coordinate::HasProximity for $t {
293            fn is_close(&self, other: &Self) -> bool {
294                isclose(*self, *other)
295            }
296        }
297    };
298}
299
300impl_has_proximity!(f32);
301impl_has_proximity!(f64);
302
303macro_rules! impl_has_proximity_exact {
304    ($t:ty) => {
305        impl $crate::coordinate::HasProximity for $t {
306            fn is_close(&self, other: &Self) -> bool {
307                self == other
308            }
309        }
310    };
311}
312
313impl_has_proximity_exact!(i8);
314impl_has_proximity_exact!(i16);
315impl_has_proximity_exact!(i32);
316impl_has_proximity_exact!(i64);
317
318impl<T: HasProximity> HasProximity for Option<T> {
319    fn is_close(&self, other: &Self) -> bool {
320        match (self, other) {
321            (Some(x), Some(y)) => x.is_close(y),
322            _ => false,
323        }
324    }
325}
326
327impl HasProximity for u8 {}
328impl HasProximity for u16 {}
329impl HasProximity for u32 {}
330impl HasProximity for u64 {}
331
332impl HasProximity for usize {}
333impl HasProximity for isize {}
334
335
336#[cfg(test)]
337mod test {
338    use super::*;
339
340    #[test]
341    fn test_is_close() {
342        assert!(0.0.is_close(&0.0));
343        assert!(Some(0.0).is_close(&Some(0.0)));
344        assert!(!Some(0.0).is_close(&None));
345        assert!(5.is_close(&5));
346    }
347
348    #[test]
349    fn test_axes() {
350        let dims = [Dimension::MZ(MZ()), Dimension::Mass(Mass()), Dimension::Time(Time()), Dimension::IonMobility(IonMobility()), Dimension::Dimensionless(Dimensionless())];
351        for dim in dims {
352            match dim {
353                Dimension::MZ(x) => {
354                    assert_eq!(x.name_of(), "m/z");
355                    assert_eq!(dim.to_string(), "m/z");
356                    assert_eq!(dim.minimum_value(), 0.0);
357                    assert_eq!(dim.maximum_value(), f64::INFINITY);
358                },
359                Dimension::Mass(x) => {
360                    assert_eq!(x.name_of(), "neutral mass");
361                    assert_eq!(dim.to_string(), "neutral mass");
362                    assert_eq!(dim.minimum_value(), 0.0);
363                    assert_eq!(dim.maximum_value(), f64::INFINITY);
364                },
365                Dimension::IonMobility(x) => {
366                    assert_eq!(x.name_of(), "ion mobility");
367                    assert_eq!(dim.to_string(), "ion mobility");
368                    assert_eq!(dim.minimum_value(), 0.0);
369                    assert_eq!(dim.maximum_value(), f64::INFINITY);
370                }
371                Dimension::Time(x) => {
372                    assert_eq!(x.name_of(), "time");
373                    assert_eq!(dim.to_string(), "time");
374                    assert_eq!(dim.minimum_value(), 0.0);
375                    assert_eq!(dim.maximum_value(), f64::INFINITY);
376                }
377                Dimension::Dimensionless(x) => {
378                    assert_eq!(x.name_of(), "");
379                    assert_eq!(dim.to_string(), "");
380                    assert_eq!(dim.minimum_value(), 0.0);
381                    assert_eq!(dim.maximum_value(), f64::INFINITY);
382                }
383            }
384        }
385    }
386}