1use crate::parameters::DiveParameters;
2use crate::plan::DivePlan;
3use crate::result::DiveResult;
4use crate::{PPO2_MAXIMUM_DECO, PPO2_MINIMUM};
5use capra_core::common::dive_segment::SegmentType::{AscDesc, DecoStop};
6use capra_core::common::dive_segment::{DiveSegment, SegmentType};
7use capra_core::common::gas::Gas;
8use capra_core::common::tank::Tank;
9use capra_core::common::time_taken;
10use capra_core::deco::deco_algorithm::DecoAlgorithm;
11use std::cmp::Ordering;
12use std::collections::HashMap;
13use std::iter;
14use time::Duration;
15
16#[derive(Copy, Clone, Debug)]
18pub struct OpenCircuit<'a, T: DecoAlgorithm> {
19 deco_algorithm: T,
21 deco_gases: &'a [(Gas, Option<usize>)],
23 bottom_segments: &'a [(DiveSegment, Gas)],
25 parameters: DiveParameters,
27}
28
29impl<'a, T: DecoAlgorithm> OpenCircuit<'a, T> {
30 pub fn new(
41 deco_algorithm: T,
42 deco_gases: &'a [(Gas, Option<usize>)],
43 bottom_segments: &'a [(DiveSegment, Gas)],
44 parameters: DiveParameters,
45 ) -> Self {
46 OpenCircuit {
47 deco_algorithm,
48 deco_gases,
49 bottom_segments,
50 parameters,
51 }
52 }
53
54 fn filter_gases<'b>(
56 segment: &DiveSegment,
57 gases: &'b [(Gas, Option<usize>)],
58 metres_per_bar: f64,
59 ) -> Vec<&'b Gas> {
60 let mut candidates = gases
61 .iter()
62 .filter(|x| x.1.map_or(true, |t| t >= segment.start_depth()))
63 .map(|x| &x.0)
64 .filter(|a| a.in_ppo2_range(segment.start_depth(), PPO2_MINIMUM, PPO2_MAXIMUM_DECO))
65 .filter(|a| a.equivalent_narcotic_depth(segment.start_depth()) <= segment.start_depth())
66 .collect::<Vec<&Gas>>();
67
68 candidates.sort_by(|a, b| {
69 a.pp_o2(segment.start_depth(), metres_per_bar)
70 .partial_cmp(&b.pp_o2(segment.start_depth(), metres_per_bar))
71 .unwrap()
72 }); candidates
75 }
76
77 fn find_gas_switch_point<'c>(
79 segments: &'c [DiveSegment],
80 current_gas: &Gas,
81 gases: &'c [(Gas, Option<usize>)],
82 metres_per_bar: f64,
83 ) -> Option<(&'c DiveSegment, &'c Gas)> {
84 for stop in segments.iter().filter(|x| x.segment_type() != AscDesc) {
86 let candidate_gases = <OpenCircuit<'a, T>>::filter_gases(stop, gases, metres_per_bar);
87 if candidate_gases.is_empty() {
88 continue;
90 }
91 if candidate_gases[candidate_gases.len() - 1] != current_gas {
92 return Some((stop, &candidate_gases[candidate_gases.len() - 1]));
93 }
94 }
95 None
96 }
97
98 pub(crate) fn level_to_level(
101 &self,
102 mut deco: T,
103 start: &(DiveSegment, Gas),
104 end: Option<&(DiveSegment, Gas)>,
105 stops_performed: &mut Vec<(DiveSegment, Gas)>,
106 ) -> T {
107 if let Some(t) = end {
109 match start.0.end_depth().cmp(&t.0.start_depth()) {
110 Ordering::Less => {
111 let descent = DiveSegment::new(
113 SegmentType::AscDesc,
114 start.0.end_depth(),
115 t.0.start_depth(),
116 time_taken(
117 self.parameters.descent_rate,
118 start.0.end_depth(),
119 t.0.start_depth(),
120 ),
121 self.parameters.ascent_rate,
122 self.parameters.descent_rate,
123 )
124 .unwrap();
125 deco.add_dive_segment(&descent, &start.1, self.parameters.metres_per_bar);
126 stops_performed.push((descent, start.1));
127 return deco;
128 }
129 Ordering::Equal => {
130 return deco;
132 }
133 Ordering::Greater => {} }
135 }
136
137 let mut virtual_deco = deco;
138 let end_depth = match end {
140 Some(t) => t.0.start_depth(),
141 None => 0,
142 };
143 let stops = virtual_deco
144 .surface(
145 self.parameters.ascent_rate,
146 self.parameters.descent_rate,
147 &start.1,
148 self.parameters.metres_per_bar,
149 )
150 .into_iter()
151 .take_while(|x| x.start_depth() > end_depth)
152 .collect::<Vec<DiveSegment>>();
153
154 let switch_gases: Vec<(Gas, Option<usize>)> = match end {
155 Some(t) => vec![(start.1, None), (t.1, None)],
156 None => self.deco_gases.to_vec(),
157 };
158
159 let switch_point = <OpenCircuit<'a, T>>::find_gas_switch_point(
160 &stops,
161 &start.1,
162 &switch_gases,
163 self.parameters.metres_per_bar,
164 );
165
166 if let (Some(switch), true) = (
168 switch_point,
169 stops.iter().any(|x| x.segment_type() == DecoStop),
170 ) {
171 virtual_deco = deco;
173
174 for stop in stops
176 .iter()
177 .take_while(|x| x.start_depth() > switch.0.start_depth())
178 {
179 virtual_deco.add_dive_segment(&stop, &start.1, self.parameters.metres_per_bar);
180 stops_performed.push((*stop, start.1));
181 }
182
183 let new_stop = match virtual_deco
185 .get_stops(
186 self.parameters.ascent_rate,
187 self.parameters.descent_rate,
188 &switch.1,
189 self.parameters.metres_per_bar,
190 )
191 .into_iter()
192 .find(|x| x.segment_type() == DecoStop && x.start_depth() == switch.0.start_depth())
193 {
194 Some(t) => t,
195 None => DiveSegment::new(
196 SegmentType::DecoStop,
197 switch.0.start_depth(),
198 switch.0.end_depth(),
199 Duration::minute(),
200 self.parameters.ascent_rate,
201 self.parameters.descent_rate,
202 )
203 .unwrap(),
204 };
205
206 virtual_deco.add_dive_segment(&new_stop, switch.1, self.parameters.metres_per_bar);
207 stops_performed.push((new_stop, *switch.1));
208
209 deco = virtual_deco;
211 self.level_to_level(deco, &(new_stop, *switch.1), end, stops_performed)
212 } else {
213 stops_performed.append(&mut stops.into_iter().zip(iter::repeat(start.1)).collect());
215 deco = virtual_deco;
216 deco
217 }
218 }
219}
220
221impl<'a, T: DecoAlgorithm> DivePlan<T> for OpenCircuit<'a, T> {
222 fn plan(&self) -> DiveResult<T> {
223 let mut total_segments: Vec<(DiveSegment, Gas)> = Vec::new();
224 let mut deco = self.deco_algorithm;
225
226 let descent_to_beginning = DiveSegment::new(
228 AscDesc,
229 0,
230 self.bottom_segments[0].0.start_depth(),
231 time_taken(
232 self.parameters.descent_rate,
233 0,
234 self.bottom_segments[0].0.start_depth(),
235 ),
236 self.parameters.ascent_rate,
237 self.parameters.descent_rate,
238 )
239 .unwrap();
240
241 deco.add_dive_segment(
242 &descent_to_beginning,
243 &self.bottom_segments[0].1,
244 self.parameters.metres_per_bar,
245 );
246 total_segments.push((descent_to_beginning, self.bottom_segments[0].1));
247
248 for win in self.bottom_segments.windows(2) {
249 let mut stops_performed: Vec<(DiveSegment, Gas)> = Vec::new();
250 let start = win[0];
251 let end = win[1];
252
253 deco.add_dive_segment(&start.0, &start.1, self.parameters.metres_per_bar);
254 total_segments.push(start);
255
256 deco = self.level_to_level(deco, &start, Some(&end), &mut stops_performed);
257 total_segments.append(&mut stops_performed);
258 }
259
260 let final_stop = self.bottom_segments.last().unwrap();
262 deco.add_dive_segment(&final_stop.0, &final_stop.1, self.parameters.metres_per_bar);
263 total_segments.push(*final_stop);
264
265 let mut stops_performed: Vec<(DiveSegment, Gas)> = Vec::new();
266 deco = self.level_to_level(deco, &final_stop, None, &mut stops_performed);
267 total_segments.append(&mut stops_performed);
268
269 let mut gas_plan: HashMap<Gas, usize> = HashMap::new();
271 for (segment, gas) in &total_segments {
272 let gas_consumed =
273 match segment.segment_type() {
274 SegmentType::DecoStop => segment
275 .gas_consumed(self.parameters.sac_deco, self.parameters.metres_per_bar),
276 SegmentType::NoDeco => 0, _ => segment
278 .gas_consumed(self.parameters.sac_bottom, self.parameters.metres_per_bar),
279 };
280 let gas_needed = *(gas_plan.entry(*gas).or_insert(0)) + gas_consumed;
281 gas_plan.insert(*gas, gas_needed);
282 }
283
284 DiveResult::new(deco, total_segments, gas_plan)
285 }
286
287 fn plan_backwards(&self, _tanks: &[Tank]) -> DiveResult<T> {
288 unimplemented!()
289 }
290}