1use std::fmt::Display;
2
3use num_traits::{Float, FromPrimitive};
4
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7
8
9#[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))]
60pub struct MZ();
62
63impl MZ {
64 #[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))]
73pub struct Mass();
75
76impl Mass {
77 #[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))]
86pub struct Time();
88impl Time {
89 #[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))]
98pub struct IonMobility();
100impl IonMobility {
101 #[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 #[inline]
116 pub fn coordinate<T: CoordinateLike<Dimensionless>>(inst: &T) -> f64 {
117 CoordinateLike::<Dimensionless>::coordinate(inst)
118 }
119}
120
121pub 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
177pub trait CoordinateLike<T>: PartialOrd {
179 fn coordinate(&self) -> f64;
182}
183
184pub trait CoordinateLikeMut<T>: CoordinateLike<T> {
186 fn coordinate_mut(&mut self) -> &mut f64;
187}
188
189pub trait MassLocated: CoordinateLike<Mass> {
191 #[inline]
192 fn neutral_mass(&self) -> f64 {
193 CoordinateLike::<Mass>::coordinate(self)
194 }
195}
196
197pub 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
243pub type IndexType = u32;
245
246pub 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}