1use mscore::algorithm::isotope::{
2 calculate_precursor_transmission_factor,
3 calculate_transmission_dependent_fragment_ion_isotope_distribution,
4};
5use mscore::data::peptide::{PeptideIon, PeptideProductIonSeriesCollection};
6use mscore::data::spectrum::{IndexedMzSpectrum, MsType, MzSpectrum};
7use mscore::simulation::annotation::{
8 MzSpectrumAnnotated, PeakAnnotation, TimsFrameAnnotated, TimsSpectrumAnnotated,
9};
10use mscore::timstof::collision::{TimsTofCollisionEnergy, TimsTofCollisionEnergyDIA};
11use mscore::timstof::frame::TimsFrame;
12use mscore::timstof::quadrupole::{IonTransmission, TimsTransmissionDIA};
13use mscore::timstof::spectrum::TimsSpectrum;
14use std::collections::{BTreeMap, HashSet};
15use std::path::Path;
16use std::sync::Arc;
17
18use rand::Rng;
19use rayon::prelude::*;
20use rayon::ThreadPoolBuilder;
21
22use crate::sim::containers::{IsotopeTransmissionConfig, IsotopeTransmissionMode};
23use crate::sim::handle::{FragmentIonsWithComplementary, TimsTofSyntheticsDataHandle};
24use crate::sim::precursor::TimsTofSyntheticsPrecursorFrameBuilder;
25
26pub struct TimsTofSyntheticsFrameBuilderDIA {
27 pub path: String,
28 pub precursor_frame_builder: TimsTofSyntheticsPrecursorFrameBuilder,
29 pub transmission_settings: TimsTransmissionDIA,
30 pub fragmentation_settings: TimsTofCollisionEnergyDIA,
31 pub fragment_ions:
32 Option<BTreeMap<(u32, i8, i32), (PeptideProductIonSeriesCollection, Vec<MzSpectrum>)>>,
33 pub fragment_ions_annotated: Option<
34 BTreeMap<(u32, i8, i32), (PeptideProductIonSeriesCollection, Vec<MzSpectrumAnnotated>)>,
35 >,
36 pub isotope_config: IsotopeTransmissionConfig,
38 pub fragment_ions_with_transmission:
40 Option<BTreeMap<(u32, i8, i32), FragmentIonsWithComplementary>>,
41}
42
43impl TimsTofSyntheticsFrameBuilderDIA {
44 pub fn new(path: &Path, with_annotations: bool, num_threads: usize) -> rusqlite::Result<Self> {
45 Self::new_with_config(path, with_annotations, num_threads, IsotopeTransmissionConfig::default())
46 }
47
48 pub fn new_with_config(
49 path: &Path,
50 with_annotations: bool,
51 num_threads: usize,
52 isotope_config: IsotopeTransmissionConfig,
53 ) -> rusqlite::Result<Self> {
54 let synthetics = TimsTofSyntheticsPrecursorFrameBuilder::new(path)?;
55 let handle = TimsTofSyntheticsDataHandle::new(path)?;
56
57 let fragment_ions = handle.read_fragment_ions()?;
58
59 let fragmentation_settings = handle.get_collision_energy_dia();
61 let transmission_settings = handle.get_transmission_dia();
63
64 let fragment_ions_with_transmission = if isotope_config.is_enabled() {
66 Some(TimsTofSyntheticsDataHandle::build_fragment_ions_with_transmission_data(
67 &synthetics.peptides,
68 &fragment_ions,
69 num_threads,
70 ))
71 } else {
72 None
73 };
74
75 match with_annotations {
76 true => {
77 let fragment_ions_annotated =
78 Some(TimsTofSyntheticsDataHandle::build_fragment_ions_annotated(
79 &synthetics.peptides,
80 &fragment_ions,
81 num_threads,
82 ));
83 Ok(Self {
84 path: path.to_str().unwrap().to_string(),
85 precursor_frame_builder: synthetics,
86 transmission_settings,
87 fragmentation_settings,
88 fragment_ions: None,
89 fragment_ions_annotated,
90 isotope_config,
91 fragment_ions_with_transmission,
92 })
93 }
94
95 false => {
96 let fragment_ions = Some(TimsTofSyntheticsDataHandle::build_fragment_ions(
97 &synthetics.peptides,
98 &fragment_ions,
99 num_threads,
100 ));
101 Ok(Self {
102 path: path.to_str().unwrap().to_string(),
103 precursor_frame_builder: synthetics,
104 transmission_settings,
105 fragmentation_settings,
106 fragment_ions,
107 fragment_ions_annotated: None,
108 isotope_config,
109 fragment_ions_with_transmission,
110 })
111 }
112 }
113 }
114
115 pub fn build_frame(
127 &self,
128 frame_id: u32,
129 fragmentation: bool,
130 mz_noise_precursor: bool,
131 uniform: bool,
132 precursor_noise_ppm: f64,
133 mz_noise_fragment: bool,
134 fragment_noise_ppm: f64,
135 right_drag: bool,
136 ) -> TimsFrame {
137 match self
139 .precursor_frame_builder
140 .precursor_frame_id_set
141 .contains(&frame_id)
142 {
143 true => self.build_ms1_frame(
144 frame_id,
145 mz_noise_precursor,
146 uniform,
147 precursor_noise_ppm,
148 right_drag,
149 ),
150 false => self.build_ms2_frame(
151 frame_id,
152 fragmentation,
153 mz_noise_fragment,
154 uniform,
155 fragment_noise_ppm,
156 right_drag,
157 ),
158 }
159 }
160
161 pub fn build_frame_annotated(
162 &self,
163 frame_id: u32,
164 fragmentation: bool,
165 mz_noise_precursor: bool,
166 uniform: bool,
167 precursor_noise_ppm: f64,
168 mz_noise_fragment: bool,
169 fragment_noise_ppm: f64,
170 right_drag: bool,
171 ) -> TimsFrameAnnotated {
172 match self
173 .precursor_frame_builder
174 .precursor_frame_id_set
175 .contains(&frame_id)
176 {
177 true => self.build_ms1_frame_annotated(
178 frame_id,
179 mz_noise_precursor,
180 uniform,
181 precursor_noise_ppm,
182 right_drag,
183 ),
184 false => self.build_ms2_frame_annotated(
185 frame_id,
186 fragmentation,
187 mz_noise_fragment,
188 uniform,
189 fragment_noise_ppm,
190 right_drag,
191 ),
192 }
193 }
194
195 pub fn get_fragment_ion_ids(&self, precursor_frame_ids: Vec<u32>) -> Vec<u32> {
196 let mut peptide_ids: HashSet<u32> = HashSet::new();
197 for frame_id in precursor_frame_ids {
199 for (peptide_id, peptide) in self.precursor_frame_builder.peptides.iter() {
200 if peptide.frame_start <= frame_id && peptide.frame_end >= frame_id {
201 peptide_ids.insert(*peptide_id);
202 }
203 }
204 }
205 let mut result: Vec<u32> = Vec::new();
207 for item in peptide_ids {
208 let ions = self.precursor_frame_builder.ions.get(&item).unwrap();
209 for ion in ions.iter() {
210 result.push(ion.ion_id);
211 }
212 }
213 result
214 }
215
216 pub fn build_frames(
217 &self,
218 frame_ids: Vec<u32>,
219 fragmentation: bool,
220 mz_noise_precursor: bool,
221 uniform: bool,
222 precursor_noise_ppm: f64,
223 mz_noise_fragment: bool,
224 fragment_noise_ppm: f64,
225 right_drag: bool,
226 num_threads: usize,
227 ) -> Vec<TimsFrame> {
228 let pool = rayon::ThreadPoolBuilder::new()
230 .num_threads(num_threads)
231 .build()
232 .unwrap();
233
234 pool.install(|| {
235 let mut tims_frames: Vec<TimsFrame> = Vec::with_capacity(frame_ids.len());
237 unsafe { tims_frames.set_len(frame_ids.len()); }
239
240 frame_ids.par_iter().enumerate().for_each(|(idx, frame_id)| {
241 let frame = self.build_frame(
242 *frame_id,
243 fragmentation,
244 mz_noise_precursor,
245 uniform,
246 precursor_noise_ppm,
247 mz_noise_fragment,
248 fragment_noise_ppm,
249 right_drag,
250 );
251 unsafe {
253 let ptr = tims_frames.as_ptr() as *mut TimsFrame;
254 std::ptr::write(ptr.add(idx), frame);
255 }
256 });
257
258 tims_frames
259 })
260 }
261
262 pub fn build_frames_annotated(
263 &self,
264 frame_ids: Vec<u32>,
265 fragmentation: bool,
266 mz_noise_precursor: bool,
267 uniform: bool,
268 precursor_noise_ppm: f64,
269 mz_noise_fragment: bool,
270 fragment_noise_ppm: f64,
271 right_drag: bool,
272 num_threads: usize,
273 ) -> Vec<TimsFrameAnnotated> {
274 let pool = rayon::ThreadPoolBuilder::new()
276 .num_threads(num_threads)
277 .build()
278 .unwrap();
279
280 pool.install(|| {
281 let mut tims_frames: Vec<TimsFrameAnnotated> = Vec::with_capacity(frame_ids.len());
283 unsafe { tims_frames.set_len(frame_ids.len()); }
284
285 frame_ids.par_iter().enumerate().for_each(|(idx, frame_id)| {
286 let frame = self.build_frame_annotated(
287 *frame_id,
288 fragmentation,
289 mz_noise_precursor,
290 uniform,
291 precursor_noise_ppm,
292 mz_noise_fragment,
293 fragment_noise_ppm,
294 right_drag,
295 );
296 unsafe {
297 let ptr = tims_frames.as_ptr() as *mut TimsFrameAnnotated;
298 std::ptr::write(ptr.add(idx), frame);
299 }
300 });
301
302 tims_frames
303 })
304 }
305
306 fn build_ms1_frame(
307 &self,
308 frame_id: u32,
309 mz_noise_precursor: bool,
310 uniform: bool,
311 precursor_ppm: f64,
312 right_drag: bool,
313 ) -> TimsFrame {
314 let mut tims_frame = self.precursor_frame_builder.build_precursor_frame(
315 frame_id,
316 mz_noise_precursor,
317 uniform,
318 precursor_ppm,
319 right_drag,
320 );
321 let intensities_rounded = tims_frame
322 .ims_frame
323 .intensity
324 .iter()
325 .map(|x| x.round())
326 .collect::<Vec<_>>();
327 tims_frame.ims_frame.intensity = Arc::new(intensities_rounded);
328 tims_frame
329 }
330
331 fn build_ms1_frame_annotated(
332 &self,
333 frame_id: u32,
334 mz_noise_precursor: bool,
335 uniform: bool,
336 precursor_ppm: f64,
337 right_drag: bool,
338 ) -> TimsFrameAnnotated {
339 let mut tims_frame = self
340 .precursor_frame_builder
341 .build_precursor_frame_annotated(
342 frame_id,
343 mz_noise_precursor,
344 uniform,
345 precursor_ppm,
346 right_drag,
347 );
348 let intensities_rounded = tims_frame
349 .intensity
350 .iter()
351 .map(|x| x.round())
352 .collect::<Vec<_>>();
353 tims_frame.intensity = intensities_rounded;
354 tims_frame
355 }
356
357 fn build_ms2_frame(
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 ) -> TimsFrame {
366 match fragmentation {
367 false => {
368 let mut frame = self.transmission_settings.transmit_tims_frame(
369 &self.build_ms1_frame(
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 .ims_frame
380 .intensity
381 .iter()
382 .map(|x| x.round())
383 .collect::<Vec<_>>();
384 frame.ims_frame.intensity = Arc::new(intensities_rounded);
385 frame.ms_type = MsType::FragmentDia;
386 frame
387 }
388 true => {
389 let mut frame = self.build_fragment_frame(
390 frame_id,
391 &self.fragment_ions.as_ref().unwrap(),
392 mz_noise_fragment,
393 uniform,
394 fragment_ppm,
395 None,
396 None,
397 None,
398 Some(right_drag),
399 );
400 let intensities_rounded = frame
401 .ims_frame
402 .intensity
403 .iter()
404 .map(|x| x.round())
405 .collect::<Vec<_>>();
406 frame.ims_frame.intensity = Arc::new(intensities_rounded);
407 frame
408 }
409 }
410 }
411
412 fn build_ms2_frame_annotated(
413 &self,
414 frame_id: u32,
415 fragmentation: bool,
416 mz_noise_fragment: bool,
417 uniform: bool,
418 fragment_ppm: f64,
419 right_drag: bool,
420 ) -> TimsFrameAnnotated {
421 match fragmentation {
422 false => {
423 let mut frame = self.transmission_settings.transmit_tims_frame_annotated(
424 &self.build_ms1_frame_annotated(
425 frame_id,
426 mz_noise_fragment,
427 uniform,
428 fragment_ppm,
429 right_drag,
430 ),
431 None,
432 );
433 let intensities_rounded = frame
434 .intensity
435 .iter()
436 .map(|x| x.round())
437 .collect::<Vec<_>>();
438 frame.intensity = intensities_rounded;
439 frame.ms_type = MsType::FragmentDia;
440 frame
441 }
442 true => {
443 let mut frame = self.build_fragment_frame_annotated(
444 frame_id,
445 &self.fragment_ions_annotated.as_ref().unwrap(),
446 mz_noise_fragment,
447 uniform,
448 fragment_ppm,
449 None,
450 None,
451 None,
452 Some(right_drag),
453 );
454 let intensities_rounded = frame
455 .intensity
456 .iter()
457 .map(|x| x.round())
458 .collect::<Vec<_>>();
459 frame.intensity = intensities_rounded;
460 frame
461 }
462 }
463 }
464
465 fn build_fragment_frame(
479 &self,
480 frame_id: u32,
481 fragment_ions: &BTreeMap<
482 (u32, i8, i32),
483 (PeptideProductIonSeriesCollection, Vec<MzSpectrum>),
484 >,
485 mz_noise_fragment: bool,
486 uniform: bool,
487 fragment_ppm: f64,
488 mz_min: Option<f64>,
489 mz_max: Option<f64>,
490 intensity_min: Option<f64>,
491 right_drag: Option<bool>,
492 ) -> TimsFrame {
493 let ms_type = if self.precursor_frame_builder.precursor_frame_id_set.contains(&frame_id) {
495 MsType::Unknown
496 } else {
497 MsType::FragmentDia
498 };
499
500 let rt = *self.precursor_frame_builder.frame_to_rt.get(&frame_id).unwrap() as f64;
501 let right_drag_val = right_drag.unwrap_or(false);
502 let mz_min_val = mz_min.unwrap_or(100.0);
503 let mz_max_val = mz_max.unwrap_or(1700.0);
504 let intensity_min_val = intensity_min.unwrap_or(1.0);
505
506 let Some((peptide_ids, frame_abundances)) = self
508 .precursor_frame_builder
509 .frame_to_abundances
510 .get(&frame_id)
511 else {
512 return TimsFrame::new(frame_id as i32, ms_type, rt, vec![], vec![], vec![], vec![], vec![]);
513 };
514
515 let estimated_capacity = peptide_ids.len() * 4;
517 let mut tims_spectra: Vec<TimsSpectrum> = Vec::with_capacity(estimated_capacity);
518
519 for (peptide_id, frame_abundance) in peptide_ids.iter().zip(frame_abundances.iter()) {
521 let Some((ion_abundances, scan_occurrences, scan_abundances, charges, spectra)) = self
523 .precursor_frame_builder
524 .peptide_to_ions
525 .get(peptide_id)
526 else {
527 continue;
528 };
529
530 let total_events = *self.precursor_frame_builder.peptide_to_events.get(peptide_id).unwrap();
532
533 for (index, ion_abundance) in ion_abundances.iter().enumerate() {
534 let all_scan_occurrence = &scan_occurrences[index];
535 let all_scan_abundance = &scan_abundances[index];
536 let spectrum = &spectra[index];
537 let charge_state = charges[index];
538
539 for (scan, scan_abundance) in all_scan_occurrence.iter().zip(all_scan_abundance.iter()) {
540 let (is_transmitted, transmitted_indices) = match self.isotope_config.mode {
542 IsotopeTransmissionMode::None => {
543 let any = self.transmission_settings.any_transmitted(
545 frame_id as i32,
546 *scan as i32,
547 &spectrum.mz,
548 None,
549 );
550 (any, HashSet::new())
551 },
552 IsotopeTransmissionMode::PrecursorScaling | IsotopeTransmissionMode::PerFragment => {
553 let indices = self.transmission_settings.get_transmission_set(
554 frame_id as i32,
555 *scan as i32,
556 &spectrum.mz,
557 Some(self.isotope_config.min_probability),
558 );
559 (!indices.is_empty(), indices)
560 },
561 };
562
563 if !is_transmitted {
564 continue;
565 }
566
567 let fraction_events = frame_abundance * scan_abundance * ion_abundance * total_events;
569
570 let collision_energy = self.fragmentation_settings.get_collision_energy(frame_id as i32, *scan as i32);
572 let collision_energy_quantized = (collision_energy * 1e1).round() as i32;
573
574 let Some((_, fragment_series_vec)) = fragment_ions.get(&(*peptide_id, charge_state, collision_energy_quantized)) else {
576 continue;
577 };
578
579 let scan_mobility = *self.precursor_frame_builder.scan_to_mobility.get(scan).unwrap() as f64;
581
582 let transmission_factor = if self.isotope_config.mode == IsotopeTransmissionMode::PrecursorScaling {
584 if let Some(comp_data) = self.fragment_ions_with_transmission.as_ref() {
585 if let Some(frag_data) = comp_data.get(&(*peptide_id, charge_state, collision_energy_quantized)) {
586 calculate_precursor_transmission_factor(
587 &frag_data.precursor_isotope_distribution,
588 &transmitted_indices,
589 )
590 } else {
591 1.0
592 }
593 } else {
594 1.0
595 }
596 } else {
597 1.0
598 };
599
600 for (series_idx, fragment_ion_series) in fragment_series_vec.iter().enumerate() {
601 let final_spectrum = match self.isotope_config.mode {
602 IsotopeTransmissionMode::None => {
603 fragment_ion_series.clone() * fraction_events as f64
605 },
606 IsotopeTransmissionMode::PrecursorScaling => {
607 fragment_ion_series.clone() * (fraction_events as f64 * transmission_factor)
609 },
610 IsotopeTransmissionMode::PerFragment => {
611 if let Some(comp_data) = self.fragment_ions_with_transmission.as_ref() {
613 if let Some(frag_data) = comp_data.get(&(*peptide_id, charge_state, collision_energy_quantized)) {
614 if series_idx < frag_data.per_fragment_data.len() {
615 let series_data = &frag_data.per_fragment_data[series_idx];
617 let mut aggregated_mz: Vec<f64> = Vec::new();
618 let mut aggregated_intensity: Vec<f64> = Vec::new();
619
620 for frag_ion_data in series_data {
621 let adjusted_dist = calculate_transmission_dependent_fragment_ion_isotope_distribution(
623 &frag_ion_data.fragment_distribution,
624 &frag_ion_data.complementary_distribution,
625 &transmitted_indices,
626 self.isotope_config.max_isotopes,
627 );
628
629 for (mz, abundance) in adjusted_dist {
631 aggregated_mz.push(mz);
632 aggregated_intensity.push(abundance * frag_ion_data.predicted_intensity * fraction_events as f64);
633 }
634 }
635
636 if !aggregated_mz.is_empty() {
637 MzSpectrum::new(aggregated_mz, aggregated_intensity)
638 } else {
639 fragment_ion_series.clone() * fraction_events as f64
640 }
641 } else {
642 fragment_ion_series.clone() * fraction_events as f64
643 }
644 } else {
645 fragment_ion_series.clone() * fraction_events as f64
646 }
647 } else {
648 fragment_ion_series.clone() * fraction_events as f64
649 }
650 },
651 };
652
653 let mz_spectrum = if mz_noise_fragment {
654 if uniform {
655 final_spectrum.add_mz_noise_uniform(fragment_ppm, right_drag_val)
656 } else {
657 final_spectrum.add_mz_noise_normal(fragment_ppm)
658 }
659 } else {
660 final_spectrum
661 };
662
663 let spectrum_len = mz_spectrum.mz.len();
664 tims_spectra.push(TimsSpectrum::new(
665 frame_id as i32,
666 *scan as i32,
667 rt,
668 scan_mobility,
669 ms_type.clone(),
670 IndexedMzSpectrum::from_mz_spectrum(
671 vec![0; spectrum_len],
672 mz_spectrum,
673 ).filter_ranged(100.0, 1700.0, 1.0, 1e9),
674 ));
675 }
676
677 if self.isotope_config.has_precursor_survival() {
679 let mut rng = rand::thread_rng();
680 let survival_fraction = rng.gen_range(
681 self.isotope_config.precursor_survival_min
682 ..=self.isotope_config.precursor_survival_max
683 );
684
685 if survival_fraction > 0.0 {
686 let precursor_transmitted = self.transmission_settings.transmit_spectrum(
688 frame_id as i32,
689 *scan as i32,
690 spectrum.clone(),
691 Some(self.isotope_config.min_probability),
692 );
693
694 if !precursor_transmitted.mz.is_empty() {
695 let precursor_scaled = precursor_transmitted * (fraction_events as f64 * survival_fraction);
697
698 let precursor_mz_spectrum = if mz_noise_fragment {
699 if uniform {
700 precursor_scaled.add_mz_noise_uniform(fragment_ppm, right_drag_val)
701 } else {
702 precursor_scaled.add_mz_noise_normal(fragment_ppm)
703 }
704 } else {
705 precursor_scaled
706 };
707
708 let precursor_len = precursor_mz_spectrum.mz.len();
709 tims_spectra.push(TimsSpectrum::new(
710 frame_id as i32,
711 *scan as i32,
712 rt,
713 scan_mobility,
714 ms_type.clone(),
715 IndexedMzSpectrum::from_mz_spectrum(
716 vec![0; precursor_len],
717 precursor_mz_spectrum,
718 ).filter_ranged(100.0, 1700.0, 1.0, 1e9),
719 ));
720 }
721 }
722 }
723 }
724 }
725 }
726
727 if tims_spectra.is_empty() {
728 return TimsFrame::new(frame_id as i32, ms_type, rt, vec![], vec![], vec![], vec![], vec![]);
729 }
730
731 let tims_frame = TimsFrame::from_tims_spectra(tims_spectra);
732 tims_frame.filter_ranged(
733 mz_min_val,
734 mz_max_val,
735 0,
736 1000,
737 0.0,
738 10.0,
739 intensity_min_val,
740 1e9,
741 0,
742 i32::MAX,
743 )
744 }
745
746 pub fn build_fragment_frame_annotated(
747 &self,
748 frame_id: u32,
749 fragment_ions: &BTreeMap<
750 (u32, i8, i32),
751 (PeptideProductIonSeriesCollection, Vec<MzSpectrumAnnotated>),
752 >,
753 mz_noise_fragment: bool,
754 uniform: bool,
755 fragment_ppm: f64,
756 mz_min: Option<f64>,
757 mz_max: Option<f64>,
758 intensity_min: Option<f64>,
759 right_drag: Option<bool>,
760 ) -> TimsFrameAnnotated {
761 let ms_type = if self.precursor_frame_builder.precursor_frame_id_set.contains(&frame_id) {
763 MsType::Unknown
764 } else {
765 MsType::FragmentDia
766 };
767
768 let rt = *self.precursor_frame_builder.frame_to_rt.get(&frame_id).unwrap() as f64;
769 let right_drag_val = right_drag.unwrap_or(false);
770 let mz_min_val = mz_min.unwrap_or(100.0);
771 let mz_max_val = mz_max.unwrap_or(1700.0);
772 let intensity_min_val = intensity_min.unwrap_or(1.0);
773
774 let Some((peptide_ids, frame_abundances)) = self
776 .precursor_frame_builder
777 .frame_to_abundances
778 .get(&frame_id)
779 else {
780 return TimsFrameAnnotated::new(frame_id as i32, rt, ms_type, vec![], vec![], vec![], vec![], vec![], vec![]);
781 };
782
783 let estimated_capacity = peptide_ids.len() * 4;
785 let mut tims_spectra: Vec<TimsSpectrumAnnotated> = Vec::with_capacity(estimated_capacity);
786
787 for (peptide_id, frame_abundance) in peptide_ids.iter().zip(frame_abundances.iter()) {
788 let Some((ion_abundances, scan_occurrences, scan_abundances, charges, _)) = self
790 .precursor_frame_builder
791 .peptide_to_ions
792 .get(peptide_id)
793 else {
794 continue;
795 };
796
797 let total_events = *self.precursor_frame_builder.peptide_to_events.get(peptide_id).unwrap();
799 let peptide = self.precursor_frame_builder.peptides.get(peptide_id).unwrap();
800
801 for (index, ion_abundance) in ion_abundances.iter().enumerate() {
802 let all_scan_occurrence = &scan_occurrences[index];
803 let all_scan_abundance = &scan_abundances[index];
804 let charge_state = charges[index];
805
806 let ion = PeptideIon::new(
807 peptide.sequence.sequence.clone(),
808 charge_state as i32,
809 *ion_abundance as f64,
810 Some(*peptide_id as i32),
811 );
812 let spectrum = ion.calculate_isotopic_spectrum_annotated(1e-3, 1e-8, 200, 1e-4);
814
815 for (scan, scan_abundance) in all_scan_occurrence.iter().zip(all_scan_abundance.iter()) {
816 if !self.transmission_settings.any_transmitted(
817 frame_id as i32,
818 *scan as i32,
819 &spectrum.mz,
820 None,
821 ) {
822 continue;
823 }
824
825 let fraction_events = frame_abundance * scan_abundance * ion_abundance * total_events;
826
827 let collision_energy = self.fragmentation_settings.get_collision_energy(frame_id as i32, *scan as i32);
828 let collision_energy_quantized = (collision_energy * 1e1).round() as i32;
829
830 let Some((_, fragment_series_vec)) = fragment_ions.get(&(*peptide_id, charge_state, collision_energy_quantized)) else {
831 continue;
832 };
833
834 let scan_mobility = *self.precursor_frame_builder.scan_to_mobility.get(scan).unwrap() as f64;
836
837 for fragment_ion_series in fragment_series_vec.iter() {
838 let scaled_spec = fragment_ion_series.clone() * fraction_events as f64;
839
840 let mz_spectrum = if mz_noise_fragment {
841 if uniform {
842 scaled_spec.add_mz_noise_uniform(fragment_ppm, right_drag_val)
843 } else {
844 scaled_spec.add_mz_noise_normal(fragment_ppm)
845 }
846 } else {
847 scaled_spec
848 };
849
850 let spectrum_len = mz_spectrum.mz.len();
851 tims_spectra.push(TimsSpectrumAnnotated::new(
852 frame_id as i32,
853 *scan,
854 rt,
855 scan_mobility,
856 ms_type.clone(),
857 vec![0; spectrum_len],
858 mz_spectrum,
859 ));
860 }
861
862 if self.isotope_config.has_precursor_survival() {
864 let mut rng = rand::thread_rng();
865 let survival_fraction = rng.gen_range(
866 self.isotope_config.precursor_survival_min
867 ..=self.isotope_config.precursor_survival_max
868 );
869
870 if survival_fraction > 0.0 {
871 let precursor_mz_spectrum = MzSpectrum::new(spectrum.mz.clone(), spectrum.intensity.clone());
873
874 let precursor_transmitted = self.transmission_settings.transmit_spectrum(
876 frame_id as i32,
877 *scan as i32,
878 precursor_mz_spectrum,
879 Some(self.isotope_config.min_probability),
880 );
881
882 if !precursor_transmitted.mz.is_empty() {
883 let precursor_scaled = precursor_transmitted * (fraction_events as f64 * survival_fraction);
885
886 let precursor_final = if mz_noise_fragment {
887 if uniform {
888 precursor_scaled.add_mz_noise_uniform(fragment_ppm, right_drag_val)
889 } else {
890 precursor_scaled.add_mz_noise_normal(fragment_ppm)
891 }
892 } else {
893 precursor_scaled
894 };
895
896 let annotations: Vec<PeakAnnotation> = precursor_final.mz.iter()
898 .map(|_| PeakAnnotation { contributions: vec![] })
899 .collect();
900 let precursor_annotated = MzSpectrumAnnotated::new(
901 precursor_final.mz.to_vec(),
902 precursor_final.intensity.to_vec(),
903 annotations,
904 );
905
906 let precursor_len = precursor_annotated.mz.len();
907 tims_spectra.push(TimsSpectrumAnnotated::new(
908 frame_id as i32,
909 *scan,
910 rt,
911 scan_mobility,
912 ms_type.clone(),
913 vec![0; precursor_len],
914 precursor_annotated,
915 ));
916 }
917 }
918 }
919 }
920 }
921 }
922
923 if tims_spectra.is_empty() {
924 return TimsFrameAnnotated::new(frame_id as i32, rt, ms_type, vec![], vec![], vec![], vec![], vec![], vec![]);
925 }
926
927 TimsFrameAnnotated::from_tims_spectra_annotated(tims_spectra).filter_ranged(
928 mz_min_val, mz_max_val, 0.0, 10.0, 0, 1000, intensity_min_val, 1e9,
929 )
930 }
931
932 pub fn get_ion_transmission_matrix(
933 &self,
934 peptide_id: u32,
935 charge: i8,
936 include_precursor_frames: bool,
937 ) -> Vec<Vec<f32>> {
938 let maybe_peptide_sim = self.precursor_frame_builder.peptides.get(&peptide_id);
939
940 let mut frame_ids = match maybe_peptide_sim {
941 Some(maybe_peptide_sim) => maybe_peptide_sim.frame_distribution.occurrence.clone(),
942 _ => vec![],
943 };
944
945 if !include_precursor_frames {
946 frame_ids = frame_ids
947 .iter()
948 .filter(|frame_id| {
949 !self
950 .precursor_frame_builder
951 .precursor_frame_id_set
952 .contains(frame_id)
953 })
954 .cloned()
955 .collect();
956 }
957
958 let ion = self
959 .precursor_frame_builder
960 .ions
961 .get(&peptide_id)
962 .unwrap()
963 .iter()
964 .find(|ion| ion.charge == charge)
965 .unwrap();
966 let spectrum = ion.simulated_spectrum.clone();
967 let scan_distribution = &ion.scan_distribution;
968
969 let mut transmission_matrix =
970 vec![vec![0.0; frame_ids.len()]; scan_distribution.occurrence.len()];
971
972 for (frame_index, frame) in frame_ids.iter().enumerate() {
973 for (scan_index, scan) in scan_distribution.occurrence.iter().enumerate() {
974 if self.transmission_settings.all_transmitted(
975 *frame as i32,
976 *scan as i32,
977 &spectrum.mz,
978 None,
979 ) {
980 transmission_matrix[scan_index][frame_index] = 1.0;
981 } else if self.transmission_settings.any_transmitted(
982 *frame as i32,
983 *scan as i32,
984 &spectrum.mz,
985 None,
986 ) {
987 let transmitted_spectrum = self.transmission_settings.transmit_spectrum(
988 *frame as i32,
989 *scan as i32,
990 spectrum.clone(),
991 None,
992 );
993 let percentage_transmitted = transmitted_spectrum.intensity.iter().sum::<f64>()
994 / spectrum.intensity.iter().sum::<f64>();
995 transmission_matrix[scan_index][frame_index] = percentage_transmitted as f32;
996 }
997 }
998 }
999
1000 transmission_matrix
1001 }
1002
1003 pub fn count_number_transmissions(&self, peptide_id: u32, charge: i8) -> (usize, usize) {
1004 let frame_ids: Vec<_> = self
1005 .precursor_frame_builder
1006 .peptides
1007 .get(&peptide_id)
1008 .unwrap()
1009 .frame_distribution
1010 .occurrence
1011 .clone()
1012 .iter()
1013 .filter(|frame_id| {
1014 !self
1015 .precursor_frame_builder
1016 .precursor_frame_id_set
1017 .contains(frame_id)
1018 })
1019 .cloned()
1020 .collect();
1021 let ion = self
1022 .precursor_frame_builder
1023 .ions
1024 .get(&peptide_id)
1025 .unwrap()
1026 .iter()
1027 .find(|ion| ion.charge == charge)
1028 .unwrap();
1029 let spectrum = ion.simulated_spectrum.clone();
1030 let scan_distribution = &ion.scan_distribution;
1031
1032 let mut frame_count = 0;
1033 let mut scan_count = 0;
1034
1035 for frame in frame_ids.iter() {
1036 let mut frame_transmitted = false;
1037 for scan in scan_distribution.occurrence.iter() {
1038 if self.transmission_settings.any_transmitted(
1039 *frame as i32,
1040 *scan as i32,
1041 &spectrum.mz,
1042 None,
1043 ) {
1044 frame_transmitted = true;
1045 scan_count += 1;
1046 }
1047 }
1048 if frame_transmitted {
1049 frame_count += 1;
1050 }
1051 }
1052
1053 (frame_count, scan_count)
1054 }
1055
1056 pub fn count_number_transmissions_parallel(
1057 &self,
1058 peptide_ids: Vec<u32>,
1059 charge: Vec<i8>,
1060 num_threads: usize,
1061 ) -> Vec<(usize, usize)> {
1062 let thread_pool = ThreadPoolBuilder::new()
1063 .num_threads(num_threads)
1064 .build()
1065 .unwrap();
1066 let result: Vec<(usize, usize)> = thread_pool.install(|| {
1067 peptide_ids
1068 .par_iter()
1069 .zip(charge.par_iter())
1070 .map(|(peptide_id, charge)| self.count_number_transmissions(*peptide_id, *charge))
1071 .collect()
1072 });
1073
1074 result
1075 }
1076}
1077
1078impl TimsTofCollisionEnergy for TimsTofSyntheticsFrameBuilderDIA {
1079 fn get_collision_energy(&self, frame_id: i32, scan_id: i32) -> f64 {
1080 self.fragmentation_settings
1081 .get_collision_energy(frame_id, scan_id)
1082 }
1083}