dive_deco/buhlmann/
buhlmann_config.rs

1use crate::{
2    common::{
3        AscentRatePerMinute, ConfigValidationErr, DecoModelConfig, GradientFactor, GradientFactors,
4        MbarPressure,
5    },
6    CeilingType,
7};
8
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11
12#[cfg(test)]
13use alloc::vec;
14
15const GF_RANGE_ERR_MSG: &str = "GF values have to be in 1-100 range";
16const GF_ORDER_ERR_MSG: &str = "GFLow can't be higher than GFHigh";
17const SURFACE_PRESSURE_ERR_MSG: &str = "Surface pressure must be in milibars in 500-1500 range";
18const DECO_ASCENT_RATE_ERR_MSG: &str = "Ascent rate must in 1-30 m/s range";
19
20#[derive(Copy, Clone, Debug, PartialEq)]
21#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
22pub struct BuhlmannConfig {
23    pub gf: GradientFactors,
24    pub surface_pressure: MbarPressure,
25    pub deco_ascent_rate: AscentRatePerMinute,
26    pub ceiling_type: CeilingType,
27    pub round_ceiling: bool,
28    pub recalc_all_tissues_m_values: bool,
29}
30
31impl BuhlmannConfig {
32    pub fn new() -> Self {
33        Self::default()
34    }
35
36    pub fn with_gradient_factors<T: Into<GradientFactor>>(mut self, gf_low: T, gf_high: T) -> Self {
37        self.gf = (gf_low.into(), gf_high.into());
38        self
39    }
40
41    pub fn with_surface_pressure<T: Into<MbarPressure>>(mut self, surface_pressure: T) -> Self {
42        self.surface_pressure = surface_pressure.into();
43        self
44    }
45
46    pub fn with_deco_ascent_rate<T: Into<AscentRatePerMinute>>(
47        mut self,
48        deco_ascent_rate: T,
49    ) -> Self {
50        self.deco_ascent_rate = deco_ascent_rate.into();
51        self
52    }
53
54    pub fn with_ceiling_type(mut self, ceiling_type: CeilingType) -> Self {
55        self.ceiling_type = ceiling_type;
56        self
57    }
58
59    pub fn with_round_ceiling(mut self, round_ceiling: bool) -> Self {
60        self.round_ceiling = round_ceiling;
61        self
62    }
63
64    pub fn with_all_m_values_recalculated(mut self, recalc_all_tissues_m_values: bool) -> Self {
65        self.recalc_all_tissues_m_values = recalc_all_tissues_m_values;
66        self
67    }
68}
69
70impl Default for BuhlmannConfig {
71    fn default() -> Self {
72        Self {
73            gf: (100, 100),
74            surface_pressure: 1013,
75            deco_ascent_rate: 10.,
76            ceiling_type: CeilingType::Actual,
77            round_ceiling: false,
78            recalc_all_tissues_m_values: true,
79        }
80    }
81}
82
83impl DecoModelConfig for BuhlmannConfig {
84    fn validate(&self) -> Result<(), ConfigValidationErr> {
85        let Self {
86            gf,
87            surface_pressure,
88            deco_ascent_rate,
89            ..
90        } = self;
91
92        self.validate_gradient_factors(gf)?;
93        self.validate_surface_pressure(surface_pressure)?;
94        self.validate_deco_ascent_rate(deco_ascent_rate)?;
95
96        Ok(())
97    }
98
99    fn surface_pressure(&self) -> MbarPressure {
100        self.surface_pressure
101    }
102
103    fn deco_ascent_rate(&self) -> AscentRatePerMinute {
104        self.deco_ascent_rate
105    }
106
107    fn ceiling_type(&self) -> CeilingType {
108        self.ceiling_type
109    }
110
111    fn round_ceiling(&self) -> bool {
112        self.round_ceiling
113    }
114}
115
116impl BuhlmannConfig {
117    fn validate_gradient_factors(&self, gf: &GradientFactors) -> Result<(), ConfigValidationErr> {
118        let (gf_low, gf_high) = gf;
119        let gf_range = 1..=100;
120
121        if !gf_range.contains(gf_low) || !gf_range.contains(gf_high) {
122            return Err(ConfigValidationErr::new("gf", GF_RANGE_ERR_MSG));
123        }
124
125        if gf_low > gf_high {
126            return Err(ConfigValidationErr::new("gf", GF_ORDER_ERR_MSG));
127        }
128
129        Ok(())
130    }
131
132    fn validate_surface_pressure(
133        &self,
134        surface_pressure: &MbarPressure,
135    ) -> Result<(), ConfigValidationErr> {
136        let mbar_pressure_range = 500..=1500;
137        if !mbar_pressure_range.contains(surface_pressure) {
138            return Err(ConfigValidationErr::new(
139                "surface_pressure",
140                SURFACE_PRESSURE_ERR_MSG,
141            ));
142        }
143
144        Ok(())
145    }
146
147    fn validate_deco_ascent_rate(
148        &self,
149        deco_ascent_rate: &AscentRatePerMinute,
150    ) -> Result<(), ConfigValidationErr> {
151        let ascent_rate_range = 1.0..=30.0;
152        if !ascent_rate_range.contains(deco_ascent_rate) {
153            return Err(ConfigValidationErr::new(
154                "deco_ascent_rate",
155                DECO_ASCENT_RATE_ERR_MSG,
156            ));
157        }
158
159        Ok(())
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166
167    #[test]
168    fn test_default_config() {
169        let config = BuhlmannConfig::default();
170        assert_eq!(config.validate(), Ok(()));
171        assert_eq!(config.gf, (100, 100));
172        assert_eq!(config.deco_ascent_rate, 10.);
173        assert_eq!(config.ceiling_type, CeilingType::Actual);
174        assert!(!config.round_ceiling);
175    }
176
177    #[test]
178    fn test_variable_gradient_factors() {
179        let config = BuhlmannConfig::new().with_gradient_factors(30, 70);
180        assert_eq!(config.validate(), Ok(()));
181        assert_eq!(config.gf, (30, 70));
182    }
183
184    #[test]
185    fn test_gf_range() {
186        let invalid_gf_range_cases = vec![(1, 101), (0, 99), (120, 240)];
187        for case in invalid_gf_range_cases {
188            let (gf_low, gf_high) = case;
189            let config = BuhlmannConfig::new().with_gradient_factors(gf_low, gf_high);
190            assert_eq!(
191                config.validate(),
192                Err(ConfigValidationErr::new("gf", GF_RANGE_ERR_MSG))
193            );
194        }
195    }
196
197    #[test]
198    fn test_gf_order() {
199        let config = BuhlmannConfig::new().with_gradient_factors(90, 80);
200        assert_eq!(
201            config.validate(),
202            Err(ConfigValidationErr::new("gf", GF_ORDER_ERR_MSG))
203        );
204    }
205
206    #[test]
207    fn test_surface_pressure_config() {
208        let config = BuhlmannConfig::new().with_surface_pressure(1032);
209        assert_eq!(config.validate(), Ok(()));
210        assert_eq!(config.surface_pressure, 1032);
211    }
212
213    #[test]
214    fn test_invalid_surface_pressure_values() {
215        let invalid_surface_pressure_cases = vec![0, 100, 2000];
216        for invalid_case in invalid_surface_pressure_cases {
217            let config = BuhlmannConfig::new().with_surface_pressure(invalid_case);
218            assert_eq!(
219                config.validate(),
220                Err(ConfigValidationErr::new(
221                    "surface_pressure",
222                    SURFACE_PRESSURE_ERR_MSG
223                ))
224            );
225        }
226    }
227
228    #[test]
229    fn test_deco_ascent_rate_config() {
230        let config = BuhlmannConfig::new().with_deco_ascent_rate(15.5);
231        assert_eq!(config.validate(), Ok(()));
232        assert_eq!(config.deco_ascent_rate, 15.5);
233    }
234
235    #[test]
236    fn test_invalid_deco_ascent_rate_values() {
237        let invalid_deco_ascent_rate_cases = vec![-3., 0.5, 31.0, 50.5];
238        for invalid_case in invalid_deco_ascent_rate_cases {
239            let config = BuhlmannConfig::new().with_deco_ascent_rate(invalid_case);
240            assert_eq!(
241                config.validate(),
242                Err(ConfigValidationErr::new(
243                    "deco_ascent_rate",
244                    DECO_ASCENT_RATE_ERR_MSG
245                ))
246            );
247        }
248    }
249}