1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
use crate::common::mtr_bar;

/// Represents errors that occur while working with Gases.
#[derive(thiserror::Error, Debug)]
pub enum GasError {
    #[error("gas does not have total fraction of 1.0")]
    /// The sum of all percentage gas fractions does not add up to 100.
    FractionError,
}

/// A gas mix used in a dive.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "use-serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Gas {
    /// Percentage fraction of oxygen in the mix.
    o2: usize,
    /// Percentage fraction of helium in the mix.
    he: usize,
    /// Percentage fraction of nitrogen in the mix.
    n2: usize,
}

/// Shorthand for creating a Gas, in a style similar to mix notation (O2/He)
/// # Panics
/// This macro will panic if the two supplied values exceed 100.
#[macro_export]
macro_rules! gas {
    ($o2:expr, $he:expr) => {{
        Gas::new($o2, $he, 100 - $o2 - $he).unwrap()
    }};
}

impl Gas {
    /// Returns a new Gas with the given parameters.
    /// # Arguments
    /// * `o2` - Percentage fraction of oxygen in the mix.
    /// * `he` - Percentage fraction of helium in the mix.
    /// * `n2` - Percentage fraction of nitrogen in the mix.
    /// # Errors
    /// This function will return a [`GasError`] if the percentage fractions do not add up to 100.
    pub fn new(o2: usize, he: usize, n2: usize) -> Result<Self, GasError> {
        if o2 + he + n2 != 100 {
            return Err(GasError::FractionError);
        }

        Ok(Self { o2, he, n2 })
    }

    /// Returns the **fraction** of nitrogen in the mix.
    pub fn fr_n2(&self) -> f64 {
        self.n2 as f64 / 100.0
    }

    /// Returns the **fraction** of oxygen in the mix.
    pub fn fr_o2(&self) -> f64 {
        self.o2 as f64 / 100.0
    }

    /// Returns the **fraction** of helium in the mix.
    pub fn fr_he(&self) -> f64 {
        self.he as f64 / 100.0
    }
    /// Returns the percentage fraction of oxygen in the mix.
    pub fn o2(&self) -> usize {
        self.o2
    }

    /// Returns the percentage fraction of helium in the mix.
    pub fn he(&self) -> usize {
        self.he
    }

    /// Returns the percentage fraction of nitrogen in the mix.
    pub fn n2(&self) -> usize {
        self.n2
    }

    /// Returns the Equivalent Narcotic Depth (END) of the mix at a given depth.
    /// # Arguments
    /// * `depth` - The depth the mix is being breathed at.
    pub fn equivalent_narcotic_depth(&self, depth: usize) -> usize {
        (((depth + 10) as f64 * (1.0 - self.fr_he())) - 10.0) as usize
    }

    /// Helper function to check whether the mix is in an acceptable ppO2 range at a given depth.
    /// # Arguments
    /// * `depth` -Depth the mix is being breathed at.
    /// * `min` - Minimum tolerable ppO2.
    /// * `max` - Maximum tolerable ppO2.
    pub fn in_ppo2_range(&self, depth: usize, min: f64, max: f64) -> bool {
        let ppo2 = self.pp_o2(depth, 10.0);
        ppo2 >= min && ppo2 <= max
    }

    /// Returns the ppO2 of the mix at a given depth.
    /// # Arguments
    /// * `depth` - Depth the mix is being breathed at.
    /// * `metres_per_bar` - Depth of water required to induce 1 bar of pressure.
    pub fn pp_o2(&self, depth: usize, metres_per_bar: f64) -> f64 {
        mtr_bar(depth as f64, metres_per_bar) * self.fr_o2()
    }

    /// Returns the ppHe of the mix at a given depth.
    /// # Arguments
    /// * `depth` - Depth the mix is being breathed at.
    /// * `metres_per_bar` - Depth of water required to induce 1 bar of pressure.
    pub fn pp_he(&self, depth: usize, metres_per_bar: f64) -> f64 {
        mtr_bar(depth as f64, metres_per_bar) * self.fr_he()
    }

    /// Returns the ppN2 of the mix at a given depth.
    /// # Arguments
    /// * `depth` - Depth the mix is being breathed at.
    /// * `metres_per_bar` - Depth of water required to induce 1 bar of pressure.
    pub fn pp_n2(&self, depth: usize, metre_per_bar: f64) -> f64 {
        mtr_bar(depth as f64, metre_per_bar) * self.fr_n2()
    }
}