1use mscore::data::peptide::{PeptideIon, PeptideProductIonSeriesCollection};
2use mscore::data::spectrum::{IndexedMzSpectrum, MsType, MzSpectrum};
3use mscore::simulation::annotation::{
4 MzSpectrumAnnotated, TimsFrameAnnotated, TimsSpectrumAnnotated,
5};
6use mscore::timstof::frame::TimsFrame;
7use mscore::timstof::quadrupole::{IonTransmission, TimsTransmissionDDA};
8use mscore::timstof::spectrum::TimsSpectrum;
9use std::collections::{BTreeMap, HashSet};
10use std::path::Path;
11
12use rayon::prelude::*;
13use rayon::ThreadPoolBuilder;
14use crate::sim::handle::TimsTofSyntheticsDataHandle;
15use crate::sim::precursor::TimsTofSyntheticsPrecursorFrameBuilder;
16
17pub struct TimsTofSyntheticsFrameBuilderDDA {
18 pub path: String,
19 pub precursor_frame_builder: TimsTofSyntheticsPrecursorFrameBuilder,
20 pub transmission_settings: TimsTransmissionDDA,
21 pub fragment_ions:
22 Option<BTreeMap<(u32, i8, i32), (PeptideProductIonSeriesCollection, Vec<MzSpectrum>)>>,
23 pub fragment_ions_annotated: Option<
24 BTreeMap<(u32, i8, i32), (PeptideProductIonSeriesCollection, Vec<MzSpectrumAnnotated>)>,
25 >,
26}
27
28impl TimsTofSyntheticsFrameBuilderDDA {
29 pub fn new(path: &Path, with_annotations: bool, num_threads: usize) -> Self {
30
31 let handle = TimsTofSyntheticsDataHandle::new(path).unwrap();
32 let fragment_ions = handle.read_fragment_ions().unwrap();
33 let transmission_settings = handle.get_transmission_dda();
34
35 let synthetics = TimsTofSyntheticsPrecursorFrameBuilder::new(path).unwrap();
36
37 match with_annotations {
38 true => {
39 let fragment_ions =
40 Some(TimsTofSyntheticsDataHandle::build_fragment_ions_annotated(
41 &synthetics.peptides,
42 &fragment_ions,
43 num_threads,
44 ));
45 Self {
46 path: path.to_str().unwrap().to_string(),
47 precursor_frame_builder: synthetics,
48 transmission_settings,
49 fragment_ions: None,
50 fragment_ions_annotated: fragment_ions,
51 }
52 }
53 false => {
54 let fragment_ions = Some(TimsTofSyntheticsDataHandle::build_fragment_ions(
55 &synthetics.peptides,
56 &fragment_ions,
57 num_threads,
58 ));
59 Self {
60 path: path.to_str().unwrap().to_string(),
61 precursor_frame_builder: synthetics,
62 transmission_settings,
63 fragment_ions,
64 fragment_ions_annotated: None,
65 }
66 }
67 }
68 }
69 pub fn build_frame(
81 &self,
82 frame_id: u32,
83 fragmentation: bool,
84 mz_noise_precursor: bool,
85 uniform: bool,
86 precursor_noise_ppm: f64,
87 mz_noise_fragment: bool,
88 fragment_noise_ppm: f64,
89 right_drag: bool,
90 ) -> TimsFrame {
91 match self
93 .precursor_frame_builder
94 .precursor_frame_id_set
95 .contains(&frame_id)
96 {
97 true => self.build_ms1_frame(
98 frame_id,
99 mz_noise_precursor,
100 uniform,
101 precursor_noise_ppm,
102 right_drag,
103 ),
104 false => self.build_ms2_frame(
105 frame_id,
106 fragmentation,
107 mz_noise_fragment,
108 uniform,
109 fragment_noise_ppm,
110 right_drag,
111 ),
112 }
113 }
114
115 pub fn build_frame_annotated(
116 &self,
117 frame_id: u32,
118 fragmentation: bool,
119 mz_noise_precursor: bool,
120 uniform: bool,
121 precursor_noise_ppm: f64,
122 mz_noise_fragment: bool,
123 fragment_noise_ppm: f64,
124 right_drag: bool,
125 ) -> TimsFrameAnnotated {
126 match self
127 .precursor_frame_builder
128 .precursor_frame_id_set
129 .contains(&frame_id)
130 {
131 true => self.build_ms1_frame_annotated(
132 frame_id,
133 mz_noise_precursor,
134 uniform,
135 precursor_noise_ppm,
136 right_drag,
137 ),
138 false => self.build_ms2_frame_annotated(
139 frame_id,
140 fragmentation,
141 mz_noise_fragment,
142 uniform,
143 fragment_noise_ppm,
144 right_drag,
145 ),
146 }
147 }
148
149 pub fn get_fragment_ion_ids(&self, precursor_frame_ids: Vec<u32>) -> Vec<u32> {
150 let mut peptide_ids: HashSet<u32> = HashSet::new();
151 for frame_id in precursor_frame_ids {
153 for (peptide_id, peptide) in self.precursor_frame_builder.peptides.iter() {
154 if peptide.frame_start <= frame_id && peptide.frame_end >= frame_id {
155 peptide_ids.insert(*peptide_id);
156 }
157 }
158 }
159 let mut result: Vec<u32> = Vec::new();
161 for item in peptide_ids {
162 let ions = self.precursor_frame_builder.ions.get(&item).unwrap();
163 for ion in ions.iter() {
164 result.push(ion.ion_id);
165 }
166 }
167 result
168 }
169
170 pub fn build_frames(
171 &self,
172 frame_ids: Vec<u32>,
173 fragmentation: bool,
174 mz_noise_precursor: bool,
175 uniform: bool,
176 precursor_noise_ppm: f64,
177 mz_noise_fragment: bool,
178 fragment_noise_ppm: f64,
179 right_drag: bool,
180 num_threads: usize,
181 ) -> Vec<TimsFrame> {
182 let thread_pool = ThreadPoolBuilder::new()
183 .num_threads(num_threads)
184 .build()
185 .unwrap();
186 let mut tims_frames: Vec<TimsFrame> = Vec::new();
187
188 thread_pool.install(|| {
189 tims_frames = frame_ids
190 .par_iter()
191 .map(|frame_id| {
192 self.build_frame(
193 *frame_id,
194 fragmentation,
195 mz_noise_precursor,
196 uniform,
197 precursor_noise_ppm,
198 mz_noise_fragment,
199 fragment_noise_ppm,
200 right_drag,
201 )
202 })
203 .collect();
204 });
205
206 tims_frames.sort_by(|a, b| a.frame_id.cmp(&b.frame_id));
207
208 tims_frames
209 }
210 pub fn build_frames_annotated(
211 &self,
212 frame_ids: Vec<u32>,
213 fragmentation: bool,
214 mz_noise_precursor: bool,
215 uniform: bool,
216 precursor_noise_ppm: f64,
217 mz_noise_fragment: bool,
218 fragment_noise_ppm: f64,
219 right_drag: bool,
220 num_threads: usize,
221 ) -> Vec<TimsFrameAnnotated> {
222 let thread_pool = ThreadPoolBuilder::new()
223 .num_threads(num_threads)
224 .build()
225 .unwrap();
226 let mut tims_frames: Vec<TimsFrameAnnotated> = Vec::new();
227
228 thread_pool.install(|| {
229 tims_frames = frame_ids
230 .par_iter()
231 .map(|frame_id| {
232 self.build_frame_annotated(
233 *frame_id,
234 fragmentation,
235 mz_noise_precursor,
236 uniform,
237 precursor_noise_ppm,
238 mz_noise_fragment,
239 fragment_noise_ppm,
240 right_drag,
241 )
242 })
243 .collect();
244 });
245
246 tims_frames.sort_by(|a, b| a.frame_id.cmp(&b.frame_id));
247
248 tims_frames
249 }
250
251 fn build_ms1_frame(
252 &self,
253 frame_id: u32,
254 mz_noise_precursor: bool,
255 uniform: bool,
256 precursor_ppm: f64,
257 right_drag: bool,
258 ) -> TimsFrame {
259 let mut tims_frame = self.precursor_frame_builder.build_precursor_frame(
260 frame_id,
261 mz_noise_precursor,
262 uniform,
263 precursor_ppm,
264 right_drag,
265 );
266 let intensities_rounded = tims_frame
267 .ims_frame
268 .intensity
269 .iter()
270 .map(|x| x.round())
271 .collect::<Vec<_>>();
272 tims_frame.ims_frame.intensity = intensities_rounded;
273 tims_frame
274 }
275
276 fn build_ms1_frame_annotated(
277 &self,
278 frame_id: u32,
279 mz_noise_precursor: bool,
280 uniform: bool,
281 precursor_ppm: f64,
282 right_drag: bool,
283 ) -> TimsFrameAnnotated {
284 let mut tims_frame = self
285 .precursor_frame_builder
286 .build_precursor_frame_annotated(
287 frame_id,
288 mz_noise_precursor,
289 uniform,
290 precursor_ppm,
291 right_drag,
292 );
293 let intensities_rounded = tims_frame
294 .intensity
295 .iter()
296 .map(|x| x.round())
297 .collect::<Vec<_>>();
298 tims_frame.intensity = intensities_rounded;
299 tims_frame
300 }
301
302 fn build_ms2_frame(
303 &self,
304 frame_id: u32,
305 fragmentation: bool,
306 mz_noise_fragment: bool,
307 uniform: bool,
308 fragment_ppm: f64,
309 right_drag: bool,
310 ) -> TimsFrame {
311 match fragmentation {
312 false => {
313 let mut frame = self.transmission_settings.transmit_tims_frame(
314 &self.build_ms1_frame(
315 frame_id,
316 mz_noise_fragment,
317 uniform,
318 fragment_ppm,
319 right_drag,
320 ),
321 None,
322 );
323 let intensities_rounded = frame
324 .ims_frame
325 .intensity
326 .iter()
327 .map(|x| x.round())
328 .collect::<Vec<_>>();
329 frame.ims_frame.intensity = intensities_rounded;
330 frame.ms_type = MsType::FragmentDia;
331 frame
332 }
333 true => {
334 let mut frame = self.build_fragment_frame(
335 frame_id,
336 &self.fragment_ions.as_ref().unwrap(),
337 mz_noise_fragment,
338 uniform,
339 fragment_ppm,
340 None,
341 None,
342 None,
343 Some(right_drag),
344 );
345 let intensities_rounded = frame
346 .ims_frame
347 .intensity
348 .iter()
349 .map(|x| x.round())
350 .collect::<Vec<_>>();
351 frame.ims_frame.intensity = intensities_rounded;
352 frame
353 }
354 }
355 }
356
357 fn build_ms2_frame_annotated(
358 &self,
359 frame_id: u32,
360 fragmentation: bool,
361 mz_noise_fragment: bool,
362 uniform: bool,
363 fragment_ppm: f64,
364 right_drag: bool,
365 ) -> TimsFrameAnnotated {
366 match fragmentation {
367 false => {
368 let mut frame = self.transmission_settings.transmit_tims_frame_annotated(
369 &self.build_ms1_frame_annotated(
370 frame_id,
371 mz_noise_fragment,
372 uniform,
373 fragment_ppm,
374 right_drag,
375 ),
376 None,
377 );
378 let intensities_rounded = frame
379 .intensity
380 .iter()
381 .map(|x| x.round())
382 .collect::<Vec<_>>();
383 frame.intensity = intensities_rounded;
384 frame.ms_type = MsType::FragmentDia;
385 frame
386 }
387 true => {
388 let mut frame = self.build_fragment_frame_annotated(
389 frame_id,
390 &self.fragment_ions_annotated.as_ref().unwrap(),
391 mz_noise_fragment,
392 uniform,
393 fragment_ppm,
394 None,
395 None,
396 None,
397 Some(right_drag),
398 );
399 let intensities_rounded = frame
400 .intensity
401 .iter()
402 .map(|x| x.round())
403 .collect::<Vec<_>>();
404 frame.intensity = intensities_rounded;
405 frame
406 }
407 }
408 }
409
410 fn build_fragment_frame(
424 &self,
425 frame_id: u32,
426 fragment_ions: &BTreeMap<
427 (u32, i8, i32),
428 (PeptideProductIonSeriesCollection, Vec<MzSpectrum>),
429 >,
430 mz_noise_fragment: bool,
431 uniform: bool,
432 fragment_ppm: f64,
433 mz_min: Option<f64>,
434 mz_max: Option<f64>,
435 intensity_min: Option<f64>,
436 right_drag: Option<bool>,
437 ) -> TimsFrame {
438 let ms_type = match self
440 .precursor_frame_builder
441 .precursor_frame_id_set
442 .contains(&frame_id)
443 {
444 false => MsType::FragmentDda,
445 true => MsType::Unknown,
446 };
447
448 let rt = *self
450 .precursor_frame_builder
451 .frame_to_rt
452 .get(&frame_id)
453 .expect("frame_to_rt should always have this frame") as f64;
454
455 if self
457 .transmission_settings
458 .pasef_meta
459 .get(&(frame_id as i32))
460 .is_none()
461 || !self
462 .precursor_frame_builder
463 .frame_to_abundances
464 .contains_key(&frame_id)
465 {
466 return TimsFrame::new(
467 frame_id as i32,
468 ms_type.clone(),
469 rt,
470 vec![], vec![], vec![], vec![], vec![],
471 );
472 }
473
474 let mut tims_spectra: Vec<TimsSpectrum> = Vec::new();
475
476 let (peptide_ids, frame_abundances) = self
478 .precursor_frame_builder
479 .frame_to_abundances
480 .get(&frame_id)
481 .unwrap();
482
483 for (peptide_id, frame_abundance) in peptide_ids.iter().zip(frame_abundances.iter()) {
485 if !self
487 .precursor_frame_builder
488 .peptide_to_ions
489 .contains_key(&peptide_id)
490 {
491 continue;
492 }
493
494 let (ion_abundances, scan_occurrences, scan_abundances, charges, spectra) = self
496 .precursor_frame_builder
497 .peptide_to_ions
498 .get(&peptide_id)
499 .unwrap();
500
501 for (index, ion_abundance) in ion_abundances.iter().enumerate() {
502 let all_scan_occurrence = scan_occurrences.get(index).unwrap();
504 let all_scan_abundance = scan_abundances.get(index).unwrap();
505
506 let spectrum = spectra.get(index).unwrap();
508
509 for (scan, scan_abundance) in
511 all_scan_occurrence.iter().zip(all_scan_abundance.iter())
512 {
513 if !self.transmission_settings.any_transmitted(
515 frame_id as i32,
516 *scan as i32,
517 &spectrum.mz,
518 None,
519 ) {
520 continue;
521 }
522
523 let total_events = self
525 .precursor_frame_builder
526 .peptide_to_events
527 .get(&peptide_id)
528 .unwrap();
529 let fraction_events =
530 frame_abundance * scan_abundance * ion_abundance * total_events;
531
532 let maybe_pasef_meta = self.transmission_settings.pasef_meta.get(&(frame_id as i32));
534
535 let collision_energy: f64 = match maybe_pasef_meta {
536 Some(pasef_meta) => {
537 pasef_meta
538 .iter()
539 .find(|scan_meta| scan_meta.scan_start <= *scan as i32 && scan_meta.scan_end >= *scan as i32)
540 .map(|s| s.collision_energy)
541 .unwrap_or(0.0)
542 },
543 None => 0.0
544 };
545
546 let collision_energy_quantized = (collision_energy * 1e1).round() as i32;
547
548 let charge_state = charges.get(index).unwrap();
550 let maybe_value = fragment_ions.get(&(
552 *peptide_id,
553 *charge_state,
554 collision_energy_quantized,
555 ));
556
557 if maybe_value.is_none() {
559 continue;
560 }
561
562 for fragment_ion_series in maybe_value.unwrap().1.iter() {
564 let scaled_spec = fragment_ion_series.clone() * fraction_events as f64;
565 let right_drag = right_drag.unwrap_or(false);
566
567 let mz_spectrum = if mz_noise_fragment {
568 match uniform {
569 true => scaled_spec.add_mz_noise_uniform(fragment_ppm, right_drag),
570 false => scaled_spec.add_mz_noise_normal(fragment_ppm),
571 }
572 } else {
573 scaled_spec
574 };
575
576 tims_spectra.push(TimsSpectrum::new(
577 frame_id as i32,
578 *scan as i32,
579 *self
580 .precursor_frame_builder
581 .frame_to_rt
582 .get(&frame_id)
583 .unwrap() as f64,
584 *self
585 .precursor_frame_builder
586 .scan_to_mobility
587 .get(&scan)
588 .unwrap() as f64,
589 ms_type.clone(),
590 IndexedMzSpectrum::new(
591 vec![0; mz_spectrum.mz.len()],
592 mz_spectrum.mz,
593 mz_spectrum.intensity,
594 )
595 .filter_ranged(100.0, 1700.0, 1.0, 1e9),
596 ));
597 }
598 }
599 }
600 }
601
602 if tims_spectra.is_empty() {
603 return TimsFrame::new(
604 frame_id as i32,
605 ms_type.clone(),
606 *self
607 .precursor_frame_builder
608 .frame_to_rt
609 .get(&frame_id)
610 .unwrap() as f64,
611 vec![],
612 vec![],
613 vec![],
614 vec![],
615 vec![],
616 );
617 }
618
619 let tims_frame = TimsFrame::from_tims_spectra(tims_spectra);
620 tims_frame.filter_ranged(
621 mz_min.unwrap_or(100.0),
622 mz_max.unwrap_or(1700.0),
623 0,
624 1000,
625 0.0,
626 10.0,
627 intensity_min.unwrap_or(1.0),
628 1e9,
629 )
630 }
631
632 pub fn build_fragment_frame_annotated(
633 &self,
634 frame_id: u32,
635 fragment_ions: &BTreeMap<
636 (u32, i8, i32),
637 (PeptideProductIonSeriesCollection, Vec<MzSpectrumAnnotated>),
638 >,
639 mz_noise_fragment: bool,
640 uniform: bool,
641 fragment_ppm: f64,
642 mz_min: Option<f64>,
643 mz_max: Option<f64>,
644 intensity_min: Option<f64>,
645 right_drag: Option<bool>,
646 ) -> TimsFrameAnnotated {
647 let ms_type = match self
648 .precursor_frame_builder
649 .precursor_frame_id_set
650 .contains(&frame_id)
651 {
652 false => MsType::FragmentDia,
653 true => MsType::Unknown,
654 };
655
656 let mut tims_spectra: Vec<TimsSpectrumAnnotated> = Vec::new();
657
658 if !self
659 .precursor_frame_builder
660 .frame_to_abundances
661 .contains_key(&frame_id)
662 {
663 return TimsFrameAnnotated::new(
664 frame_id as i32,
665 *self
666 .precursor_frame_builder
667 .frame_to_rt
668 .get(&frame_id)
669 .unwrap() as f64,
670 ms_type.clone(),
671 vec![],
672 vec![],
673 vec![],
674 vec![],
675 vec![],
676 vec![],
677 );
678 }
679
680 let (peptide_ids, frame_abundances) = self
681 .precursor_frame_builder
682 .frame_to_abundances
683 .get(&frame_id)
684 .unwrap();
685
686 for (peptide_id, frame_abundance) in peptide_ids.iter().zip(frame_abundances.iter()) {
687 if !self
688 .precursor_frame_builder
689 .peptide_to_ions
690 .contains_key(&peptide_id)
691 {
692 continue;
693 }
694
695 let (ion_abundances, scan_occurrences, scan_abundances, charges, _) = self
696 .precursor_frame_builder
697 .peptide_to_ions
698 .get(&peptide_id)
699 .unwrap();
700
701 for (index, ion_abundance) in ion_abundances.iter().enumerate() {
702 let all_scan_occurrence = scan_occurrences.get(index).unwrap();
703 let all_scan_abundance = scan_abundances.get(index).unwrap();
704
705 let peptide = self
706 .precursor_frame_builder
707 .peptides
708 .get(peptide_id)
709 .unwrap();
710 let ion = PeptideIon::new(
711 peptide.sequence.sequence.clone(),
712 charges[index] as i32,
713 *ion_abundance as f64,
714 Some(*peptide_id as i32),
715 );
716 let spectrum = ion.calculate_isotopic_spectrum_annotated(1e-3, 1e-8, 200, 1e-4);
718
719 for (scan, scan_abundance) in
720 all_scan_occurrence.iter().zip(all_scan_abundance.iter())
721 {
722 if !self.transmission_settings.any_transmitted(
723 frame_id as i32,
724 *scan as i32,
725 &spectrum.mz,
726 None,
727 ) {
728 continue;
729 }
730
731 let total_events = self
732 .precursor_frame_builder
733 .peptide_to_events
734 .get(&peptide_id)
735 .unwrap();
736 let fraction_events =
737 frame_abundance * scan_abundance * ion_abundance * total_events;
738
739 let maybe_pasef_meta = self.transmission_settings.pasef_meta.get(&(frame_id as i32));
741
742 let collision_energy: f64 = match maybe_pasef_meta {
743 Some(pasef_meta) => {
744 pasef_meta
745 .iter()
746 .find(|scan_meta| scan_meta.scan_start <= *scan as i32 && scan_meta.scan_end >= *scan as i32)
747 .map(|s| s.collision_energy)
748 .unwrap_or(0.0)
749 },
750 None => 0.0
751 };
752
753 let collision_energy_quantized = (collision_energy * 1e1).round() as i32;
754
755 let charge_state = charges.get(index).unwrap();
756 let maybe_value = fragment_ions.get(&(
757 *peptide_id,
758 *charge_state,
759 collision_energy_quantized,
760 ));
761
762 if maybe_value.is_none() {
763 continue;
764 }
765
766 for fragment_ion_series in maybe_value.unwrap().1.iter() {
767 let scaled_spec = fragment_ion_series.clone() * fraction_events as f64;
768 let right_drag = right_drag.unwrap_or(false);
769
770 let mz_spectrum = if mz_noise_fragment {
771 match uniform {
772 true => scaled_spec.add_mz_noise_uniform(fragment_ppm, right_drag),
773 false => scaled_spec.add_mz_noise_normal(fragment_ppm),
774 }
775 } else {
776 scaled_spec
777 };
778
779 tims_spectra.push(TimsSpectrumAnnotated::new(
780 frame_id as i32,
781 *scan,
782 *self
783 .precursor_frame_builder
784 .frame_to_rt
785 .get(&frame_id)
786 .unwrap() as f64,
787 *self
788 .precursor_frame_builder
789 .scan_to_mobility
790 .get(&scan)
791 .unwrap() as f64,
792 ms_type.clone(),
793 vec![0; mz_spectrum.mz.len()],
794 mz_spectrum,
795 ));
796 }
797 }
798 }
799 }
800
801 if tims_spectra.is_empty() {
802 return TimsFrameAnnotated::new(
803 frame_id as i32,
804 *self
805 .precursor_frame_builder
806 .frame_to_rt
807 .get(&frame_id)
808 .unwrap() as f64,
809 ms_type.clone(),
810 vec![],
811 vec![],
812 vec![],
813 vec![],
814 vec![],
815 vec![],
816 );
817 }
818
819 let tims_frame = TimsFrameAnnotated::from_tims_spectra_annotated(tims_spectra);
820
821 tims_frame.filter_ranged(
822 mz_min.unwrap_or(100.0),
823 mz_max.unwrap_or(1700.0),
824 0.0,
825 10.0,
826 0,
827 1000,
828 intensity_min.unwrap_or(1.0),
829 1e9,
830 )
831 }
832
833 pub fn get_collision_energy(&self, frame_id: i32, scan_id: i32) -> f64 {
834 self.transmission_settings.get_collision_energy(frame_id, scan_id).unwrap_or(0.0)
835 }
836
837 pub fn get_collision_energies(&self, frame_ids: Vec<i32>, scan_ids: Vec<i32>) -> Vec<f64> {
838 let mut collision_energies: Vec<f64> = Vec::new();
839 for frame_id in frame_ids {
840 for scan_id in &scan_ids {
841 collision_energies.push(self.get_collision_energy(frame_id, *scan_id));
842 }
843 }
844 collision_energies
845 }
846}