Skip to main content

use_thermodynamics/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4//! Ideal gas and heat-energy helpers.
5
6use core::fmt;
7
8pub mod prelude;
9
10pub const IDEAL_GAS_CONSTANT: f64 = 8.314_462_618_153_24;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum ThermodynamicsError {
14    NonPositiveTemperature,
15    NonPositiveVolume,
16}
17
18impl fmt::Display for ThermodynamicsError {
19    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
20        match self {
21            Self::NonPositiveTemperature => {
22                formatter.write_str("temperature must be greater than zero kelvin")
23            },
24            Self::NonPositiveVolume => formatter.write_str("volume must be greater than zero"),
25        }
26    }
27}
28
29impl std::error::Error for ThermodynamicsError {}
30
31#[must_use]
32pub const fn celsius_to_kelvin(celsius: f64) -> f64 {
33    celsius + 273.15
34}
35
36#[must_use]
37pub const fn heat_energy(mass: f64, specific_heat_capacity: f64, delta_temperature: f64) -> f64 {
38    mass * specific_heat_capacity * delta_temperature
39}
40
41/// Computes ideal gas pressure from amount of substance, temperature, and volume.
42///
43/// # Errors
44///
45/// Returns [`ThermodynamicsError::NonPositiveTemperature`] when `temperature_kelvin` is less
46/// than or equal to zero. Returns [`ThermodynamicsError::NonPositiveVolume`] when `volume` is
47/// less than or equal to zero.
48pub fn ideal_gas_pressure(
49    moles: f64,
50    temperature_kelvin: f64,
51    volume: f64,
52) -> Result<f64, ThermodynamicsError> {
53    if temperature_kelvin <= 0.0 {
54        Err(ThermodynamicsError::NonPositiveTemperature)
55    } else if volume <= 0.0 {
56        Err(ThermodynamicsError::NonPositiveVolume)
57    } else {
58        Ok((moles * IDEAL_GAS_CONSTANT * temperature_kelvin) / volume)
59    }
60}
61
62#[cfg(test)]
63#[allow(clippy::float_cmp)]
64mod tests {
65    use super::{
66        IDEAL_GAS_CONSTANT, ThermodynamicsError, celsius_to_kelvin, heat_energy, ideal_gas_pressure,
67    };
68
69    #[test]
70    fn thermodynamics_helpers_cover_common_calculations() -> Result<(), ThermodynamicsError> {
71        let pressure = ideal_gas_pressure(2.0, 300.0, 3.0)?;
72        let expected = (2.0 * IDEAL_GAS_CONSTANT * 300.0) / 3.0;
73
74        assert!((pressure - expected).abs() < 1.0e-12);
75        assert_eq!(celsius_to_kelvin(0.0), 273.15);
76        assert_eq!(heat_energy(2.0, 4.0, 5.0), 40.0);
77        Ok(())
78    }
79
80    #[test]
81    fn ideal_gas_pressure_requires_positive_state() {
82        assert_eq!(
83            ideal_gas_pressure(1.0, 0.0, 1.0),
84            Err(ThermodynamicsError::NonPositiveTemperature)
85        );
86        assert_eq!(
87            ideal_gas_pressure(1.0, 300.0, 0.0),
88            Err(ThermodynamicsError::NonPositiveVolume)
89        );
90    }
91}