chem_eq/
compound.rs

1//! Implementation of [`Compound`]
2
3use crate::{error::CompoundError, parse, Element, State, AVAGADRO_CONSTANT};
4
5/// An inidiviual compound. Containing some elements and a coefficient.
6///
7/// Eg: 2Fe2O3
8#[derive(Debug, Default, Clone, PartialOrd)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10pub struct Compound {
11    /// The elements of a compound
12    #[cfg_attr(feature = "serde", serde(skip))]
13    pub elements: Vec<Element>,
14    /// The coefficient of the whole compound
15    pub coefficient: usize,
16    /// The state of the compound
17    pub state: Option<State>,
18    /// The concentration in M (mol/L) of the compound
19    pub concentration: f32,
20}
21
22impl PartialEq for Compound {
23    fn eq(&self, other: &Self) -> bool {
24        self.elements == other.elements
25            && self.coefficient == other.coefficient
26            && self.state == other.state
27            && self.concentration == other.concentration
28    }
29}
30
31impl Compound {
32    /// Get the formula units, atoms or molecules of a compound
33    ///
34    /// ## Examples
35    ///
36    /// ```rust
37    /// use chem_eq::{Equation, AVAGADRO_CONSTANT};
38    ///
39    /// let mut eq = Equation::new("H2 + O2 -> H2O").unwrap();
40    /// eq.set_concentration_by_name("H2", 1.0).unwrap();
41    /// eq.set_volume(1.0);
42    /// let cmp = eq.iter_compounds().next().unwrap();
43    /// assert_eq!(cmp.get_units(eq.volume().unwrap()), AVAGADRO_CONSTANT);
44    /// ```
45    pub fn get_units(&self, volume: f32) -> f64 {
46        // c = n/v
47        // n = cv
48        let moles = self.concentration * volume;
49
50        // N = nNₐ
51        moles as f64 * AVAGADRO_CONSTANT
52    }
53
54    /// Parse a compound from str
55    ///
56    /// ## Examples
57    ///
58    /// ```rust
59    /// use chem_eq::{Compound, error::CompoundError};
60    ///
61    /// let cmp = Compound::parse("Fe2O3");
62    /// assert!(cmp.is_ok());
63    ///
64    /// let cmp = Compound::parse("Fe2O3 + O2");
65    /// assert_eq!(cmp.unwrap_err(), CompoundError::TooMuchInput(" + O2".to_string()));
66    /// ```
67    pub fn parse(input: &str) -> Result<Self, CompoundError> {
68        match parse::parse_compound(input) {
69            Ok((i, eq)) if i.trim().is_empty() => Ok(eq),
70            Ok((i, _)) => Err(CompoundError::TooMuchInput(i.to_string())),
71            Err(nom::Err::Error(e) | nom::Err::Failure(e)) => {
72                Err(CompoundError::ParsingError(e.into()))
73            }
74            // no streaming parsers were used
75            Err(nom::Err::Incomplete(_)) => unreachable!(),
76        }
77    }
78
79    /// Add to the formula units, atoms or molecules of a compound
80    ///
81    /// ## Examples
82    ///
83    /// ```rust
84    /// use chem_eq::{Equation, AVAGADRO_CONSTANT};
85    ///
86    /// let mut eq = Equation::new("H2 + O2 -> H2O").unwrap();
87    /// eq.set_concentration_by_name("H2", 1.0).unwrap();
88    /// eq.set_volume(1.0);
89    /// let volume = eq.volume().unwrap();
90    /// let cmp = eq.get_compound_by_name_mut("H2").unwrap();
91    /// cmp.add_unit(volume, 1);
92    /// assert_eq!(cmp.get_units(volume), AVAGADRO_CONSTANT + 1.0);
93    /// ```
94    pub fn add_unit(&mut self, volume: f32, addend: isize) {
95        // we need N
96        let units = self.get_units(volume) + addend as f64;
97
98        // N = nNa, n = N/Na
99        let moles = units / AVAGADRO_CONSTANT;
100
101        // c = n/v
102        self.concentration = moles as f32 / volume;
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109
110    #[test]
111    fn add_unit() {
112        let mut cmp = Compound::parse("H2").unwrap();
113        cmp.concentration = 1.0;
114        let volume = 1.0;
115        cmp.add_unit(volume, 1);
116        assert_eq!(cmp.get_units(volume), AVAGADRO_CONSTANT + 1.0);
117    }
118
119    #[test]
120    fn sub_unit() {
121        let mut cmp = Compound::parse("H2").unwrap();
122        cmp.concentration = 1.0;
123        let volume = 1.0;
124        cmp.add_unit(volume, -1);
125        assert_eq!(cmp.get_units(volume), AVAGADRO_CONSTANT - 1.0);
126    }
127}