rustms/chemistry/
sum_formula.rs1use std::collections::HashMap;
2use crate::algorithm::isotope::generate_isotope_distribution;
3use crate::chemistry::constants::MASS_PROTON;
4use crate::chemistry::element::atomic_weights_mono_isotopic;
5use crate::ms::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 {
40 let atomic_weights = atomic_weights_mono_isotopic();
41 self.elements.iter().fold(0.0, |acc, (element, count)| {
42 acc + atomic_weights[element.as_str()] * *count as f64
43 })
44 }
45
46 pub fn isotope_distribution(&self, charge: i32) -> MzSpectrum {
71 let distribution = generate_isotope_distribution(&self.elements, 1e-3, 1e-9, 200);
72 let intensity = distribution.iter().map(|(_, i)| *i).collect();
73 let mz = distribution.iter().map(|(m, _)| (*m + charge as f64 * MASS_PROTON) / charge as f64).collect();
74 MzSpectrum::new(mz, intensity)
75 }
76}
77
78pub fn parse_formula(formula: &str) -> Result<HashMap<String, i32>, String> {
99 let atomic_weights = atomic_weights_mono_isotopic();
100 let mut element_counts = HashMap::new();
101 let mut current_element = String::new();
102 let mut current_count = String::new();
103 let mut chars = formula.chars().peekable();
104
105 while let Some(c) = chars.next() {
106 if c.is_ascii_uppercase() {
107 if !current_element.is_empty() {
108 let count = current_count.parse::<i32>().unwrap_or(1);
109 if atomic_weights.contains_key(current_element.as_str()) {
110 *element_counts.entry(current_element.clone()).or_insert(0) += count;
111 } else {
112 return Err(format!("Unknown element: {}", current_element));
113 }
114 }
115 current_element = c.to_string();
116 current_count = String::new();
117 } else if c.is_ascii_digit() {
118 current_count.push(c);
119 } else if c.is_ascii_lowercase() {
120 current_element.push(c);
121 }
122
123 if chars.peek().map_or(true, |next_c| next_c.is_ascii_uppercase()) {
124 let count = current_count.parse::<i32>().unwrap_or(1);
125 if atomic_weights.contains_key(current_element.as_str()) {
126 *element_counts.entry(current_element.clone()).or_insert(0) += count;
127 } else {
128 return Err(format!("Unknown element: {}", current_element));
129 }
130 current_element = String::new();
131 current_count = String::new();
132 }
133 }
134
135 Ok(element_counts)
136}