1use std::collections::{HashMap};
2use bincode::{Decode, Encode};
3use itertools::Itertools;
4use regex::Regex;
5use serde::{Deserialize, Serialize};
6use crate::algorithm::peptide::{calculate_peptide_mono_isotopic_mass, calculate_peptide_product_ion_mono_isotopic_mass, peptide_sequence_to_atomic_composition};
7use crate::chemistry::amino_acid::{amino_acid_masses};
8use crate::chemistry::formulas::calculate_mz;
9use crate::chemistry::utility::{find_unimod_patterns, reshape_prosit_array, unimod_sequence_to_tokens};
10use crate::data::spectrum::MzSpectrum;
11use crate::simulation::annotation::{MzSpectrumAnnotated, ContributionSource, SignalAttributes, SourceType, PeakAnnotation};
12
13type Mass = f64;
15type Abundance = f64;
16type IsotopeDistribution = Vec<(Mass, Abundance)>;
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct PeptideIon {
20 pub sequence: PeptideSequence,
21 pub charge: i32,
22 pub intensity: f64,
23}
24
25impl PeptideIon {
26 pub fn new(sequence: String, charge: i32, intensity: f64, peptide_id: Option<i32>) -> Self {
27 PeptideIon {
28 sequence: PeptideSequence::new(sequence, peptide_id),
29 charge,
30 intensity,
31 }
32 }
33 pub fn mz(&self) -> f64 {
34 calculate_mz(self.sequence.mono_isotopic_mass(), self.charge)
35 }
36
37 pub fn calculate_isotope_distribution(
38 &self,
39 mass_tolerance: f64,
40 abundance_threshold: f64,
41 max_result: i32,
42 intensity_min: f64,
43 ) -> IsotopeDistribution {
44
45 let atomic_composition: HashMap<String, i32> = self.sequence.atomic_composition().iter().map(|(k, v)| (k.to_string(), *v)).collect();
46
47 let distribution: IsotopeDistribution = crate::algorithm::isotope::generate_isotope_distribution(&atomic_composition, mass_tolerance, abundance_threshold, max_result)
48 .into_iter().filter(|&(_, abundance)| abundance > intensity_min).collect();
49
50 let mz_distribution = distribution.iter().map(|(mass, _)| calculate_mz(*mass, self.charge))
51 .zip(distribution.iter().map(|&(_, abundance)| abundance)).collect();
52
53 mz_distribution
54 }
55
56 pub fn calculate_isotopic_spectrum(
57 &self,
58 mass_tolerance: f64,
59 abundance_threshold: f64,
60 max_result: i32,
61 intensity_min: f64,
62 ) -> MzSpectrum {
63 let isotopic_distribution = self.calculate_isotope_distribution(mass_tolerance, abundance_threshold, max_result, intensity_min);
64 MzSpectrum::new(isotopic_distribution.iter().map(|(mz, _)| *mz).collect(), isotopic_distribution.iter().map(|(_, abundance)| *abundance).collect()) * self.intensity
65 }
66
67 pub fn calculate_isotopic_spectrum_annotated(
68 &self,
69 mass_tolerance: f64,
70 abundance_threshold: f64,
71 max_result: i32,
72 intensity_min: f64,
73 ) -> MzSpectrumAnnotated {
74 let isotopic_distribution = self.calculate_isotope_distribution(mass_tolerance, abundance_threshold, max_result, intensity_min);
75 let mut annotations = Vec::new();
76 let mut isotope_counter = 0;
77 let mut previous_mz = isotopic_distribution[0].0;
78
79
80
81 for (mz, abundance) in isotopic_distribution.iter() {
82
83 let ppm_tolerance = (mz / 1e6) * 25.0;
84
85 if (mz - previous_mz).abs() > ppm_tolerance {
86 isotope_counter += 1;
87 previous_mz = *mz;
88 }
89
90 let signal_attributes = SignalAttributes {
91 charge_state: self.charge,
92 peptide_id: self.sequence.peptide_id.unwrap_or(-1),
93 isotope_peak: isotope_counter,
94 description: None,
95 };
96
97 let contribution_source = ContributionSource {
98 intensity_contribution: *abundance,
99 source_type: SourceType::Signal,
100 signal_attributes: Some(signal_attributes)
101 };
102
103 annotations.push(PeakAnnotation {
104 contributions: vec![contribution_source]
105 });
106 }
107
108 MzSpectrumAnnotated::new(isotopic_distribution.iter().map(|(mz, _)| *mz).collect(), isotopic_distribution.iter().map(|(_, abundance)| *abundance).collect(), annotations)
109 }
110}
111
112#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
113pub enum FragmentType { A, B, C, X, Y, Z, }
114
115impl std::fmt::Display for FragmentType {
117 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
118 match self {
119 FragmentType::A => write!(f, "a"),
120 FragmentType::B => write!(f, "b"),
121 FragmentType::C => write!(f, "c"),
122 FragmentType::X => write!(f, "x"),
123 FragmentType::Y => write!(f, "y"),
124 FragmentType::Z => write!(f, "z"),
125 }
126 }
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct PeptideProductIon {
131 pub kind: FragmentType,
132 pub ion: PeptideIon,
133}
134
135impl PeptideProductIon {
136 pub fn new(kind: FragmentType, sequence: String, charge: i32, intensity: f64, peptide_id: Option<i32>) -> Self {
137 PeptideProductIon {
138 kind,
139 ion: PeptideIon {
140 sequence: PeptideSequence::new(sequence, peptide_id),
141 charge,
142 intensity,
143 },
144 }
145 }
146
147 pub fn mono_isotopic_mass(&self) -> f64 {
148 calculate_peptide_product_ion_mono_isotopic_mass(self.ion.sequence.sequence.as_str(), self.kind)
149 }
150
151 pub fn atomic_composition(&self) -> HashMap<&str, i32> {
152
153 let mut composition = peptide_sequence_to_atomic_composition(&self.ion.sequence);
154
155 match self.kind {
156 FragmentType::A => {
157 *composition.entry("H").or_insert(0) -= 2;
158 *composition.entry("O").or_insert(0) -= 2;
159 *composition.entry("C").or_insert(0) -= 1;
160 },
161
162 FragmentType::B => {
163 *composition.entry("H").or_insert(0) -= 2;
165 *composition.entry("O").or_insert(0) -= 1;
166 },
167
168 FragmentType::C => {
169 *composition.entry("H").or_insert(0) += 1;
171 *composition.entry("N").or_insert(0) += 1;
172 *composition.entry("O").or_insert(0) -= 1;
173 },
174
175 FragmentType::X => {
176 *composition.entry("C").or_insert(0) += 1;
178 *composition.entry("O").or_insert(0) += 1;
179 },
180
181 FragmentType::Y => {
182 ()
183 },
184
185 FragmentType::Z => {
186 *composition.entry("H").or_insert(0) -= 1;
187 *composition.entry("N").or_insert(0) -= 3;
188 },
189 }
190 composition
191 }
192
193 pub fn mz(&self) -> f64 {
194 calculate_mz(self.mono_isotopic_mass(), self.ion.charge)
195 }
196
197 pub fn isotope_distribution(
198 &self,
199 mass_tolerance: f64,
200 abundance_threshold: f64,
201 max_result: i32,
202 intensity_min: f64,
203 ) -> IsotopeDistribution {
204
205 let atomic_composition: HashMap<String, i32> = self.atomic_composition().iter().map(|(k, v)| (k.to_string(), *v)).collect();
206
207 let distribution: IsotopeDistribution = crate::algorithm::isotope::generate_isotope_distribution(&atomic_composition, mass_tolerance, abundance_threshold, max_result)
208 .into_iter().filter(|&(_, abundance)| abundance > intensity_min).collect();
209
210 let mz_distribution = distribution.iter().map(|(mass, _)| calculate_mz(*mass, self.ion.charge)).zip(distribution.iter().map(|&(_, abundance)| abundance)).collect();
211
212 mz_distribution
213 }
214
215 pub fn complementary_isotope_distribution(
232 &self,
233 precursor_composition: &HashMap<&str, i32>,
234 mass_tolerance: f64,
235 abundance_threshold: f64,
236 max_result: i32,
237 ) -> Vec<(f64, f64)> {
238 let fragment_composition = self.atomic_composition();
239 let complementary_composition = crate::algorithm::peptide::calculate_complementary_fragment_composition(
240 precursor_composition,
241 &fragment_composition,
242 );
243
244 crate::algorithm::isotope::generate_isotope_distribution(
245 &complementary_composition,
246 mass_tolerance,
247 abundance_threshold,
248 max_result,
249 )
250 }
251}
252
253#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
254pub struct PeptideSequence {
255 pub sequence: String,
256 pub peptide_id: Option<i32>,
257}
258
259impl PeptideSequence {
260 pub fn new(raw_sequence: String, peptide_id: Option<i32>) -> Self {
261
262 let pattern = Regex::new(r"\[UNIMOD:(\d+)]").unwrap();
264
265 let sequence = pattern.replace_all(&raw_sequence, "").to_string();
267
268 let valid_amino_acids = sequence.chars().all(|c| amino_acid_masses().contains_key(&c.to_string()[..]));
270 if !valid_amino_acids {
271 panic!("Invalid amino acid sequence, use only valid amino acids: ARNDCQEGHILKMFPSTWYVU, and modifications in the format [UNIMOD:ID]");
272 }
273
274 PeptideSequence { sequence: raw_sequence, peptide_id }
275 }
276
277 pub fn mono_isotopic_mass(&self) -> f64 {
278 calculate_peptide_mono_isotopic_mass(self)
279 }
280
281 pub fn atomic_composition(&self) -> HashMap<&str, i32> {
282 peptide_sequence_to_atomic_composition(self)
283 }
284
285 pub fn to_tokens(&self, group_modifications: bool) -> Vec<String> {
286 unimod_sequence_to_tokens(&*self.sequence, group_modifications)
287 }
288
289 pub fn to_sage_representation(&self) -> (String, Vec<f64>) {
290 find_unimod_patterns(&*self.sequence)
291 }
292
293 pub fn amino_acid_count(&self) -> usize {
294 self.to_tokens(true).len()
295 }
296
297 pub fn calculate_mono_isotopic_product_ion_spectrum(&self, charge: i32, fragment_type: FragmentType) -> MzSpectrum {
298 let product_ions = self.calculate_product_ion_series(charge, fragment_type);
299 product_ions.generate_mono_isotopic_spectrum()
300 }
301
302 pub fn calculate_mono_isotopic_product_ion_spectrum_annotated(&self, charge: i32, fragment_type: FragmentType) -> MzSpectrumAnnotated {
303 let product_ions = self.calculate_product_ion_series(charge, fragment_type);
304 product_ions.generate_mono_isotopic_spectrum_annotated()
305 }
306
307 pub fn calculate_isotopic_product_ion_spectrum(&self, charge: i32, fragment_type: FragmentType, mass_tolerance: f64, abundance_threshold: f64, max_result: i32, intensity_min: f64) -> MzSpectrum {
308 let product_ions = self.calculate_product_ion_series(charge, fragment_type);
309 product_ions.generate_isotopic_spectrum(mass_tolerance, abundance_threshold, max_result, intensity_min)
310 }
311
312 pub fn calculate_isotopic_product_ion_spectrum_annotated(&self, charge: i32, fragment_type: FragmentType, mass_tolerance: f64, abundance_threshold: f64, max_result: i32, intensity_min: f64) -> MzSpectrumAnnotated {
313 let product_ions = self.calculate_product_ion_series(charge, fragment_type);
314 product_ions.generate_isotopic_spectrum_annotated(mass_tolerance, abundance_threshold, max_result, intensity_min)
315 }
316
317 pub fn calculate_product_ion_series(&self, target_charge: i32, fragment_type: FragmentType) -> PeptideProductIonSeries {
318 let tokens = unimod_sequence_to_tokens(self.sequence.as_str(), true);
320 let mut n_terminal_ions = Vec::new();
321 let mut c_terminal_ions = Vec::new();
322
323 for i in 1..tokens.len() {
325 let n_ion_seq = tokens[..i].join("");
326 n_terminal_ions.push(PeptideProductIon {
327 kind: match fragment_type {
328 FragmentType::A => FragmentType::A,
329 FragmentType::B => FragmentType::B,
330 FragmentType::C => FragmentType::C,
331 FragmentType::X => FragmentType::A,
332 FragmentType::Y => FragmentType::B,
333 FragmentType::Z => FragmentType::C,
334 },
335 ion: PeptideIon {
336 sequence: PeptideSequence {
337 sequence: n_ion_seq,
338 peptide_id: self.peptide_id,
339 },
340 charge: target_charge,
341 intensity: 1.0, },
343 });
344 }
345
346 for i in 1..tokens.len() {
348 let c_ion_seq = tokens[tokens.len() - i..].join("");
349 c_terminal_ions.push(PeptideProductIon {
350 kind: match fragment_type {
351 FragmentType::A => FragmentType::X,
352 FragmentType::B => FragmentType::Y,
353 FragmentType::C => FragmentType::Z,
354 FragmentType::X => FragmentType::X,
355 FragmentType::Y => FragmentType::Y,
356 FragmentType::Z => FragmentType::Z,
357 },
358 ion: PeptideIon {
359 sequence: PeptideSequence {
360 sequence: c_ion_seq,
361 peptide_id: self.peptide_id,
362 },
363 charge: target_charge,
364 intensity: 1.0, },
366 });
367 }
368
369 PeptideProductIonSeries::new(target_charge, n_terminal_ions, c_terminal_ions)
370 }
371
372 pub fn associate_with_predicted_intensities(
373 &self,
374 charge: i32,
376 fragment_type: FragmentType,
377 flat_intensities: Vec<f64>,
378 normalize: bool,
379 half_charge_one: bool,
380 ) -> PeptideProductIonSeriesCollection {
381
382 let reshaped_intensities = reshape_prosit_array(flat_intensities);
383 let max_charge = std::cmp::min(charge, 3).max(1); let mut sum_intensity = if normalize { 0.0 } else { 1.0 };
385 let num_tokens = self.amino_acid_count() - 1; let mut peptide_ion_collection = Vec::new();
388
389 if normalize {
390 for z in 1..=max_charge {
391
392 let intensity_c: Vec<f64> = reshaped_intensities[..num_tokens].iter().map(|x| x[0][z as usize - 1]).filter(|&x| x > 0.0).collect();
393 let intensity_n: Vec<f64> = reshaped_intensities[..num_tokens].iter().map(|x| x[1][z as usize - 1]).filter(|&x| x > 0.0).collect();
394
395 sum_intensity += intensity_n.iter().sum::<f64>() + intensity_c.iter().sum::<f64>();
396 }
397 }
398
399 for z in 1..=max_charge {
400
401 let mut product_ions = self.calculate_product_ion_series(z, fragment_type);
402 let intensity_n: Vec<f64> = reshaped_intensities[..num_tokens].iter().map(|x| x[1][z as usize - 1]).collect();
403 let intensity_c: Vec<f64> = reshaped_intensities[..num_tokens].iter().map(|x| x[0][z as usize - 1]).collect(); let adjusted_sum_intensity = if max_charge == 1 && half_charge_one { sum_intensity * 2.0 } else { sum_intensity };
406
407 for (i, ion) in product_ions.n_ions.iter_mut().enumerate() {
408 ion.ion.intensity = intensity_n[i] / adjusted_sum_intensity;
409 }
410 for (i, ion) in product_ions.c_ions.iter_mut().enumerate() {
411 ion.ion.intensity = intensity_c[i] / adjusted_sum_intensity;
412 }
413
414 peptide_ion_collection.push(PeptideProductIonSeries::new(z, product_ions.n_ions, product_ions.c_ions));
415 }
416
417 PeptideProductIonSeriesCollection::new(peptide_ion_collection)
418 }
419}
420
421#[derive(Debug, Clone, Serialize, Deserialize)]
422pub struct PeptideProductIonSeries {
423 pub charge: i32,
424 pub n_ions: Vec<PeptideProductIon>,
425 pub c_ions: Vec<PeptideProductIon>,
426}
427
428impl PeptideProductIonSeries {
429 pub fn new(charge: i32, n_ions: Vec<PeptideProductIon>, c_ions: Vec<PeptideProductIon>) -> Self {
430 PeptideProductIonSeries {
431 charge,
432 n_ions,
433 c_ions,
434 }
435 }
436
437 pub fn generate_mono_isotopic_spectrum(&self) -> MzSpectrum {
438 let mz_i_n = self.n_ions.iter().map(|ion| (ion.mz(), ion.ion.intensity)).collect_vec();
439 let mz_i_c = self.c_ions.iter().map(|ion| (ion.mz(), ion.ion.intensity)).collect_vec();
440 let n_spectrum = MzSpectrum::new(mz_i_n.iter().map(|(mz, _)| *mz).collect(), mz_i_n.iter().map(|(_, abundance)| *abundance).collect());
441 let c_spectrum = MzSpectrum::new(mz_i_c.iter().map(|(mz, _)| *mz).collect(), mz_i_c.iter().map(|(_, abundance)| *abundance).collect());
442 MzSpectrum::from_collection(vec![n_spectrum, c_spectrum]).filter_ranged(0.0, 5_000.0, 1e-6, 1e6)
443 }
444
445 pub fn generate_mono_isotopic_spectrum_annotated(&self) -> MzSpectrumAnnotated {
446 let mut annotations: Vec<PeakAnnotation> = Vec::with_capacity(self.n_ions.len() + self.c_ions.len());
447 let mut mz_values = Vec::with_capacity(self.n_ions.len() + self.c_ions.len());
448 let mut intensity_values = Vec::with_capacity(self.n_ions.len() + self.c_ions.len());
449
450 for (index, n_ion) in self.n_ions.iter().enumerate() {
451 let kind = n_ion.kind;
452 let charge = n_ion.ion.charge;
453 let mz = n_ion.mz();
454 let intensity = n_ion.ion.intensity;
455 let signal_attributes = SignalAttributes {
456 charge_state: charge,
457 peptide_id: n_ion.ion.sequence.peptide_id.unwrap_or(-1),
458 isotope_peak: 0,
459 description: Some(format!("{}_{}_{}", kind, index + 1, 0)),
460 };
461 let contribution_source = ContributionSource {
462 intensity_contribution: intensity,
463 source_type: SourceType::Signal,
464 signal_attributes: Some(signal_attributes)
465 };
466
467 annotations.push(PeakAnnotation {
468 contributions: vec![contribution_source]
469 });
470 mz_values.push(mz);
471 intensity_values.push(intensity);
472 }
473
474 for (index, c_ion) in self.c_ions.iter().enumerate() {
475 let kind = c_ion.kind;
476 let charge = c_ion.ion.charge;
477 let mz = c_ion.mz();
478 let intensity = c_ion.ion.intensity;
479 let signal_attributes = SignalAttributes {
480 charge_state: charge,
481 peptide_id: c_ion.ion.sequence.peptide_id.unwrap_or(-1),
482 isotope_peak: 0,
483 description: Some(format!("{}_{}_{}", kind, index + 1, 0)),
484 };
485 let contribution_source = ContributionSource {
486 intensity_contribution: intensity,
487 source_type: SourceType::Signal,
488 signal_attributes: Some(signal_attributes)
489 };
490
491 annotations.push(PeakAnnotation {
492 contributions: vec![contribution_source]
493 });
494 mz_values.push(mz);
495 intensity_values.push(intensity);
496 }
497
498 MzSpectrumAnnotated::new(mz_values, intensity_values, annotations)
499 }
500
501 pub fn generate_isotopic_spectrum(&self, mass_tolerance: f64, abundance_threshold: f64, max_result: i32, intensity_min: f64) -> MzSpectrum {
502 let mut spectra: Vec<MzSpectrum> = Vec::new();
503
504 for ion in &self.n_ions {
505 let n_isotopes = ion.isotope_distribution(mass_tolerance, abundance_threshold, max_result, intensity_min);
506 let spectrum = MzSpectrum::new(n_isotopes.iter().map(|(mz, _)| *mz).collect(), n_isotopes.iter().map(|(_, abundance)| *abundance * ion.ion.intensity).collect());
507 spectra.push(spectrum);
508 }
509
510 for ion in &self.c_ions {
511 let c_isotopes = ion.isotope_distribution(mass_tolerance, abundance_threshold, max_result, intensity_min);
512 let spectrum = MzSpectrum::new(c_isotopes.iter().map(|(mz, _)| *mz).collect(), c_isotopes.iter().map(|(_, abundance)| *abundance * ion.ion.intensity).collect());
513 spectra.push(spectrum);
514 }
515
516 MzSpectrum::from_collection(spectra).filter_ranged(0.0, 5_000.0, 1e-6, 1e6)
517 }
518
519 pub fn generate_isotopic_spectrum_annotated(&self, mass_tolerance: f64, abundance_threshold: f64, max_result: i32, intensity_min: f64) -> MzSpectrumAnnotated {
520 let mut annotations: Vec<PeakAnnotation> = Vec::new();
521 let mut mz_values = Vec::new();
522 let mut intensity_values = Vec::new();
523
524 for (index, ion) in self.n_ions.iter().enumerate() {
525 let n_isotopes = ion.isotope_distribution(mass_tolerance, abundance_threshold, max_result, intensity_min);
526 let mut isotope_counter = 0;
527 let mut previous_mz = n_isotopes[0].0;
528
529 for (mz, abundance) in n_isotopes.iter() {
530 let ppm_tolerance = (mz / 1e6) * 25.0;
531
532 if (mz - previous_mz).abs() > ppm_tolerance {
533 isotope_counter += 1;
534 previous_mz = *mz;
535 }
536
537 let signal_attributes = SignalAttributes {
538 charge_state: ion.ion.charge,
539 peptide_id: ion.ion.sequence.peptide_id.unwrap_or(-1),
540 isotope_peak: isotope_counter,
541 description: Some(format!("{}_{}_{}", ion.kind, index + 1, isotope_counter)),
543 };
544
545 let contribution_source = ContributionSource {
546 intensity_contribution: *abundance * ion.ion.intensity,
547 source_type: SourceType::Signal,
548 signal_attributes: Some(signal_attributes)
549 };
550
551 annotations.push(PeakAnnotation {
552 contributions: vec![contribution_source]
553 });
554 mz_values.push(*mz);
555 intensity_values.push(*abundance * ion.ion.intensity);
556 }
557 }
558
559 for (index, ion) in self.c_ions.iter().enumerate() {
560 let c_isotopes = ion.isotope_distribution(mass_tolerance, abundance_threshold, max_result, intensity_min);
561 let mut isotope_counter = 0;
562 let mut previous_mz = c_isotopes[0].0;
563
564 for (mz, abundance) in c_isotopes.iter() {
565 let ppm_tolerance = (mz / 1e6) * 25.0;
566
567 if (mz - previous_mz).abs() > ppm_tolerance {
568 isotope_counter += 1;
569 previous_mz = *mz;
570 }
571
572 let signal_attributes = SignalAttributes {
573 charge_state: ion.ion.charge,
574 peptide_id: ion.ion.sequence.peptide_id.unwrap_or(-1),
575 isotope_peak: isotope_counter,
576 description: Some(format!("{}_{}_{}", ion.kind, index + 1, isotope_counter)),
577 };
578
579 let contribution_source = ContributionSource {
580 intensity_contribution: *abundance * ion.ion.intensity,
581 source_type: SourceType::Signal,
582 signal_attributes: Some(signal_attributes)
583 };
584
585 annotations.push(PeakAnnotation {
586 contributions: vec![contribution_source]
587 });
588
589 mz_values.push(*mz);
590 intensity_values.push(*abundance * ion.ion.intensity);
591 }
592 }
593 MzSpectrumAnnotated::new(mz_values, intensity_values, annotations)
594 }
595}
596
597#[derive(Debug, Clone, Serialize, Deserialize)]
598pub struct PeptideProductIonSeriesCollection {
599 pub peptide_ions: Vec<PeptideProductIonSeries>,
600}
601impl PeptideProductIonSeriesCollection {
602 pub fn new(peptide_ions: Vec<PeptideProductIonSeries>) -> Self {
603 PeptideProductIonSeriesCollection {
604 peptide_ions,
605 }
606 }
607
608 pub fn find_ion_series(&self, charge: i32) -> Option<&PeptideProductIonSeries> {
609 self.peptide_ions.iter().find(|ion_series| ion_series.charge == charge)
610 }
611
612 pub fn generate_isotopic_spectrum(&self, mass_tolerance: f64, abundance_threshold: f64, max_result: i32, intensity_min: f64) -> MzSpectrum {
613 let mut spectra: Vec<MzSpectrum> = Vec::new();
614
615 for ion_series in &self.peptide_ions {
616 let isotopic_spectrum = ion_series.generate_isotopic_spectrum(mass_tolerance, abundance_threshold, max_result, intensity_min);
617 spectra.push(isotopic_spectrum);
618 }
619
620 MzSpectrum::from_collection(spectra).filter_ranged(0.0, 5_000.0, 1e-6, 1e6)
621 }
622
623 pub fn generate_isotopic_spectrum_annotated(&self, mass_tolerance: f64, abundance_threshold: f64, max_result: i32, intensity_min: f64) -> MzSpectrumAnnotated {
624 let mut annotations: Vec<PeakAnnotation> = Vec::new();
625 let mut mz_values = Vec::new();
626 let mut intensity_values = Vec::new();
627
628 for ion_series in &self.peptide_ions {
629 let isotopic_spectrum = ion_series.generate_isotopic_spectrum_annotated(mass_tolerance, abundance_threshold, max_result, intensity_min);
630 for (mz, intensity) in isotopic_spectrum.mz.iter().zip(isotopic_spectrum.intensity.iter()) {
631 mz_values.push(*mz);
632 intensity_values.push(*intensity);
633 }
634 annotations.extend(isotopic_spectrum.annotations.iter().cloned());
635 }
636
637 MzSpectrumAnnotated::new(mz_values, intensity_values, annotations)
638 }
639}