1use crate::error::{Error, Result};
7
8#[derive(Debug, Clone)]
10pub struct Partition {
11 pub id: u64,
12 pub start_night: i64,
13 pub end_night: i64,
14}
15
16#[derive(Debug, Clone)]
18pub struct PartitionSummary {
19 pub id: u64,
20 pub start_night: i64,
21 pub end_night: i64,
22 pub observations: i64,
23 pub findable: Option<i64>,
24 pub found: Option<i64>,
25 pub completeness: Option<f64>,
26 pub pure_known: Option<i64>,
27 pub pure_unknown: Option<i64>,
28 pub contaminated: Option<i64>,
29 pub mixed: Option<i64>,
30}
31
32pub fn create_single(nights: &[i64]) -> Result<Partition> {
34 if nights.is_empty() {
35 return Err(Error::InvalidInput(
36 "Cannot create partition from empty nights".to_string(),
37 ));
38 }
39 let min_night = *nights.iter().min().unwrap();
40 let max_night = *nights.iter().max().unwrap();
41 Ok(Partition {
42 id: 0,
43 start_night: min_night,
44 end_night: max_night,
45 })
46}
47
48pub fn create_linking_windows(
54 nights: &[i64],
55 detection_window: Option<i64>,
56 min_nights: Option<i64>,
57 sliding: bool,
58) -> Result<Vec<Partition>> {
59 if nights.is_empty() {
60 return Err(Error::InvalidInput(
61 "Cannot create partitions from empty nights".to_string(),
62 ));
63 }
64 let min_night = *nights.iter().min().unwrap();
65 let max_night = *nights.iter().max().unwrap();
66
67 let detection_window = match detection_window {
68 None => return Ok(vec![create_single(nights)?]),
69 Some(dw) => {
70 if dw >= (max_night - min_night + 1) {
71 max_night - min_night + 1
72 } else {
73 dw
74 }
75 }
76 };
77
78 let min_nights = min_nights.unwrap_or(detection_window);
79 if detection_window < min_nights {
80 return Err(Error::InvalidInput(
81 "Detection window must be >= min_nights".to_string(),
82 ));
83 }
84
85 let mut partitions = Vec::new();
86
87 if sliding {
88 let mut i: u64 = 0;
89 let mut start_night = min_night;
90 let mut end_night = start_night + min_nights - 1;
91 loop {
92 if end_night > max_night {
93 break;
94 }
95 partitions.push(Partition {
96 id: i,
97 start_night,
98 end_night,
99 });
100 i += 1;
101 end_night += 1;
102 if end_night - detection_window == start_night {
103 start_night += 1;
104 }
105 }
106 } else {
107 let mut i: u64 = 0;
108 let mut start = min_night;
109 while start <= max_night {
110 let end = (start + detection_window - 1).min(max_night);
111 partitions.push(Partition {
112 id: i,
113 start_night: start,
114 end_night: end,
115 });
116 i += 1;
117 start += detection_window;
118 }
119 }
120
121 Ok(partitions)
122}
123
124pub fn create_summaries(
128 obs_nights: &[i64],
129 partitions: &[Partition],
130 night_sorted_indices: &[usize],
131) -> Vec<PartitionSummary> {
132 partitions
133 .iter()
134 .map(|p| {
135 let lo = night_sorted_indices.partition_point(|&i| obs_nights[i] < p.start_night);
136 let hi = night_sorted_indices.partition_point(|&i| obs_nights[i] <= p.end_night);
137 let num_obs = (hi - lo) as i64;
138
139 PartitionSummary {
140 id: p.id,
141 start_night: p.start_night,
142 end_night: p.end_night,
143 observations: num_obs,
144 findable: None,
145 found: None,
146 completeness: None,
147 pure_known: None,
148 pure_unknown: None,
149 contaminated: None,
150 mixed: None,
151 }
152 })
153 .collect()
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 #[test]
161 fn test_create_single() {
162 let nights = vec![5, 3, 7, 1, 9];
163 let p = create_single(&nights).unwrap();
164 assert_eq!(p.start_night, 1);
165 assert_eq!(p.end_night, 9);
166 }
167
168 #[test]
169 fn test_create_single_empty() {
170 assert!(create_single(&[]).is_err());
171 }
172
173 #[test]
174 fn test_create_linking_windows_non_overlapping() {
175 let nights: Vec<i64> = (0..10).collect();
176 let partitions = create_linking_windows(&nights, Some(3), None, false).unwrap();
177 assert_eq!(partitions.len(), 4); assert_eq!(partitions[0].start_night, 0);
179 assert_eq!(partitions[0].end_night, 2);
180 assert_eq!(partitions[3].start_night, 9);
181 assert_eq!(partitions[3].end_night, 9);
182 }
183
184 #[test]
185 fn test_create_linking_windows_none() {
186 let nights = vec![1, 5, 10];
187 let partitions = create_linking_windows(&nights, None, None, false).unwrap();
188 assert_eq!(partitions.len(), 1);
189 assert_eq!(partitions[0].start_night, 1);
190 assert_eq!(partitions[0].end_night, 10);
191 }
192
193 #[test]
194 fn test_create_linking_windows_sliding() {
195 let nights: Vec<i64> = (0..10).collect();
196 let partitions = create_linking_windows(&nights, Some(5), Some(3), true).unwrap();
197 assert!(!partitions.is_empty());
199 assert_eq!(partitions[0].start_night, 0);
201 assert_eq!(partitions[0].end_night, 2);
202 assert!(partitions.last().unwrap().end_night <= 9);
204 }
205
206 #[test]
207 fn test_create_linking_windows_empty() {
208 assert!(create_linking_windows(&[], Some(3), None, false).is_err());
209 }
210
211 #[test]
212 fn test_create_linking_windows_window_too_small() {
213 let nights: Vec<i64> = (0..10).collect();
214 assert!(create_linking_windows(&nights, Some(2), Some(5), false).is_err());
215 }
216
217 #[test]
218 fn test_create_linking_windows_larger_than_range() {
219 let nights: Vec<i64> = (0..5).collect();
220 let partitions = create_linking_windows(&nights, Some(100), None, false).unwrap();
221 assert_eq!(partitions.len(), 1);
222 assert_eq!(partitions[0].start_night, 0);
223 assert_eq!(partitions[0].end_night, 4);
224 }
225
226 #[test]
227 fn test_create_summaries() {
228 let nights = vec![1, 1, 2, 2, 2, 3];
229 let partitions = vec![
230 Partition {
231 id: 0,
232 start_night: 1,
233 end_night: 2,
234 },
235 Partition {
236 id: 1,
237 start_night: 3,
238 end_night: 3,
239 },
240 ];
241 let sorted = crate::types::compute_night_sorted_indices(&nights);
242 let summaries = create_summaries(&nights, &partitions, &sorted);
243 assert_eq!(summaries.len(), 2);
244 assert_eq!(summaries[0].observations, 5); assert_eq!(summaries[1].observations, 1); }
247}