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}