diving_decompression/
lib.rs

1// diving-decompression crate
2// Written in 2020 by
3// Ronald Alonzo <alonzo.ronald@gmail.com>
4
5// To the extent possible under law, the author(s) have dedicated all
6// copyright and related and neighboring rights to this software to
7// the public domain worldwide. This software is distributed without
8// any warranty.
9
10//! # Rust diving-decompression library
11//!
12//! # UNDER CONSTRUCTION
13
14//! ## DO NOT USE THIS PACKAGE UNTIL STABLE VERSION HAS BEEN RELEASED!!
15
16//! ## IMPORTANT NOTE FROM THE AUTHOR
17//! this package is under construction, it is **__NOT__** suitable for 
18//! usage in real dive operations neither commercial nor recreational, 
19//! as we need to make extensive test and audit the package reliability.
20//! it is not only a matter of applying unit testing as this calculations
21//! are crucial for divers safety; also regardless of the extensive tests 
22//! and trials in humans performed by the US Navy along the years with 
23//! regards of decompression sickness, it has been stated many times by 
24//! relevant stakeholders that these trials do not necessarily entail 100%
25//! accuracy on the results of undertaking dive operations within the 
26//! constraints of these dive tables. there are many factors that are not 
27//! taken into consideration (e.g: water temperature, diver physiological 
28//! fitness, unadverted PFOs... to name a few). 
29
30//! This is a library created with the purpose of assisting diving 
31//! professionals in planning decompression procedures for air diving
32//! operations as per the US Navy diving manual rev7. 
33//!
34//! It was initially written in TypeScript and then ported to Rust to
35//! harness the benefits of a much stronger type system. These safety 
36//! guarantees are of crucial importance when dealing with operational 
37//! and procedural safety in the commercial diving industry.
38//! 
39//! This project is and will always be 100% free and open source. 
40//! it is open for public review and we welcome PRs as long as they 
41//! adhere to international guidelines and acknowledged best practices
42//! in the industry, specially those contained within the US Navy dive 
43//! manual which is __THE ONLY__ scientifically derived set of guidelines.
44//! 
45//! Pull Requests based on anecdotical or empirical evidence or those
46//! that could contain private parties agendas will always be dismissed
47//! by the authors of this project. we do not tolerate private tables 
48//! and protocols that aim to distort the good practices in order to 
49//! increase allowed diving depth and time limits and shortened decompression
50//! procedures with economical purposes.   
51
52// Code conventions
53#![forbid(unsafe_code)]
54#![deny(non_snake_case)]
55#![deny(non_camel_case_types)]
56#![deny(non_upper_case_globals)]
57#![deny(unused_imports)]
58#![deny(unused_mut)]
59#![deny(missing_docs)]
60#![deny(dead_code)]
61#[macro_use]
62
63extern crate serde_derive;
64
65/// this module provides functionality for the US Navy dive tables
66pub mod airtables;
67
68/// single dive object
69#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
70pub struct Dive {
71  /// depth of the dive expressed in feet of sea water
72  pub depth: u16, 
73  /// bottom time of the dive expressed in minutes
74  pub bottom_time: u16,
75}
76
77/// single dive plan object
78#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
79pub struct DivePlan {
80  /// the depth of the first planned dive expressed in feet of sea water
81  pub depth: u16,
82  /// the bottom time of the first planned dive expressed in minutes
83  pub bottom_time: u16,
84  /// the planned surface interval time expressed in minutes
85  pub surface_interval_time: u16,
86  /// the depth of the next planned dive expressed in feet of sea water
87  pub next_dive_depth: u16,
88}
89
90impl Dive {
91  pub fn new(depth: u16, bottom_time: u16) -> Self {
92    //! Instantiate a new Dive object 
93    Self {
94      depth,
95      bottom_time,
96    }
97  }
98
99  pub fn no_decompression_limit(self) -> u16 {
100    //! Returns the no decompression limit of the Dive Object up to a depth of 190 feet of sea water
101    //! No decompression limit is returned in minutes as u16 integer
102
103    let mut ndl: u16 = 0;  
104    
105    if self.depth > 190 {
106      return ndl;
107    } 
108
109    let nodeco_table = airtables::nodeco_table()
110      .expect("Error serializing the data within the table nodeco_table");
111
112    for row in nodeco_table.table_data.iter() {
113      if row.min_fsw <= self.depth && self.depth <= row.max_fsw {
114        ndl = row.no_stop_limit
115      }
116    }
117
118    return ndl;
119  }
120
121  pub fn group_letter(self) -> String {
122    //! returns the group letter of the Dive object. 
123    //! the depth is expressed in feet of sea water
124    //! the bottom_time is expressed in minutes
125    //! the group letter is returned as a String
126    let mut gl: String = String::from("");
127
128    let nodeco_table = airtables::nodeco_table()
129      .expect("Error serializing the data within the deco_table");
130
131    for row in nodeco_table.table_data.iter() {
132      if row.min_fsw <= self.depth && self.depth <= row.max_fsw {
133        for value in row.values.iter() {
134          if value.min_time <= self.bottom_time && self.bottom_time <= value.max_time {
135            gl = String::from(&value.group_letter);
136          } 
137        }
138      }
139    }
140    
141    if gl == String::from("") && self.depth > 0 && self.depth <= 10 && self.bottom_time > 462 {
142      gl = String::from("F")
143    } else if gl == String::from("") && self.depth > 10 && self.depth <= 15 && self.bottom_time > 449 {
144      gl = String::from("I")
145    } else if gl == String::from("") && self.depth > 15 && self.depth <= 20 && self.bottom_time > 461 {
146      gl = String::from("L")
147    }
148
149    if gl == String::from("") && self.depth <= 190 {
150      gl = String::from("this dive is out of the time range for no-decompression air dives")
151    } else if gl == String::from("") && self.depth > 190 {
152      gl = String::from("this dive is out of the depth range for no-decompression air dives")
153    }
154
155    return gl;
156  }
157
158  pub fn deco_dive(self) -> airtables::RowDeco {
159    //! returns the decompression profile of the Dive object. 
160    //! the depth is expressed in feet of sea water
161    //! the bottom_time is expressed in minutes
162    //! the decompression profile is returned as a RowDeco struct
163    let deco_table = airtables::deco_table()
164      .expect("Error deserializing no decompression table");
165
166    let mut deco_profile: airtables::RowDeco = airtables::RowDeco {
167      min_time: 0,
168      max_time: 0,
169      air_tat: String::from("0"),
170      o2_tat: String::from("0"),
171      ttfs: String::from("0"),
172      o2cp: 0.0,
173      repetgroup_letter: String::from("0"),
174      surdo2_recommended: false,
175      exceptional_exposure: false,
176      surdo2_required: false,
177      strict_surdo2: false,
178      air_deco_stops: vec![],
179      o2_deco_stops: vec![],
180    };
181
182    for row_deco in deco_table.table_data.iter() {
183      if row_deco.min_fsw <= self.depth && self.depth <= row_deco.max_fsw  {
184        for profile in row_deco.rows.iter() {
185          if profile.min_time <= self.bottom_time && profile.max_time <= self.bottom_time {
186            deco_profile = profile.clone()
187          }
188        }
189      }
190    }
191    return deco_profile;
192  }
193}
194
195impl DivePlan {
196  pub fn new(depth: u16, bottom_time: u16, surface_interval_time: u16, next_dive_depth: u16) -> Self {
197    //! Instantiates a new Dive Plan object
198    //! the depth and next_dive_depth are expressed in feet of sea water
199    //! the bottom_time and surface_interval_time are expressed in minutes
200    //! the returned object is of type DivePlan
201    Self {
202      depth,
203      bottom_time,
204      surface_interval_time,
205      next_dive_depth,
206    }
207  }
208
209  pub fn from_dive(dive: Dive, surface_interval_time: u16, next_dive_depth: u16) -> Self {
210    //! Instantiates a new Dive Plan object from an existing Dive Object
211    //! the next_dive_depth is expressed in feet of sea water
212    //! the surface_interval_time is expressed in minutes
213    //! the returned object is of type DivePlan
214    Self {
215      depth: dive.depth,
216      bottom_time: dive.bottom_time,
217      surface_interval_time,
218      next_dive_depth,
219    }
220  }
221
222  pub fn no_decompression_limit(self) -> u16 {
223    //! returns the no decompression limit for the first dive of a DivePlan object 
224    //! No decompression limit is returned in minutes as u16 integer
225    let mut ndl: u16 = 0;  
226    
227    if self.depth > 190 {
228      return ndl;
229    } 
230
231    let nodeco_table = airtables::nodeco_table()
232      .expect("Error serializing the data withthe table deco_table");
233
234    for row in nodeco_table.table_data.iter() {
235      if row.min_fsw <= self.depth && self.depth <= row.max_fsw {
236        ndl = row.no_stop_limit
237      }
238    }
239
240    return ndl;
241  }
242
243  pub fn group_letter(self) -> String {
244    //! returns the group letter of the first dive of a DivePlan object. 
245    //! the depth is expressed in feet of sea water
246    //! the bottom_time is expressed in minutes
247    //! the group letter is returned as a String
248    let mut gl: String = String::from("");
249
250    let nodeco_table = airtables::nodeco_table()
251      .expect("Error serializing the data within the deco_table");
252
253    for row in nodeco_table.table_data.iter() {
254      if row.min_fsw <= self.depth && self.depth <= row.max_fsw {
255        for value in row.values.iter() {
256          if value.min_time <= self.bottom_time && self.bottom_time <= value.max_time {
257            gl = String::from(&value.group_letter);
258          } 
259        }
260      }
261    }
262    
263    if gl == String::from("") && self.depth > 0 && self.depth <= 10 && self.bottom_time > 462 {
264      gl = String::from("F")
265    } else if gl == String::from("") && self.depth > 10 && self.depth <= 15 && self.bottom_time > 449 {
266      gl = String::from("I")
267    } else if gl == String::from("") && self.depth > 15 && self.depth <= 20 && self.bottom_time > 461 {
268      gl = String::from("L")
269    }
270
271    if gl == String::from("") && self.depth <= 190 {
272      gl = String::from("this dive is out of the time range for no-decompression air dives")
273    } else if gl == String::from("") && self.depth > 190 {
274      gl = String::from("this dive is out of the depth range for no-decompression air dives")
275    }
276    return gl;
277  }
278
279  pub fn repet_letter(self) -> String {
280    //! Returns the repetitive group letter of the DivePlan object. 
281    //! the depth and next_dive_depth are expressed in feet of sea water
282    //! the bottom_time and surface_interval_time are expressed in minutes
283    //! the repetitive group letter is returned as a String
284    let nodeco_table = airtables::nodeco_table()
285      .expect("Error serializing the data within the deco_table");
286
287    let rgl_table = airtables::rgl_table()
288      .expect("there was an error deserializing deco table");
289  
290    let mut rl = String::new();
291  
292    for row in nodeco_table.table_data.iter() {
293      if row.min_fsw <= self.depth && self.depth <= row.max_fsw {
294        for group in row.values.iter() {
295          if group.min_time <= self.bottom_time && self.bottom_time <= group.max_time {
296            for rgl_row in rgl_table.table_data.iter() {
297              if rgl_row.group_letter == group.group_letter && rgl_row.min_time <= self.surface_interval_time && self.surface_interval_time <= rgl_row.max_time {
298                rl = String::from(&rgl_row.repet_letter)
299              }
300            }
301          } 
302        }
303      }
304    }
305
306    return rl;
307  }
308
309  pub fn residual_nitrogen_time(self) -> u16 {
310    //! Returns the residual nitrogen time of the DivePlan object. 
311    //! the depth and next_dive_depth are expressed in feet of sea water
312    //! the bottom_time and surface_interval_time are expressed in minutes
313    //! the residual nitrogen time is returned as a u16 integer
314    let nodeco_table = airtables::nodeco_table()
315      .expect("Error deserializing no decompression table");
316
317    let rgl_table = airtables::rgl_table()
318      .expect("Error deserializing repetitive group letter table");
319
320    let rnt_table = airtables::rnt_table()
321      .expect("Error deserializing residual nitrogen time table");
322  
323    let mut rnt = 0;
324    
325    for row in nodeco_table.table_data.iter() {
326      if row.min_fsw <= self.depth && self.depth <= row.max_fsw {
327        for group in row.values.iter() {
328          if group.min_time <= self.bottom_time && self.bottom_time <= group.max_time {
329            for rgl_row in rgl_table.table_data.iter() {
330              if rgl_row.group_letter == group.group_letter && rgl_row.min_time <= self.surface_interval_time && self.surface_interval_time <= rgl_row.max_time {
331                for rnt_column in rnt_table.table_data.iter() {
332                  if rnt_column.repet_letter == rgl_row.repet_letter {
333                    for element in rnt_column.rnt.iter() {
334                      if element.min_depth <= self.next_dive_depth && self.next_dive_depth <= element.max_depth {
335                        rnt = element.rnt
336                      } 
337                    }
338                  }
339                }
340              }
341            }
342          } 
343        }
344      }
345    }
346  
347    return rnt;
348  }
349}