#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Species {
Methane,
Steam,
CarbonMonoxide,
CarbonDioxide,
Hydrogen,
}
impl Species {
pub const INDEX_ORDER: [Species; 5] = [
Species::Methane,
Species::Steam,
Species::CarbonMonoxide,
Species::CarbonDioxide,
Species::Hydrogen,
];
#[must_use]
pub fn index(self) -> usize {
match self {
Species::Methane => 0,
Species::Steam => 1,
Species::CarbonMonoxide => 2,
Species::CarbonDioxide => 3,
Species::Hydrogen => 4,
}
}
#[must_use]
pub fn formula(self) -> &'static str {
match self {
Species::Methane => "CH4",
Species::Steam => "H2O",
Species::CarbonMonoxide => "CO",
Species::CarbonDioxide => "CO2",
Species::Hydrogen => "H2",
}
}
#[must_use]
pub fn from_formula(s: &str) -> Option<Self> {
Self::INDEX_ORDER.into_iter().find(|sp| sp.formula() == s)
}
#[must_use]
pub fn enthalpy_formation(self) -> f64 {
match self {
Species::Methane => -74_600.0,
Species::Steam => -241_800.0,
Species::CarbonMonoxide => -110_500.0,
Species::CarbonDioxide => -393_500.0,
Species::Hydrogen => 0.0,
}
}
#[must_use]
pub fn entropy(self) -> f64 {
match self {
Species::Methane => 186.3,
Species::Steam => 188.8,
Species::CarbonMonoxide => 197.7,
Species::CarbonDioxide => 213.8,
Species::Hydrogen => 130.7,
}
}
#[must_use]
pub fn molar_mass(self) -> f64 {
match self {
Species::Methane => 0.016_043,
Species::Steam => 0.018_015,
Species::CarbonMonoxide => 0.028_010,
Species::CarbonDioxide => 0.044_010,
Species::Hydrogen => 0.002_016,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn index_order_and_index_agree() {
for (i, sp) in Species::INDEX_ORDER.iter().enumerate() {
assert_eq!(sp.index(), i, "{sp:?}");
}
}
#[test]
fn formulae_round_trip_and_are_case_sensitive() {
for sp in Species::INDEX_ORDER {
assert_eq!(Species::from_formula(sp.formula()), Some(sp));
}
assert_eq!(
Species::from_formula("co"),
None,
"must not accept lowercase"
);
assert_eq!(Species::from_formula("Methane"), None, "formula, not name");
}
#[test]
fn hydrogen_has_zero_formation_enthalpy_but_nonzero_absolute_entropy() {
assert_eq!(Species::Hydrogen.enthalpy_formation(), 0.0);
assert!(Species::Hydrogen.entropy() > 100.0);
}
#[test]
fn the_table_is_physically_sane() {
for sp in Species::INDEX_ORDER {
assert!(sp.entropy() > 0.0, "{sp:?} has non-positive S°");
assert!(
sp.molar_mass() > 0.001 && sp.molar_mass() < 0.1,
"{sp:?} molar mass {} kg/mol",
sp.molar_mass()
);
assert!(sp.enthalpy_formation().is_finite());
}
}
#[test]
fn water_is_the_gas_phase_value() {
let h = Species::Steam.enthalpy_formation();
assert!(
(h - -241_800.0).abs() < 1.0,
"expected gas-phase H2O, got {h} J/mol"
);
assert!(
(h - -285_800.0).abs() > 40_000.0,
"this looks like liquid water"
);
}
}