1use std::cmp::Ordering;
8use std::fmt;
9
10use crate::coordinate::{CoordinateLike, IndexType, IndexedCoordinate, Mass, MZ};
11use crate::{implement_centroidlike_inner, implement_deconvoluted_centroidlike_inner, CoordinateLikeMut, IonMobility};
12use crate::{MZLocated, MassLocated};
13#[cfg(feature = "serde")]
14use serde::{Deserialize, Serialize};
15
16pub trait IntensityMeasurement {
19 fn intensity(&self) -> f32;
20}
21
22pub trait IntensityMeasurementMut: IntensityMeasurement {
23 fn intensity_mut(&mut self) -> &mut f32;
24}
25
26impl<T: IntensityMeasurement> IntensityMeasurement for &T {
27 fn intensity(&self) -> f32 {
28 (*self).intensity()
29 }
30}
31
32impl<T: IntensityMeasurement> IntensityMeasurement for &mut T {
33 fn intensity(&self) -> f32 {
34 IntensityMeasurement::intensity(*self)
35 }
36}
37
38impl<T: IntensityMeasurementMut> IntensityMeasurementMut for &mut T {
39 fn intensity_mut(&mut self) -> &mut f32 {
40 IntensityMeasurementMut::intensity_mut(*self)
41 }
42}
43
44pub trait CentroidLike: IndexedCoordinate<MZ> + IntensityMeasurement {
47 #[inline]
48 fn as_centroid(&self) -> CentroidPeak {
49 CentroidPeak {
50 mz: self.coordinate(),
51 intensity: self.intensity(),
52 index: self.get_index(),
53 }
54 }
55}
56
57pub trait KnownCharge {
59 fn charge(&self) -> i32;
60}
61
62impl<T: KnownCharge> KnownCharge for &T {
63 fn charge(&self) -> i32 {
64 (*self).charge()
65 }
66}
67
68pub trait KnownChargeMut: KnownCharge {
69 fn charge_mut(&mut self) -> &mut i32;
70}
71
72impl<T: KnownCharge> KnownCharge for &mut T {
73 fn charge(&self) -> i32 {
74 KnownCharge::charge(*self)
75 }
76}
77
78impl<T: KnownChargeMut> KnownChargeMut for &mut T {
79 fn charge_mut(&mut self) -> &mut i32 {
80 KnownChargeMut::charge_mut(*self)
81 }
82}
83
84pub trait DeconvolutedCentroidLike:
89 IndexedCoordinate<Mass> + IntensityMeasurement + KnownCharge
90{
91 fn as_centroid(&self) -> DeconvolutedPeak {
92 DeconvolutedPeak {
93 neutral_mass: self.coordinate(),
94 intensity: self.intensity(),
95 charge: self.charge(),
96 index: self.get_index(),
97 }
98 }
99}
100
101#[derive(Default, Clone, Debug)]
105#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
106pub struct CentroidPeak {
107 pub mz: f64,
108 pub intensity: f32,
109 pub index: IndexType,
110}
111
112impl CentroidPeak {
113 #[inline]
114 pub fn new(mz: f64, intensity: f32, index: IndexType) -> CentroidPeak {
115 CentroidPeak {
116 mz,
117 intensity,
118 index,
119 }
120 }
121}
122
123impl fmt::Display for CentroidPeak {
124 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
125 write!(
126 f,
127 "CentroidPeak({}, {}, {})",
128 self.mz, self.intensity, self.index
129 )
130 }
131}
132
133implement_centroidlike_inner!(CentroidPeak, true, false);
134
135impl<T: IndexedCoordinate<MZ> + IntensityMeasurement> CentroidLike for T {}
136
137impl<T: IndexedCoordinate<Mass> + IntensityMeasurement + KnownCharge> DeconvolutedCentroidLike
138 for T
139{
140}
141
142#[derive(Debug, Clone, Default, PartialEq, PartialOrd)]
143#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
144pub struct MZPoint {
145 pub mz: f64,
146 pub intensity: f32,
147}
148
149impl MZPoint {
150 #[inline]
151 pub fn new(mz: f64, intensity: f32) -> MZPoint {
152 MZPoint { mz, intensity }
153 }
154}
155
156impl CoordinateLike<MZ> for MZPoint {
157 fn coordinate(&self) -> f64 {
158 self.mz
159 }
160}
161
162impl IntensityMeasurement for MZPoint {
163 #[inline]
164 fn intensity(&self) -> f32 {
165 self.intensity
166 }
167}
168
169impl IntensityMeasurementMut for MZPoint {
170 #[inline]
171 fn intensity_mut(&mut self) -> &mut f32 {
172 &mut self.intensity
173 }
174}
175
176impl IndexedCoordinate<MZ> for MZPoint {
177 #[inline]
178 fn get_index(&self) -> IndexType {
179 0
180 }
181 fn set_index(&mut self, _index: IndexType) {}
182}
183
184impl From<MZPoint> for CentroidPeak {
185 fn from(peak: MZPoint) -> Self {
186 CentroidPeak {
187 mz: peak.mz,
188 intensity: peak.intensity,
189 index: 0,
190 }
191 }
192}
193
194impl From<CentroidPeak> for MZPoint {
195 fn from(peak: CentroidPeak) -> Self {
196 let mut inst = Self {
197 mz: peak.coordinate(),
198 intensity: peak.intensity(),
199 };
200 inst.set_index(peak.index);
201 inst
202 }
203}
204
205#[derive(Default, Clone, Debug)]
206#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
207pub struct DeconvolutedPeak {
210 pub neutral_mass: f64,
211 pub intensity: f32,
212 pub charge: i32,
213 pub index: IndexType,
214}
215
216impl DeconvolutedPeak {
217 pub fn new(neutral_mass: f64, intensity: f32, charge: i32, index: IndexType) -> Self {
218 Self {
219 neutral_mass,
220 intensity,
221 charge,
222 index,
223 }
224 }
225
226 pub fn mz(&self) -> f64 {
227 let charge_carrier: f64 = 1.007276;
228 let charge = self.charge as f64;
229 (self.neutral_mass + charge_carrier * charge) / charge
230 }
231}
232
233implement_deconvoluted_centroidlike_inner!(DeconvolutedPeak, true, false);
234
235impl fmt::Display for DeconvolutedPeak {
236 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
237 write!(
238 f,
239 "DeconvolutedPeak({}, {}, {}, {})",
240 self.neutral_mass, self.intensity, self.charge, self.index
241 )
242 }
243}
244
245impl CoordinateLike<MZ> for DeconvolutedPeak {
246 fn coordinate(&self) -> f64 {
247 self.mz()
248 }
249}
250
251#[derive(Debug, Clone, Copy)]
257pub struct CentroidRef<'a, C: CentroidLike> {
258 inner: &'a C,
259 index: IndexType,
260}
261
262impl<C: CentroidLike> PartialEq for CentroidRef<'_, C> {
263 fn eq(&self, other: &Self) -> bool {
264 if (self.mz() - other.coordinate()).abs() > 1e-3
265 || (self.intensity() - other.intensity()).abs() > 1e-3
266 {
267 return false;
268 }
269 true
270 }
271}
272
273impl<C: CentroidLike> PartialOrd for CentroidRef<'_, C> {
274 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
275 Some(
276 self.mz()
277 .total_cmp(&other.mz())
278 .then_with(|| self.intensity().total_cmp(&other.intensity())),
279 )
280 }
281}
282
283impl<'a, C: CentroidLike> CentroidRef<'a, C> {
284 pub fn new(inner: &'a C, index: IndexType) -> Self {
285 Self { inner, index }
286 }
287}
288
289impl<C: CentroidLike> CoordinateLike<MZ> for CentroidRef<'_, C> {
290 fn coordinate(&self) -> f64 {
291 self.inner.mz()
292 }
293}
294
295impl<C: CentroidLike> IntensityMeasurement for CentroidRef<'_, C> {
296 #[inline]
297 fn intensity(&self) -> f32 {
298 self.inner.intensity()
299 }
300}
301
302impl<C: CentroidLike> IndexedCoordinate<MZ> for CentroidRef<'_, C> {
303 #[inline]
304 fn get_index(&self) -> IndexType {
305 self.index
306 }
307 fn set_index(&mut self, index: IndexType) {
308 self.index = index
309 }
310}
311
312#[derive(Debug, Clone, Copy)]
318pub struct DeconvolutedCentroidRef<'a, D: DeconvolutedCentroidLike> {
319 inner: &'a D,
320 index: IndexType,
321}
322
323impl<D: DeconvolutedCentroidLike> PartialEq for DeconvolutedCentroidRef<'_, D> {
324 fn eq(&self, other: &Self) -> bool {
325 if (self.neutral_mass() - other.coordinate()).abs() > 1e-3
326 || self.charge() != other.charge()
327 || (self.intensity() - other.intensity()).abs() > 1e-3
328 {
329 return false;
330 }
331 true
332 }
333}
334
335impl<D: DeconvolutedCentroidLike> PartialOrd for DeconvolutedCentroidRef<'_, D> {
336 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
337 Some(
338 self.neutral_mass()
339 .total_cmp(&other.neutral_mass())
340 .then_with(|| self.intensity().total_cmp(&other.intensity())),
341 )
342 }
343}
344
345impl<'a, D: DeconvolutedCentroidLike> DeconvolutedCentroidRef<'a, D> {
346 pub fn new(inner: &'a D, index: IndexType) -> Self {
347 Self { inner, index }
348 }
349}
350
351impl<D: DeconvolutedCentroidLike> CoordinateLike<Mass> for DeconvolutedCentroidRef<'_, D> {
352 fn coordinate(&self) -> f64 {
353 self.inner.neutral_mass()
354 }
355}
356
357impl<D: DeconvolutedCentroidLike> KnownCharge for DeconvolutedCentroidRef<'_, D> {
358 fn charge(&self) -> i32 {
359 self.inner.charge()
360 }
361}
362
363impl<D: DeconvolutedCentroidLike> IntensityMeasurement for DeconvolutedCentroidRef<'_, D> {
364 #[inline]
365 fn intensity(&self) -> f32 {
366 self.inner.intensity()
367 }
368}
369
370impl<D: DeconvolutedCentroidLike> IndexedCoordinate<Mass> for DeconvolutedCentroidRef<'_, D> {
371 #[inline]
372 fn get_index(&self) -> IndexType {
373 self.index
374 }
375 fn set_index(&mut self, index: IndexType) {
376 self.index = index
377 }
378}
379
380impl<D: DeconvolutedCentroidLike> CoordinateLike<MZ> for DeconvolutedCentroidRef<'_, D>
381where
382 D: CoordinateLike<MZ>,
383{
384 fn coordinate(&self) -> f64 {
385 <D as CoordinateLike<MZ>>::coordinate(self.inner)
386 }
387}
388
389#[derive(Debug, Default, Clone, PartialEq, PartialOrd)]
392pub struct IonMobilityAwareCentroidPeak {
393 pub mz: f64,
394 pub ion_mobility: f64,
395 pub intensity: f32,
396 pub index: u32,
397}
398
399impl CoordinateLike<MZ> for IonMobilityAwareCentroidPeak {
400 fn coordinate(&self) -> f64 {
401 self.mz
402 }
403}
404
405impl CoordinateLike<IonMobility> for IonMobilityAwareCentroidPeak {
406 fn coordinate(&self) -> f64 {
407 self.ion_mobility
408 }
409}
410
411impl IntensityMeasurement for IonMobilityAwareCentroidPeak {
412 fn intensity(&self) -> f32 {
413 self.intensity
414 }
415}
416
417impl CoordinateLikeMut<MZ> for IonMobilityAwareCentroidPeak {
418 fn coordinate_mut(&mut self) -> &mut f64 {
419 &mut self.mz
420 }
421}
422
423impl CoordinateLikeMut<IonMobility> for IonMobilityAwareCentroidPeak {
424 fn coordinate_mut(&mut self) -> &mut f64 {
425 &mut self.ion_mobility
426 }
427}
428
429impl IndexedCoordinate<MZ> for IonMobilityAwareCentroidPeak {
430 fn get_index(&self) -> IndexType {
431 self.index
432 }
433
434 fn set_index(&mut self, index: IndexType) {
435 self.index = index
436 }
437}
438
439impl IntensityMeasurementMut for IonMobilityAwareCentroidPeak {
440 fn intensity_mut(&mut self) -> &mut f32 {
441 &mut self.intensity
442 }
443}
444
445impl IonMobilityAwareCentroidPeak {
446 pub fn new(mz: f64, ion_mobility: f64, intensity: f32, index: u32) -> Self {
447 Self { mz, ion_mobility, intensity, index }
448 }
449}
450
451
452#[derive(Debug, Default, Clone, PartialEq, PartialOrd)]
455pub struct IonMobilityAwareDeconvolutedPeak {
456 pub neutral_mass: f64,
457 pub ion_mobility: f64,
458 pub charge: i32,
459 pub intensity: f32,
460 pub index: u32,
461}
462
463impl IonMobilityAwareDeconvolutedPeak {
464 pub fn new(neutral_mass: f64, ion_mobility: f64, charge: i32, intensity: f32, index: u32) -> Self {
465 Self {
466 neutral_mass,
467 ion_mobility,
468 charge,
469 intensity,
470 index,
471 }
472 }
473}
474
475impl CoordinateLike<Mass> for IonMobilityAwareDeconvolutedPeak {
476 fn coordinate(&self) -> f64 {
477 self.neutral_mass
478 }
479}
480
481impl CoordinateLike<MZ> for IonMobilityAwareDeconvolutedPeak {
482 fn coordinate(&self) -> f64 {
483 self.as_centroid().mz()
484 }
485}
486
487impl CoordinateLike<IonMobility> for IonMobilityAwareDeconvolutedPeak {
488 fn coordinate(&self) -> f64 {
489 self.ion_mobility
490 }
491}
492
493impl CoordinateLikeMut<Mass> for IonMobilityAwareDeconvolutedPeak {
494 fn coordinate_mut(&mut self) -> &mut f64 {
495 &mut self.neutral_mass
496 }
497}
498
499impl CoordinateLikeMut<IonMobility> for IonMobilityAwareDeconvolutedPeak {
500 fn coordinate_mut(&mut self) -> &mut f64 {
501 &mut self.ion_mobility
502 }
503}
504
505impl From<IonMobilityAwareDeconvolutedPeak> for DeconvolutedPeak {
506 fn from(value: IonMobilityAwareDeconvolutedPeak) -> Self {
507 Self::new(
508 value.neutral_mass,
509 value.intensity,
510 value.charge,
511 value.index,
512 )
513 }
514}
515
516impl IntensityMeasurement for IonMobilityAwareDeconvolutedPeak {
517 fn intensity(&self) -> f32 {
518 self.intensity
519 }
520}
521
522impl IntensityMeasurementMut for IonMobilityAwareDeconvolutedPeak {
523 fn intensity_mut(&mut self) -> &mut f32 {
524 &mut self.intensity
525 }
526}
527
528impl KnownCharge for IonMobilityAwareDeconvolutedPeak {
529 fn charge(&self) -> i32 {
530 self.charge
531 }
532}
533
534impl KnownChargeMut for IonMobilityAwareDeconvolutedPeak {
535 fn charge_mut(&mut self) -> &mut i32 {
536 &mut self.charge
537 }
538}
539
540impl IndexedCoordinate<Mass> for IonMobilityAwareDeconvolutedPeak {
541 fn get_index(&self) -> IndexType {
542 self.index
543 }
544
545 fn set_index(&mut self, index: IndexType) {
546 self.index = index;
547 }
548}
549
550
551#[cfg(test)]
552mod test {
553 use super::*;
554 use crate::coordinate::*;
555
556 #[test]
557 fn test_conversion() {
558 let x = CentroidPeak::new(204.07, 5000f32, 19);
559 let y: MZPoint = x.clone().into();
560 assert_eq!(y.mz, x.mz);
561 assert_eq!(y.coordinate(), x.coordinate());
562 assert_eq!(y.intensity(), x.intensity());
563 let mut z: CentroidPeak = y.clone().into();
565 assert_eq!(z, y);
566 *z.intensity_mut() += 500.0;
567 assert!(x < z);
568 assert!(z > x);
569 assert!(x == x);
570 assert!(z != x);
571
572 let xr = CentroidRef::new(&x, 0);
573 let zr = CentroidRef::new(&z, 0);
574 assert!(xr < zr);
575 assert!(zr > xr);
576 assert!(xr == xr);
577 assert!(zr != xr);
578 }
579
580 #[test]
581 fn test_to_string() {
582 let x = CentroidPeak::new(204.07, 5000f32, 19);
583 assert!(x.to_string().starts_with("CentroidPeak"));
584
585 let x = DeconvolutedPeak {
586 neutral_mass: 799.359964027,
587 charge: 2,
588 intensity: 5000f32,
589 index: 1,
590 };
591
592 assert!(x.to_string().starts_with("DeconvolutedPeak"));
593 }
594
595 #[test]
596 fn test_coordinate_context() {
597 let x = DeconvolutedPeak {
598 neutral_mass: 799.359964027,
599 charge: 2,
600 intensity: 5000f32,
601 index: 1,
602 };
603 assert_eq!(x.neutral_mass, 799.359964027);
604 assert_eq!(CoordinateLike::<Mass>::coordinate(&x), 799.359964027);
605 assert_eq!(Mass::coordinate(&x), 799.359964027);
606 assert!((x.mz() - 400.68725848027003).abs() < 1e-6);
607 assert!((MZ::coordinate(&x) - 400.68725848027003).abs() < 1e-6);
608
609 let mut y = x.as_centroid();
610 *y.intensity_mut() += 500.0;
611 assert!(x < y);
612 assert!(y > x);
613 assert!(x == x);
614 assert!(y != x);
615
616 let xr = DeconvolutedCentroidRef::new(&x, 0);
617 let yr = DeconvolutedCentroidRef::new(&y, 0);
618 assert!(xr < yr);
619 assert!(yr > xr);
620 assert!(xr == xr);
621 assert!(yr != xr);
622 }
623
624 #[cfg(feature = "serde")]
625 #[test]
626 fn test_serialize() -> std::io::Result<()> {
627 use serde_json;
628 use std::io;
629 use std::io::prelude::*;
630
631 let mut buff = Vec::new();
632 let buffer_writer = io::Cursor::new(&mut buff);
633 let mut writer = io::BufWriter::new(buffer_writer);
634
635 let x = CentroidPeak::new(204.07, 5000f32, 19);
636 serde_json::to_writer_pretty(&mut writer, &x)?;
638 writer.flush()?;
639 let view = String::from_utf8_lossy(writer.get_ref().get_ref());
640 let peak: CentroidPeak = serde_json::from_str(&view)?;
641 assert!((peak.mz() - x.mz()).abs() < 1e-6);
642 Ok(())
643 }
644}