mscore/chemistry/
sum_formula.rs

1use std::collections::HashMap;
2use crate::algorithm::isotope::generate_isotope_distribution;
3use crate::chemistry::constants::MASS_PROTON;
4use crate::chemistry::elements::atomic_weights_mono_isotopic;
5use crate::data::spectrum::MzSpectrum;
6
7pub struct SumFormula {
8    pub formula: String,
9    pub elements: HashMap<String, i32>,
10}
11
12impl SumFormula {
13    pub fn new(formula: &str) -> Self {
14        let elements = parse_formula(formula).unwrap();
15        SumFormula {
16            formula: formula.to_string(),
17            elements,
18        }
19    }
20    pub fn monoisotopic_weight(&self) -> f64 {
21        let atomic_weights = atomic_weights_mono_isotopic();
22        self.elements.iter().fold(0.0, |acc, (element, count)| {
23            acc + atomic_weights[element.as_str()] * *count as f64
24        })
25    }
26
27    pub fn isotope_distribution(&self, charge: i32) -> MzSpectrum {
28        let distribution = generate_isotope_distribution(&self.elements, 1e-3, 1e-9, 200);
29        let intensity = distribution.iter().map(|(_, i)| *i).collect();
30        let mz = distribution.iter().map(|(m, _)| (*m + charge as f64 * MASS_PROTON) / charge as f64).collect();
31        MzSpectrum::new(mz, intensity)
32    }
33}
34
35fn parse_formula(formula: &str) -> Result<HashMap<String, i32>, String> {
36    let atomic_weights = atomic_weights_mono_isotopic();
37    let mut element_counts = HashMap::new();
38    let mut current_element = String::new();
39    let mut current_count = String::new();
40    let mut chars = formula.chars().peekable();
41
42    while let Some(c) = chars.next() {
43        if c.is_ascii_uppercase() {
44            if !current_element.is_empty() {
45                let count = current_count.parse::<i32>().unwrap_or(1);
46                if atomic_weights.contains_key(current_element.as_str()) {
47                    *element_counts.entry(current_element.clone()).or_insert(0) += count;
48                } else {
49                    return Err(format!("Unknown element: {}", current_element));
50                }
51            }
52            current_element = c.to_string();
53            current_count = String::new();
54        } else if c.is_ascii_digit() {
55            current_count.push(c);
56        } else if c.is_ascii_lowercase() {
57            current_element.push(c);
58        }
59
60        if chars.peek().map_or(true, |next_c| next_c.is_ascii_uppercase()) {
61            let count = current_count.parse::<i32>().unwrap_or(1);
62            if atomic_weights.contains_key(current_element.as_str()) {
63                *element_counts.entry(current_element.clone()).or_insert(0) += count;
64            } else {
65                return Err(format!("Unknown element: {}", current_element));
66            }
67            current_element = String::new();
68            current_count = String::new();
69        }
70    }
71
72    Ok(element_counts)
73}