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}