1use std::sync::Arc;
2
3use timsrust_core::utils::vec::argsort;
4use timsrust_core::{
5 Im, InvertibleConverter, IsolationWindow, Mz, QuadrupoleSettings, ScanIndex,
6};
7
8use super::{
9 TDFPathLike,
10 file_readers::sql_reader::{
11 ReadableSqlTable, SqlReader, SqlReaderError,
12 frame_groups::SqlWindowGroup, quad_settings::SqlQuadSettings,
13 },
14};
15
16pub struct QuadrupoleSettingsReader {
17 quadrupole_settings: Vec<QuadrupoleSettings>,
18 sql_quadrupole_settings: Vec<SqlQuadSettings>,
19}
20
21impl QuadrupoleSettingsReader {
22 #[allow(clippy::new_ret_no_self)]
24 pub fn new(
25 path: impl TDFPathLike,
26 ) -> Result<Vec<QuadrupoleSettings>, QuadrupoleSettingsReaderError> {
27 let tdf_sql_reader = SqlReader::open(path)?;
28 Self::from_sql_settings(&tdf_sql_reader)
29 }
30
31 fn from_sql_settings(
32 tdf_sql_reader: &SqlReader,
33 ) -> Result<Vec<QuadrupoleSettings>, QuadrupoleSettingsReaderError> {
34 let sql_quadrupole_settings =
35 SqlQuadSettings::from_sql_reader(tdf_sql_reader)?;
36 let window_group_count = sql_quadrupole_settings
37 .iter()
38 .map(|x| x.window_group)
39 .max()
40 .expect("SqlReader cannot return empty vecs, so there is always a max window_group");
41 let quadrupole_settings = (0..window_group_count)
42 .map(|window_group| QuadrupoleSettings {
43 index: window_group + 1,
44 ..Default::default()
45 })
46 .collect();
47 let mut quad_reader = Self {
48 quadrupole_settings,
49 sql_quadrupole_settings,
50 };
51 quad_reader.update_from_sql_quadrupole_settings();
52 quad_reader.resort_groups();
53 Ok(quad_reader.quadrupole_settings)
54 }
55
56 pub(crate) fn from_splitting<ImC: InvertibleConverter<ScanIndex, Im>>(
57 tdf_sql_reader: &SqlReader,
58 splitting_strat: FrameWindowSplittingStrategy<ImC>,
59 ) -> Result<Vec<QuadrupoleSettings>, QuadrupoleSettingsReaderError> {
60 let quadrupole_settings = Self::from_sql_settings(tdf_sql_reader)?;
61 let window_groups = SqlWindowGroup::from_sql_reader(tdf_sql_reader)?;
62 let expanded_quadrupole_settings = match splitting_strat {
63 FrameWindowSplittingStrategy::Quadrupole(x) => {
64 expand_quadrupole_settings(
65 &window_groups,
66 &quadrupole_settings,
67 &x,
68 )
69 },
70 FrameWindowSplittingStrategy::Window(x) => {
71 expand_window_settings(&window_groups, &quadrupole_settings, &x)
72 },
73 };
74 Ok(expanded_quadrupole_settings)
75 }
76
77 fn update_from_sql_quadrupole_settings(&mut self) {
78 for window_group in self.sql_quadrupole_settings.iter() {
79 let group = window_group.window_group - 1;
80 self.quadrupole_settings[group]
81 .scan_starts
82 .push(window_group.scan_start);
83 self.quadrupole_settings[group]
84 .scan_ends
85 .push(window_group.scan_end);
86 let isolation_window = IsolationWindow::new_from_center(
87 Mz::from(window_group.mz_center),
88 Mz::from(window_group.mz_width),
89 window_group.collision_energy,
90 );
91 self.quadrupole_settings[group]
92 .isolation_windows
93 .push(isolation_window);
94 }
95 }
96
97 fn resort_groups(&mut self) {
98 self.quadrupole_settings = self
99 .quadrupole_settings
100 .iter()
101 .map(|_window| {
102 let mut window = _window.clone();
103 let order = argsort(&window.scan_starts);
104 window.isolation_windows = order
105 .iter()
106 .map(|&i| window.isolation_windows[i].clone())
107 .collect();
108 window.scan_starts =
109 order.iter().map(|&i| window.scan_starts[i]).collect();
110 window.scan_ends =
111 order.iter().map(|&i| window.scan_ends[i]).collect();
112 window
113 })
114 .collect();
115 }
116}
117
118#[allow(private_interfaces)]
119#[derive(Debug, thiserror::Error)]
120pub enum QuadrupoleSettingsReaderError {
121 #[error("{0}")]
122 SqlReaderError(#[from] SqlReaderError),
123}
124
125type MobilitySpanStep = (f64, f64);
126type ScanSpanStep = (usize, usize);
127
128#[derive(Debug)]
153pub enum QuadWindowExpansionStrategy<ImC> {
154 None,
155 Even(usize),
156 UniformMobility(MobilitySpanStep, Option<Arc<ImC>>),
157 UniformScan(ScanSpanStep),
158}
159
160impl<ImC> Clone for QuadWindowExpansionStrategy<ImC> {
161 fn clone(&self) -> Self {
162 match self {
163 Self::None => Self::None,
164 Self::Even(n) => Self::Even(*n),
165 Self::UniformMobility(span_step, converter) => {
166 Self::UniformMobility(*span_step, converter.clone())
167 },
168 Self::UniformScan(span_step) => Self::UniformScan(*span_step),
169 }
170 }
171}
172
173impl<ImC> Default for QuadWindowExpansionStrategy<ImC> {
174 fn default() -> Self {
175 Self::Even(1)
176 }
177}
178
179#[derive(Debug)]
180pub(crate) enum FrameWindowSplittingStrategy<ImC> {
181 Quadrupole(QuadWindowExpansionStrategy<ImC>),
182 Window(QuadWindowExpansionStrategy<ImC>),
183}
184
185impl<ImC> Clone for FrameWindowSplittingStrategy<ImC> {
186 fn clone(&self) -> Self {
187 match self {
188 Self::Quadrupole(s) => Self::Quadrupole(s.clone()),
189 Self::Window(s) => Self::Window(s.clone()),
190 }
191 }
192}
193
194#[derive(Debug)]
195pub enum FrameWindowSplittingConfiguration<ImC> {
196 Quadrupole(QuadWindowExpansionStrategy<ImC>),
197 Window(QuadWindowExpansionStrategy<ImC>),
198}
199
200impl<ImC> Clone for FrameWindowSplittingConfiguration<ImC> {
201 fn clone(&self) -> Self {
202 match self {
203 Self::Quadrupole(s) => Self::Quadrupole(s.clone()),
204 Self::Window(s) => Self::Window(s.clone()),
205 }
206 }
207}
208
209impl<ImC> Default for FrameWindowSplittingConfiguration<ImC> {
210 fn default() -> Self {
211 Self::Quadrupole(QuadWindowExpansionStrategy::Even(1))
212 }
213}
214
215impl<ImC> FrameWindowSplittingConfiguration<ImC> {
216 pub(crate) fn finalize(
217 self,
218 scan_converter: Option<Arc<ImC>>,
219 ) -> FrameWindowSplittingStrategy<ImC> {
220 match self {
221 Self::Quadrupole(x) => FrameWindowSplittingStrategy::Quadrupole(
222 Self::update_im_converter(x, scan_converter),
223 ),
224 Self::Window(x) => FrameWindowSplittingStrategy::Window(
225 Self::update_im_converter(x, scan_converter),
226 ),
227 }
228 }
229
230 fn update_im_converter(
231 quad_strategy: QuadWindowExpansionStrategy<ImC>,
232 scan_converter: Option<Arc<ImC>>,
233 ) -> QuadWindowExpansionStrategy<ImC> {
234 match quad_strategy {
235 QuadWindowExpansionStrategy::UniformMobility((span, step), _) => {
236 QuadWindowExpansionStrategy::UniformMobility(
237 (span, step),
238 scan_converter,
239 )
240 },
241 _ => quad_strategy,
242 }
243 }
244}
245
246fn scan_range_subsplit<ImC: InvertibleConverter<ScanIndex, Im>>(
247 start: usize,
248 end: usize,
249 strategy: &QuadWindowExpansionStrategy<ImC>,
250) -> Vec<(usize, usize)> {
251 let out: Vec<(usize, usize)> = match strategy {
252 QuadWindowExpansionStrategy::None => {
253 vec![(start, end)]
254 },
255 QuadWindowExpansionStrategy::Even(num_splits) => {
256 let sub_subwindow_width = (end - start) / (num_splits + 1);
257 let mut out = Vec::new();
258 for sub_subwindow in 0..*num_splits {
259 let sub_subwindow_scan_start =
260 start + (sub_subwindow_width * sub_subwindow);
261 let sub_subwindow_scan_end =
262 start + (sub_subwindow_width * (sub_subwindow + 2));
263
264 out.push((sub_subwindow_scan_start, sub_subwindow_scan_end))
265 }
266 out
267 },
268 QuadWindowExpansionStrategy::UniformMobility(
269 (span, step),
270 _converter,
271 ) => {
272 let converter = _converter.clone().unwrap(); let mut curr_start_offset = start;
276 let mut curr_start_im = f64::from(
278 converter.convert(
279 ScanIndex::try_from(curr_start_offset)
280 .expect("ScanIndex conversion out of bounds"),
281 ),
282 );
283
284 let mut curr_end_im = curr_start_im - span;
285 let mut curr_end_offset =
286 usize::from(converter.convert(Im::from(curr_end_im)));
287
288 let mut out = Vec::new();
289 while curr_end_offset < end {
290 out.push((curr_start_offset, curr_end_offset));
291
292 curr_start_im -= step;
293 curr_start_offset =
294 usize::from(converter.convert(Im::from(curr_start_im)));
295
296 curr_end_im = curr_start_im - span;
297 curr_end_offset =
298 usize::from(converter.convert(Im::from(curr_end_im)));
299 }
300 if curr_start_offset < end {
301 out.push((curr_start_offset, end));
302 }
303 out
304 },
305 QuadWindowExpansionStrategy::UniformScan((span, step)) => {
306 let mut curr_start_offset = start;
307 let mut curr_end_offset = start + span;
308 let mut out = Vec::new();
309
310 while curr_end_offset < end {
311 out.push((curr_start_offset, curr_end_offset));
312 curr_start_offset += step;
313 curr_end_offset += step;
314 }
315 if curr_start_offset < end {
316 out.push((curr_start_offset, end));
317 }
318 out
319 },
320 };
321
322 debug_assert!(
323 out.iter().all(|(s, e)| s < e),
324 "Invalid scan range: {:?}",
325 out
326 );
327 debug_assert!(
328 out.iter().all(|(s, e)| *s >= start && *e <= end),
329 "Invalid scan range: {:?}",
330 out
331 );
332 out
333}
334
335fn expand_window_settings<ImC: InvertibleConverter<ScanIndex, Im>>(
336 window_groups: &[SqlWindowGroup],
337 quadrupole_settings: &[QuadrupoleSettings],
338 strategy: &QuadWindowExpansionStrategy<ImC>,
339) -> Vec<QuadrupoleSettings> {
340 let mut expanded_quadrupole_settings: Vec<QuadrupoleSettings> = vec![];
341 for window_group in window_groups {
342 let window = window_group.window_group;
343 let frame = window_group.frame;
344 let group = &quadrupole_settings[window - 1];
345 let window_group_start = *group
346 .scan_starts
347 .iter()
348 .min()
349 .expect("SqlReader cannot return empty vecs, so there is always min window_group index");
350 let window_group_end = *group
351 .scan_ends
352 .iter()
353 .max()
354 .expect("SqlReader cannot return empty vecs, so there is always max window_group index");
355 for (sws, swe) in
356 scan_range_subsplit(window_group_start, window_group_end, strategy)
357 {
358 let mut mz_min = f64::MAX;
359 let mut mz_max = f64::MIN;
360 let mut nce_sum = 0.0;
361 let mut total_scan_width = 0.0;
362 for i in 0..group.len() {
363 let gss = group.scan_starts[i];
364 let gse = group.scan_ends[i];
365 if (swe <= gse) || (gss <= sws) {
366 continue;
367 }
368 let half_isolation_width =
369 f64::from(group.isolation_windows[i].width()) / 2.0;
370 let isolation_mz =
371 f64::from(group.isolation_windows[i].center());
372 mz_min = mz_min.min(isolation_mz - half_isolation_width);
373 mz_max = mz_max.max(isolation_mz + half_isolation_width);
374 let scan_width = (gse.min(swe) - gss.max(sws)) as f64;
375 nce_sum +=
376 group.isolation_windows[i].collision_energy() * scan_width;
377 total_scan_width += scan_width
378 }
379 let isolation_window = IsolationWindow::new_from_bounds(
380 Mz::from(mz_min),
381 Mz::from(mz_max),
382 nce_sum / total_scan_width,
383 );
384 let sub_quad_settings = QuadrupoleSettings {
385 index: frame,
386 scan_starts: vec![sws],
387 scan_ends: vec![swe],
388 isolation_windows: vec![isolation_window],
389 };
390 expanded_quadrupole_settings.push(sub_quad_settings)
391 }
392 }
393 expanded_quadrupole_settings
394}
395
396fn expand_quadrupole_settings<ImC: InvertibleConverter<ScanIndex, Im>>(
397 window_groups: &[SqlWindowGroup],
398 quadrupole_settings: &[QuadrupoleSettings],
399 strategy: &QuadWindowExpansionStrategy<ImC>,
400) -> Vec<QuadrupoleSettings> {
401 let mut expanded_quadrupole_settings: Vec<QuadrupoleSettings> = vec![];
402 for window_group in window_groups {
403 let window = window_group.window_group;
404 let frame = window_group.frame;
405 let group = &quadrupole_settings[window - 1];
406 for sub_window in 0..group.isolation_windows.len() {
407 let subwindow_scan_start = group.scan_starts[sub_window];
408 let subwindow_scan_end = group.scan_ends[sub_window];
409 for (sws, swe) in scan_range_subsplit(
410 subwindow_scan_start,
411 subwindow_scan_end,
412 strategy,
413 ) {
414 let isolation_window = &group.isolation_windows[sub_window];
415 let sub_quad_settings = QuadrupoleSettings {
416 index: frame,
417 scan_starts: vec![sws],
418 scan_ends: vec![swe],
419 isolation_windows: vec![isolation_window.clone()],
420 };
421 expanded_quadrupole_settings.push(sub_quad_settings)
422 }
423 }
424 }
425 expanded_quadrupole_settings
426}