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
119
120
121
122
123
124
125
126
127
128
129
130
131
use crate::common::global_types::{Pressure, Depth};

use super::MbarPressure;

// alveolar water vapor pressure assuming 47 mm Hg at 37C (Buehlmann's value)
const ALVEOLI_WATER_VAPOR_PRESSURE: f64 = 0.0627;

#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Gas {
    o2_pp: Pressure,
    n2_pp: Pressure,
    he_pp: Pressure,
}

#[derive(Debug, PartialEq)]
pub struct PartialPressures {
    pub o2: Pressure,
    pub n2: Pressure,
    pub he: Pressure,
}

impl Gas {
    /// init new gas with partial pressures (eg. 0.21, 0. for air)
    /// helium not supported yet
    pub fn new(o2_pp: Pressure, he_pp: Pressure) -> Self {
        if !(0. ..=1.).contains(&o2_pp) {
            panic!("Invalid O2 partial pressure");
        }
        if !(0. ..=1.).contains(&he_pp) {
            panic!("Invalid He partial pressure");
        }
        if (o2_pp + he_pp) > 1. {
            panic!("Invalid partial pressures, can't exceed 1ATA in total");
        }
        // @todo helium
        if he_pp != 0. {
            panic!("Helium not supported");
        }

        Self {
            o2_pp,
            he_pp,
            n2_pp: 1. - (o2_pp + he_pp),
        }
    }

    /// gas partial pressures
    pub fn partial_pressures(&self, depth: &Depth, surface_pressure: MbarPressure) -> PartialPressures {
        let gas_pressure = (surface_pressure as f64 / 1000.) + (depth / 10.);
        self.gas_pressures_compound(gas_pressure)
    }

    /// gas partial pressures in alveoli taking into account alveolar water vapor pressure
    pub fn inspired_partial_pressures(&self, depth: &Depth, surface_pressure: MbarPressure) -> PartialPressures {
        let gas_pressure = ((surface_pressure as f64 / 1000.) + (depth / 10.)) - ALVEOLI_WATER_VAPOR_PRESSURE;
        self.gas_pressures_compound(gas_pressure)
    }

    pub fn air() -> Self {
        Self::new(0.21, 0.)
    }

    fn gas_pressures_compound(&self, gas_pressure: f64) -> PartialPressures {
        PartialPressures {
            o2: self.o2_pp * gas_pressure,
            n2: self.n2_pp * gas_pressure,
            he: self.he_pp * gas_pressure,
        }
    }
}


#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_valid_gas_air() {
        let air = Gas::new(0.21, 0.);
        assert_eq!(air.o2_pp, 0.21);
        assert_eq!(air.n2_pp, 0.79);
        assert_eq!(air.he_pp, 0.);
    }

    #[ignore = "trimix unsupported"]
    #[test]
    fn test_valid_gas_tmx() {
        let tmx = Gas::new(0.18, 0.35);
        assert_eq!(tmx.o2_pp, 0.18);
        assert_eq!(tmx.he_pp, 0.35);
        assert_eq!(tmx.n2_pp, 0.47);
    }

    #[test]
    #[should_panic]
    fn test_invalid_o2_high() {
        Gas::new(1.1, 0.);
    }

    #[test]
    #[should_panic]
    fn test_invalid_o2_low() {
        Gas::new(-3., 0.);
    }

    #[test]
    #[should_panic]
    fn test_invalid_partial_pressures() {
        Gas::new(0.5, 0.51);
    }

    #[test]
    #[should_panic]
    fn test_unsupported_helium() {
        Gas::new(0.18, 0.35);
    }

    #[test]
    fn test_partial_pressures() {
        let air = Gas::new(0.21, 0.);
        let partial_pressures = air.partial_pressures(&10., 1000);
        assert_eq!(partial_pressures, PartialPressures { o2: 0.42, n2: 1.58, he: 0. });
    }

    #[test]
    fn test_inspired_partial_pressures() {
        let air = Gas::new(0.21, 0.);
        let inspired_partial_pressures = air.inspired_partial_pressures(&10., 1000);
        assert_eq!(inspired_partial_pressures, PartialPressures { o2: 0.406833, n2: 1.530467, he: 0.0 });
    }
}