Skip to main content

polysim_core/properties/
molecular_weight.rs

1use opensmiles::{parse as parse_smiles, AtomSymbol};
2
3use crate::polymer::PolymerChain;
4
5/// Masse standard de l'hydrogène (IUPAC 2021), en g/mol.
6const H_AVERAGE_MASS: f64 = 1.008;
7
8/// Masse du proton (¹H), en g/mol.
9const H_MONO_MASS: f64 = 1.00782503207;
10
11/// Calcule la masse moléculaire moyenne (poids atomiques IUPAC) de la chaîne, en g/mol.
12///
13/// Chaque atome lourd contribue par sa masse standard (moyenne isotopique), et les
14/// hydrogènes implicites/explicites sont ajoutés avec la masse standard de l'hydrogène.
15///
16/// # Exemple
17///
18/// ```rust
19/// use polysim_core::{parse, builder::{linear::LinearBuilder, BuildStrategy},
20///                    properties::molecular_weight::average_mass};
21///
22/// let bs = parse("{[]CC[]}").unwrap();
23/// let chain = LinearBuilder::new(bs, BuildStrategy::ByRepeatCount(1))
24///     .homopolymer()
25///     .unwrap();
26/// // CC = éthane C₂H₆ ≈ 30.07 g/mol
27/// let mw = average_mass(&chain);
28/// assert!((mw - 30.070).abs() < 0.01, "got {mw}");
29/// ```
30pub fn average_mass(chain: &PolymerChain) -> f64 {
31    let mol = parse_smiles(&chain.smiles).expect("chain SMILES must be valid SMILES");
32    mol.nodes().iter().fold(0.0, |acc, node| {
33        // atom.mass() renvoie la masse standard (ou la masse isotopique si explicite [¹³C])
34        acc + node.atom().mass() + node.hydrogens() as f64 * H_AVERAGE_MASS
35    })
36}
37
38/// Calcule la masse monoisotopique de la chaîne (nucléide le plus abondant), en g/mol.
39///
40/// Pour les atomes sans isotope explicite, utilise le nucléide le plus abondant de chaque
41/// élément (ex. ¹²C = 12.000, ¹⁶O = 15.9949…). Pour les atomes avec isotope explicite
42/// (`[13C]`), respecte l'isotope spécifié.
43///
44/// # Exemple
45///
46/// ```rust
47/// use polysim_core::{parse, builder::{linear::LinearBuilder, BuildStrategy},
48///                    properties::molecular_weight::monoisotopic_mass};
49///
50/// let bs = parse("{[]CC[]}").unwrap();
51/// let chain = LinearBuilder::new(bs, BuildStrategy::ByRepeatCount(1))
52///     .homopolymer()
53///     .unwrap();
54/// // CC = éthane C₂H₆, masse monoisotopique ≈ 30.047 g/mol
55/// let m = monoisotopic_mass(&chain);
56/// assert!((m - 30.047).abs() < 0.01, "got {m}");
57/// ```
58pub fn monoisotopic_mass(chain: &PolymerChain) -> f64 {
59    let mol = parse_smiles(&chain.smiles).expect("chain SMILES must be valid SMILES");
60    mol.nodes().iter().fold(0.0, |acc, node| {
61        let atom = node.atom();
62        let heavy_mass = if atom.isotope().is_some() {
63            // Isotope explicitement spécifié → respecter (ex. [13C])
64            atom.mass()
65        } else {
66            most_abundant_isotope_mass(atom.element())
67        };
68        acc + heavy_mass + node.hydrogens() as f64 * H_MONO_MASS
69    })
70}
71
72/// Retourne la masse du nucléide le plus abondant pour chaque élément.
73///
74/// Pour les éléments organiques courants en chimie des polymères, les valeurs exactes
75/// sont codées en dur. Pour les éléments rares, la masse standard IUPAC est utilisée
76/// comme approximation.
77fn most_abundant_isotope_mass(element: &AtomSymbol) -> f64 {
78    match element.atomic_number() {
79        0 => 0.0,                     // Wildcard (*)
80        1 => H_MONO_MASS,             // ¹H (99.985 %)
81        5 => 11.0093054,              // ¹¹B (80.1 %)
82        6 => 12.0,                    // ¹²C (98.89 %)
83        7 => 14.0030740048,           // ¹⁴N (99.63 %)
84        8 => 15.9949146221,           // ¹⁶O (99.76 %)
85        9 => 18.9984032,              // ¹⁹F (100 %)
86        14 => 27.9769265325,          // ²⁸Si (92.23 %)
87        15 => 30.97376163,            // ³¹P (100 %)
88        16 => 31.97207100,            // ³²S (95.02 %)
89        17 => 34.96885268,            // ³⁵Cl (75.77 %)
90        35 => 78.9183371,             // ⁷⁹Br (50.69 %)
91        53 => 126.904468,             // ¹²⁷I (100 %)
92        _ => element.standard_mass(), // fallback : masse IUPAC pour éléments rares
93    }
94}