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
216#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
217pub struct PeptideSequence {
218 pub sequence: String,
219 pub peptide_id: Option<i32>,
220}
221
222impl PeptideSequence {
223 pub fn new(raw_sequence: String, peptide_id: Option<i32>) -> Self {
224
225 let pattern = Regex::new(r"\[UNIMOD:(\d+)]").unwrap();
227
228 let sequence = pattern.replace_all(&raw_sequence, "").to_string();
230
231 let valid_amino_acids = sequence.chars().all(|c| amino_acid_masses().contains_key(&c.to_string()[..]));
233 if !valid_amino_acids {
234 panic!("Invalid amino acid sequence, use only valid amino acids: ARNDCQEGHILKMFPSTWYVU, and modifications in the format [UNIMOD:ID]");
235 }
236
237 PeptideSequence { sequence: raw_sequence, peptide_id }
238 }
239
240 pub fn mono_isotopic_mass(&self) -> f64 {
241 calculate_peptide_mono_isotopic_mass(self)
242 }
243
244 pub fn atomic_composition(&self) -> HashMap<&str, i32> {
245 peptide_sequence_to_atomic_composition(self)
246 }
247
248 pub fn to_tokens(&self, group_modifications: bool) -> Vec<String> {
249 unimod_sequence_to_tokens(&*self.sequence, group_modifications)
250 }
251
252 pub fn to_sage_representation(&self) -> (String, Vec<f64>) {
253 find_unimod_patterns(&*self.sequence)
254 }
255
256 pub fn amino_acid_count(&self) -> usize {
257 self.to_tokens(true).len()
258 }
259
260 pub fn calculate_mono_isotopic_product_ion_spectrum(&self, charge: i32, fragment_type: FragmentType) -> MzSpectrum {
261 let product_ions = self.calculate_product_ion_series(charge, fragment_type);
262 product_ions.generate_mono_isotopic_spectrum()
263 }
264
265 pub fn calculate_mono_isotopic_product_ion_spectrum_annotated(&self, charge: i32, fragment_type: FragmentType) -> MzSpectrumAnnotated {
266 let product_ions = self.calculate_product_ion_series(charge, fragment_type);
267 product_ions.generate_mono_isotopic_spectrum_annotated()
268 }
269
270 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 {
271 let product_ions = self.calculate_product_ion_series(charge, fragment_type);
272 product_ions.generate_isotopic_spectrum(mass_tolerance, abundance_threshold, max_result, intensity_min)
273 }
274
275 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 {
276 let product_ions = self.calculate_product_ion_series(charge, fragment_type);
277 product_ions.generate_isotopic_spectrum_annotated(mass_tolerance, abundance_threshold, max_result, intensity_min)
278 }
279
280 pub fn calculate_product_ion_series(&self, target_charge: i32, fragment_type: FragmentType) -> PeptideProductIonSeries {
281 let tokens = unimod_sequence_to_tokens(self.sequence.as_str(), true);
283 let mut n_terminal_ions = Vec::new();
284 let mut c_terminal_ions = Vec::new();
285
286 for i in 1..tokens.len() {
288 let n_ion_seq = tokens[..i].join("");
289 n_terminal_ions.push(PeptideProductIon {
290 kind: match fragment_type {
291 FragmentType::A => FragmentType::A,
292 FragmentType::B => FragmentType::B,
293 FragmentType::C => FragmentType::C,
294 FragmentType::X => FragmentType::A,
295 FragmentType::Y => FragmentType::B,
296 FragmentType::Z => FragmentType::C,
297 },
298 ion: PeptideIon {
299 sequence: PeptideSequence {
300 sequence: n_ion_seq,
301 peptide_id: self.peptide_id,
302 },
303 charge: target_charge,
304 intensity: 1.0, },
306 });
307 }
308
309 for i in 1..tokens.len() {
311 let c_ion_seq = tokens[tokens.len() - i..].join("");
312 c_terminal_ions.push(PeptideProductIon {
313 kind: match fragment_type {
314 FragmentType::A => FragmentType::X,
315 FragmentType::B => FragmentType::Y,
316 FragmentType::C => FragmentType::Z,
317 FragmentType::X => FragmentType::X,
318 FragmentType::Y => FragmentType::Y,
319 FragmentType::Z => FragmentType::Z,
320 },
321 ion: PeptideIon {
322 sequence: PeptideSequence {
323 sequence: c_ion_seq,
324 peptide_id: self.peptide_id,
325 },
326 charge: target_charge,
327 intensity: 1.0, },
329 });
330 }
331
332 PeptideProductIonSeries::new(target_charge, n_terminal_ions, c_terminal_ions)
333 }
334
335 pub fn associate_with_predicted_intensities(
336 &self,
337 charge: i32,
339 fragment_type: FragmentType,
340 flat_intensities: Vec<f64>,
341 normalize: bool,
342 half_charge_one: bool,
343 ) -> PeptideProductIonSeriesCollection {
344
345 let reshaped_intensities = reshape_prosit_array(flat_intensities);
346 let max_charge = std::cmp::min(charge, 3).max(1); let mut sum_intensity = if normalize { 0.0 } else { 1.0 };
348 let num_tokens = self.amino_acid_count() - 1; let mut peptide_ion_collection = Vec::new();
351
352 if normalize {
353 for z in 1..=max_charge {
354
355 let intensity_c: Vec<f64> = reshaped_intensities[..num_tokens].iter().map(|x| x[0][z as usize - 1]).filter(|&x| x > 0.0).collect();
356 let intensity_n: Vec<f64> = reshaped_intensities[..num_tokens].iter().map(|x| x[1][z as usize - 1]).filter(|&x| x > 0.0).collect();
357
358 sum_intensity += intensity_n.iter().sum::<f64>() + intensity_c.iter().sum::<f64>();
359 }
360 }
361
362 for z in 1..=max_charge {
363
364 let mut product_ions = self.calculate_product_ion_series(z, fragment_type);
365 let intensity_n: Vec<f64> = reshaped_intensities[..num_tokens].iter().map(|x| x[1][z as usize - 1]).collect();
366 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 };
369
370 for (i, ion) in product_ions.n_ions.iter_mut().enumerate() {
371 ion.ion.intensity = intensity_n[i] / adjusted_sum_intensity;
372 }
373 for (i, ion) in product_ions.c_ions.iter_mut().enumerate() {
374 ion.ion.intensity = intensity_c[i] / adjusted_sum_intensity;
375 }
376
377 peptide_ion_collection.push(PeptideProductIonSeries::new(z, product_ions.n_ions, product_ions.c_ions));
378 }
379
380 PeptideProductIonSeriesCollection::new(peptide_ion_collection)
381 }
382}
383
384#[derive(Debug, Clone, Serialize, Deserialize)]
385pub struct PeptideProductIonSeries {
386 pub charge: i32,
387 pub n_ions: Vec<PeptideProductIon>,
388 pub c_ions: Vec<PeptideProductIon>,
389}
390
391impl PeptideProductIonSeries {
392 pub fn new(charge: i32, n_ions: Vec<PeptideProductIon>, c_ions: Vec<PeptideProductIon>) -> Self {
393 PeptideProductIonSeries {
394 charge,
395 n_ions,
396 c_ions,
397 }
398 }
399
400 pub fn generate_mono_isotopic_spectrum(&self) -> MzSpectrum {
401 let mz_i_n = self.n_ions.iter().map(|ion| (ion.mz(), ion.ion.intensity)).collect_vec();
402 let mz_i_c = self.c_ions.iter().map(|ion| (ion.mz(), ion.ion.intensity)).collect_vec();
403 let n_spectrum = MzSpectrum::new(mz_i_n.iter().map(|(mz, _)| *mz).collect(), mz_i_n.iter().map(|(_, abundance)| *abundance).collect());
404 let c_spectrum = MzSpectrum::new(mz_i_c.iter().map(|(mz, _)| *mz).collect(), mz_i_c.iter().map(|(_, abundance)| *abundance).collect());
405 MzSpectrum::from_collection(vec![n_spectrum, c_spectrum]).filter_ranged(0.0, 5_000.0, 1e-6, 1e6)
406 }
407
408 pub fn generate_mono_isotopic_spectrum_annotated(&self) -> MzSpectrumAnnotated {
409 let mut annotations: Vec<PeakAnnotation> = Vec::with_capacity(self.n_ions.len() + self.c_ions.len());
410 let mut mz_values = Vec::with_capacity(self.n_ions.len() + self.c_ions.len());
411 let mut intensity_values = Vec::with_capacity(self.n_ions.len() + self.c_ions.len());
412
413 for (index, n_ion) in self.n_ions.iter().enumerate() {
414 let kind = n_ion.kind;
415 let charge = n_ion.ion.charge;
416 let mz = n_ion.mz();
417 let intensity = n_ion.ion.intensity;
418 let signal_attributes = SignalAttributes {
419 charge_state: charge,
420 peptide_id: n_ion.ion.sequence.peptide_id.unwrap_or(-1),
421 isotope_peak: 0,
422 description: Some(format!("{}_{}_{}", kind, index + 1, 0)),
423 };
424 let contribution_source = ContributionSource {
425 intensity_contribution: intensity,
426 source_type: SourceType::Signal,
427 signal_attributes: Some(signal_attributes)
428 };
429
430 annotations.push(PeakAnnotation {
431 contributions: vec![contribution_source]
432 });
433 mz_values.push(mz);
434 intensity_values.push(intensity);
435 }
436
437 for (index, c_ion) in self.c_ions.iter().enumerate() {
438 let kind = c_ion.kind;
439 let charge = c_ion.ion.charge;
440 let mz = c_ion.mz();
441 let intensity = c_ion.ion.intensity;
442 let signal_attributes = SignalAttributes {
443 charge_state: charge,
444 peptide_id: c_ion.ion.sequence.peptide_id.unwrap_or(-1),
445 isotope_peak: 0,
446 description: Some(format!("{}_{}_{}", kind, index + 1, 0)),
447 };
448 let contribution_source = ContributionSource {
449 intensity_contribution: intensity,
450 source_type: SourceType::Signal,
451 signal_attributes: Some(signal_attributes)
452 };
453
454 annotations.push(PeakAnnotation {
455 contributions: vec![contribution_source]
456 });
457 mz_values.push(mz);
458 intensity_values.push(intensity);
459 }
460
461 MzSpectrumAnnotated::new(mz_values, intensity_values, annotations)
462 }
463
464 pub fn generate_isotopic_spectrum(&self, mass_tolerance: f64, abundance_threshold: f64, max_result: i32, intensity_min: f64) -> MzSpectrum {
465 let mut spectra: Vec<MzSpectrum> = Vec::new();
466
467 for ion in &self.n_ions {
468 let n_isotopes = ion.isotope_distribution(mass_tolerance, abundance_threshold, max_result, intensity_min);
469 let spectrum = MzSpectrum::new(n_isotopes.iter().map(|(mz, _)| *mz).collect(), n_isotopes.iter().map(|(_, abundance)| *abundance * ion.ion.intensity).collect());
470 spectra.push(spectrum);
471 }
472
473 for ion in &self.c_ions {
474 let c_isotopes = ion.isotope_distribution(mass_tolerance, abundance_threshold, max_result, intensity_min);
475 let spectrum = MzSpectrum::new(c_isotopes.iter().map(|(mz, _)| *mz).collect(), c_isotopes.iter().map(|(_, abundance)| *abundance * ion.ion.intensity).collect());
476 spectra.push(spectrum);
477 }
478
479 MzSpectrum::from_collection(spectra).filter_ranged(0.0, 5_000.0, 1e-6, 1e6)
480 }
481
482 pub fn generate_isotopic_spectrum_annotated(&self, mass_tolerance: f64, abundance_threshold: f64, max_result: i32, intensity_min: f64) -> MzSpectrumAnnotated {
483 let mut annotations: Vec<PeakAnnotation> = Vec::new();
484 let mut mz_values = Vec::new();
485 let mut intensity_values = Vec::new();
486
487 for (index, ion) in self.n_ions.iter().enumerate() {
488 let n_isotopes = ion.isotope_distribution(mass_tolerance, abundance_threshold, max_result, intensity_min);
489 let mut isotope_counter = 0;
490 let mut previous_mz = n_isotopes[0].0;
491
492 for (mz, abundance) in n_isotopes.iter() {
493 let ppm_tolerance = (mz / 1e6) * 25.0;
494
495 if (mz - previous_mz).abs() > ppm_tolerance {
496 isotope_counter += 1;
497 previous_mz = *mz;
498 }
499
500 let signal_attributes = SignalAttributes {
501 charge_state: ion.ion.charge,
502 peptide_id: ion.ion.sequence.peptide_id.unwrap_or(-1),
503 isotope_peak: isotope_counter,
504 description: Some(format!("{}_{}_{}", ion.kind, index + 1, isotope_counter)),
506 };
507
508 let contribution_source = ContributionSource {
509 intensity_contribution: *abundance * ion.ion.intensity,
510 source_type: SourceType::Signal,
511 signal_attributes: Some(signal_attributes)
512 };
513
514 annotations.push(PeakAnnotation {
515 contributions: vec![contribution_source]
516 });
517 mz_values.push(*mz);
518 intensity_values.push(*abundance * ion.ion.intensity);
519 }
520 }
521
522 for (index, ion) in self.c_ions.iter().enumerate() {
523 let c_isotopes = ion.isotope_distribution(mass_tolerance, abundance_threshold, max_result, intensity_min);
524 let mut isotope_counter = 0;
525 let mut previous_mz = c_isotopes[0].0;
526
527 for (mz, abundance) in c_isotopes.iter() {
528 let ppm_tolerance = (mz / 1e6) * 25.0;
529
530 if (mz - previous_mz).abs() > ppm_tolerance {
531 isotope_counter += 1;
532 previous_mz = *mz;
533 }
534
535 let signal_attributes = SignalAttributes {
536 charge_state: ion.ion.charge,
537 peptide_id: ion.ion.sequence.peptide_id.unwrap_or(-1),
538 isotope_peak: isotope_counter,
539 description: Some(format!("{}_{}_{}", ion.kind, index + 1, isotope_counter)),
540 };
541
542 let contribution_source = ContributionSource {
543 intensity_contribution: *abundance * ion.ion.intensity,
544 source_type: SourceType::Signal,
545 signal_attributes: Some(signal_attributes)
546 };
547
548 annotations.push(PeakAnnotation {
549 contributions: vec![contribution_source]
550 });
551
552 mz_values.push(*mz);
553 intensity_values.push(*abundance * ion.ion.intensity);
554 }
555 }
556 MzSpectrumAnnotated::new(mz_values, intensity_values, annotations)
557 }
558}
559
560#[derive(Debug, Clone, Serialize, Deserialize)]
561pub struct PeptideProductIonSeriesCollection {
562 pub peptide_ions: Vec<PeptideProductIonSeries>,
563}
564impl PeptideProductIonSeriesCollection {
565 pub fn new(peptide_ions: Vec<PeptideProductIonSeries>) -> Self {
566 PeptideProductIonSeriesCollection {
567 peptide_ions,
568 }
569 }
570
571 pub fn find_ion_series(&self, charge: i32) -> Option<&PeptideProductIonSeries> {
572 self.peptide_ions.iter().find(|ion_series| ion_series.charge == charge)
573 }
574
575 pub fn generate_isotopic_spectrum(&self, mass_tolerance: f64, abundance_threshold: f64, max_result: i32, intensity_min: f64) -> MzSpectrum {
576 let mut spectra: Vec<MzSpectrum> = Vec::new();
577
578 for ion_series in &self.peptide_ions {
579 let isotopic_spectrum = ion_series.generate_isotopic_spectrum(mass_tolerance, abundance_threshold, max_result, intensity_min);
580 spectra.push(isotopic_spectrum);
581 }
582
583 MzSpectrum::from_collection(spectra).filter_ranged(0.0, 5_000.0, 1e-6, 1e6)
584 }
585
586 pub fn generate_isotopic_spectrum_annotated(&self, mass_tolerance: f64, abundance_threshold: f64, max_result: i32, intensity_min: f64) -> MzSpectrumAnnotated {
587 let mut annotations: Vec<PeakAnnotation> = Vec::new();
588 let mut mz_values = Vec::new();
589 let mut intensity_values = Vec::new();
590
591 for ion_series in &self.peptide_ions {
592 let isotopic_spectrum = ion_series.generate_isotopic_spectrum_annotated(mass_tolerance, abundance_threshold, max_result, intensity_min);
593 for (mz, intensity) in isotopic_spectrum.mz.iter().zip(isotopic_spectrum.intensity.iter()) {
594 mz_values.push(*mz);
595 intensity_values.push(*intensity);
596 }
597 annotations.extend(isotopic_spectrum.annotations.iter().cloned());
598 }
599
600 MzSpectrumAnnotated::new(mz_values, intensity_values, annotations)
601 }
602}