1use crate::error::{CoreError, CoreResult, ErrorContext, ErrorLocation};
37
38pub fn parallel_prefix_sum<T>(data: &[T]) -> Vec<T>
51where
52 T: Clone + Send + Sync + Default + std::ops::Add<Output = T>,
53{
54 parallel_scan(data, T::default(), |a, b| a + b)
55}
56
57pub fn parallel_prefix_sum_exclusive<T>(data: &[T]) -> Vec<T>
64where
65 T: Clone + Send + Sync + Default + std::ops::Add<Output = T>,
66{
67 parallel_scan_exclusive(data, T::default(), |a, b| a + b)
68}
69
70pub fn parallel_scan<T, F>(data: &[T], identity: T, op: F) -> Vec<T>
90where
91 T: Clone + Send + Sync,
92 F: Fn(T, T) -> T + Send + Sync + Clone,
93{
94 if data.is_empty() {
95 return Vec::new();
96 }
97
98 let n = data.len();
99
100 if n < SEQUENTIAL_THRESHOLD {
102 return sequential_inclusive_scan(data, &identity, &op);
103 }
104
105 blelloch_inclusive_scan(data, &identity, &op)
106}
107
108pub fn parallel_scan_exclusive<T, F>(data: &[T], identity: T, op: F) -> Vec<T>
113where
114 T: Clone + Send + Sync,
115 F: Fn(T, T) -> T + Send + Sync + Clone,
116{
117 if data.is_empty() {
118 return Vec::new();
119 }
120
121 let n = data.len();
122
123 if n < SEQUENTIAL_THRESHOLD {
124 return sequential_exclusive_scan(data, &identity, &op);
125 }
126
127 blelloch_exclusive_scan(data, &identity, &op)
128}
129
130pub fn try_parallel_prefix_sum<T>(data: &[T]) -> CoreResult<Vec<T>>
133where
134 T: Clone + Send + Sync + Default + std::ops::Add<Output = T>,
135{
136 Ok(parallel_prefix_sum(data))
137}
138
139pub fn try_parallel_scan<T, F>(data: &[T], identity: T, op: F) -> CoreResult<Vec<T>>
141where
142 T: Clone + Send + Sync,
143 F: Fn(T, T) -> T + Send + Sync + Clone,
144{
145 if data.is_empty() {
146 return Err(CoreError::ValueError(
147 ErrorContext::new("parallel_scan requires non-empty input".to_string())
148 .with_location(ErrorLocation::new(file!(), line!())),
149 ));
150 }
151 Ok(parallel_scan(data, identity, op))
152}
153
154const SEQUENTIAL_THRESHOLD: usize = 1024;
160
161const TILE_SIZE: usize = 256;
164
165fn sequential_inclusive_scan<T, F>(data: &[T], identity: &T, op: &F) -> Vec<T>
166where
167 T: Clone,
168 F: Fn(T, T) -> T,
169{
170 let mut result = Vec::with_capacity(data.len());
171 let mut acc = identity.clone();
172 for item in data {
173 acc = op(acc, item.clone());
174 result.push(acc.clone());
175 }
176 result
177}
178
179fn sequential_exclusive_scan<T, F>(data: &[T], identity: &T, op: &F) -> Vec<T>
180where
181 T: Clone,
182 F: Fn(T, T) -> T,
183{
184 let mut result = Vec::with_capacity(data.len());
185 let mut acc = identity.clone();
186 for item in data {
187 result.push(acc.clone());
188 acc = op(acc, item.clone());
189 }
190 result
191}
192
193#[cfg(feature = "parallel")]
195fn blelloch_inclusive_scan<T, F>(data: &[T], identity: &T, op: &F) -> Vec<T>
196where
197 T: Clone + Send + Sync,
198 F: Fn(T, T) -> T + Send + Sync + Clone,
199{
200 use rayon::prelude::*;
201
202 let n = data.len();
203 let num_tiles = (n + TILE_SIZE - 1) / TILE_SIZE;
204
205 let tile_reductions: Vec<T> = (0..num_tiles)
207 .into_par_iter()
208 .map(|tile_idx| {
209 let start = tile_idx * TILE_SIZE;
210 let end = (start + TILE_SIZE).min(n);
211 let mut acc = identity.clone();
212 for item in &data[start..end] {
213 acc = op(acc, item.clone());
214 }
215 acc
216 })
217 .collect();
218
219 let mut tile_prefixes: Vec<T> = Vec::with_capacity(num_tiles);
221 {
222 let mut acc = identity.clone();
223 for red in &tile_reductions {
224 tile_prefixes.push(acc.clone());
225 acc = op(acc, red.clone());
226 }
227 }
228
229 let mut result: Vec<T> = vec![identity.clone(); n];
231 let result_chunks: Vec<&mut [T]> = result.chunks_mut(TILE_SIZE).collect();
232
233 let result_ptr = result.as_mut_ptr();
235 let data_ref = data;
236
237 std::thread::scope(|s| {
240 let mut handles = Vec::with_capacity(num_tiles);
241 for tile_idx in 0..num_tiles {
242 let start = tile_idx * TILE_SIZE;
243 let end = (start + TILE_SIZE).min(n);
244 let tile_prefix = tile_prefixes[tile_idx].clone();
245 let op_clone = op.clone();
246 let tile_data = &data_ref[start..end];
247
248 let handle = s.spawn(move || {
249 let mut acc = tile_prefix;
250 let mut local_result = Vec::with_capacity(end - start);
251 for item in tile_data {
252 acc = op_clone(acc, item.clone());
253 local_result.push(acc.clone());
254 }
255 (start, local_result)
256 });
257 handles.push(handle);
258 }
259
260 for handle in handles {
261 if let Ok((start, local_result)) = handle.join() {
262 for (i, val) in local_result.into_iter().enumerate() {
263 unsafe {
265 std::ptr::write(result_ptr.add(start + i), val);
266 }
267 }
268 }
269 }
270 });
271
272 result
273}
274
275#[cfg(feature = "parallel")]
277fn blelloch_exclusive_scan<T, F>(data: &[T], identity: &T, op: &F) -> Vec<T>
278where
279 T: Clone + Send + Sync,
280 F: Fn(T, T) -> T + Send + Sync + Clone,
281{
282 use rayon::prelude::*;
283
284 let n = data.len();
285 let num_tiles = (n + TILE_SIZE - 1) / TILE_SIZE;
286
287 let tile_reductions: Vec<T> = (0..num_tiles)
289 .into_par_iter()
290 .map(|tile_idx| {
291 let start = tile_idx * TILE_SIZE;
292 let end = (start + TILE_SIZE).min(n);
293 let mut acc = identity.clone();
294 for item in &data[start..end] {
295 acc = op(acc, item.clone());
296 }
297 acc
298 })
299 .collect();
300
301 let mut tile_prefixes: Vec<T> = Vec::with_capacity(num_tiles);
303 {
304 let mut acc = identity.clone();
305 for red in &tile_reductions {
306 tile_prefixes.push(acc.clone());
307 acc = op(acc, red.clone());
308 }
309 }
310
311 let mut result: Vec<T> = vec![identity.clone(); n];
313 let result_ptr = result.as_mut_ptr();
314 let data_ref = data;
315
316 std::thread::scope(|s| {
317 let mut handles = Vec::with_capacity(num_tiles);
318 for tile_idx in 0..num_tiles {
319 let start = tile_idx * TILE_SIZE;
320 let end = (start + TILE_SIZE).min(n);
321 let tile_prefix = tile_prefixes[tile_idx].clone();
322 let op_clone = op.clone();
323 let tile_data = &data_ref[start..end];
324
325 let handle = s.spawn(move || {
326 let mut acc = tile_prefix;
327 let mut local_result = Vec::with_capacity(end - start);
328 for item in tile_data {
329 local_result.push(acc.clone());
330 acc = op_clone(acc, item.clone());
331 }
332 (start, local_result)
333 });
334 handles.push(handle);
335 }
336
337 for handle in handles {
338 if let Ok((start, local_result)) = handle.join() {
339 for (i, val) in local_result.into_iter().enumerate() {
340 unsafe {
341 std::ptr::write(result_ptr.add(start + i), val);
342 }
343 }
344 }
345 }
346 });
347
348 result
349}
350
351#[cfg(not(feature = "parallel"))]
353fn blelloch_inclusive_scan<T, F>(data: &[T], identity: &T, op: &F) -> Vec<T>
354where
355 T: Clone + Send + Sync,
356 F: Fn(T, T) -> T + Send + Sync + Clone,
357{
358 sequential_inclusive_scan(data, identity, op)
359}
360
361#[cfg(not(feature = "parallel"))]
363fn blelloch_exclusive_scan<T, F>(data: &[T], identity: &T, op: &F) -> Vec<T>
364where
365 T: Clone + Send + Sync,
366 F: Fn(T, T) -> T + Send + Sync + Clone,
367{
368 sequential_exclusive_scan(data, identity, op)
369}
370
371pub fn parallel_prefix_sum_f64(data: &[f64]) -> Vec<f64> {
380 parallel_prefix_sum(data)
381}
382
383pub fn parallel_prefix_sum_i64(data: &[i64]) -> Vec<i64> {
385 parallel_prefix_sum(data)
386}
387
388pub fn parallel_prefix_min<T>(data: &[T]) -> Vec<T>
390where
391 T: Clone + Send + Sync + Ord,
392{
393 if data.is_empty() {
394 return Vec::new();
395 }
396 let identity = data[0].clone();
397 parallel_scan(data, identity, |a, b| if a <= b { a } else { b })
398}
399
400pub fn parallel_prefix_max<T>(data: &[T]) -> Vec<T>
402where
403 T: Clone + Send + Sync + Ord,
404{
405 if data.is_empty() {
406 return Vec::new();
407 }
408 let identity = data[0].clone();
409 parallel_scan(data, identity, |a, b| if a >= b { a } else { b })
410}
411
412pub fn segmented_prefix_sum<T>(data: &[T], flags: &[bool]) -> Vec<T>
428where
429 T: Clone + Send + Sync + Default + std::ops::Add<Output = T>,
430{
431 let n = data.len().min(flags.len());
432 if n == 0 {
433 return Vec::new();
434 }
435
436 let mut result = Vec::with_capacity(n);
437 let mut acc = T::default();
438
439 for i in 0..n {
440 if flags[i] {
441 acc = T::default();
442 }
443 acc = acc + data[i].clone();
444 result.push(acc.clone());
445 }
446
447 result
448}
449
450#[cfg(test)]
455mod tests {
456 use super::*;
457
458 #[test]
459 fn test_empty_prefix_sum() {
460 let data: Vec<i32> = Vec::new();
461 assert!(parallel_prefix_sum(&data).is_empty());
462 assert!(parallel_prefix_sum_exclusive(&data).is_empty());
463 }
464
465 #[test]
466 fn test_single_element() {
467 assert_eq!(parallel_prefix_sum(&[42]), vec![42]);
468 assert_eq!(parallel_prefix_sum_exclusive(&[42]), vec![0]);
469 }
470
471 #[test]
472 fn test_small_inclusive_sum() {
473 let data = vec![1, 2, 3, 4, 5];
474 let result = parallel_prefix_sum(&data);
475 assert_eq!(result, vec![1, 3, 6, 10, 15]);
476 }
477
478 #[test]
479 fn test_small_exclusive_sum() {
480 let data = vec![1, 2, 3, 4, 5];
481 let result = parallel_prefix_sum_exclusive(&data);
482 assert_eq!(result, vec![0, 1, 3, 6, 10]);
483 }
484
485 #[test]
486 fn test_generic_scan_multiplication() {
487 let data = vec![1, 2, 3, 4, 5];
488 let result = parallel_scan(&data, 1, |a, b| a * b);
489 assert_eq!(result, vec![1, 2, 6, 24, 120]);
490 }
491
492 #[test]
493 fn test_generic_scan_max() {
494 let data = vec![3, 1, 4, 1, 5, 9, 2, 6];
495 let result = parallel_prefix_max(&data);
496 assert_eq!(result, vec![3, 3, 4, 4, 5, 9, 9, 9]);
497 }
498
499 #[test]
500 fn test_generic_scan_min() {
501 let data = vec![5, 3, 7, 1, 4, 2, 8, 6];
502 let result = parallel_prefix_min(&data);
503 assert_eq!(result, vec![5, 3, 3, 1, 1, 1, 1, 1]);
504 }
505
506 #[test]
507 fn test_large_prefix_sum() {
508 let n = 5000;
510 let data: Vec<i64> = (1..=n).collect();
511 let result = parallel_prefix_sum(&data);
512 assert_eq!(result[0], 1);
514 assert_eq!(result[n as usize - 1], n * (n + 1) / 2);
515 for i in 1..result.len() {
517 assert!(result[i] > result[i - 1]);
518 }
519 }
520
521 #[test]
522 fn test_large_exclusive_sum() {
523 let n = 5000;
524 let data: Vec<i64> = (1..=n).collect();
525 let result = parallel_prefix_sum_exclusive(&data);
526 assert_eq!(result[0], 0);
527 assert_eq!(result[1], 1);
528 assert_eq!(result[n as usize - 1], n * (n - 1) / 2);
529 }
530
531 #[test]
532 fn test_segmented_prefix_sum() {
533 let data = vec![1, 2, 3, 1, 2, 3];
534 let flags = vec![true, false, false, true, false, false];
535 let result = segmented_prefix_sum(&data, &flags);
536 assert_eq!(result, vec![1, 3, 6, 1, 3, 6]);
537 }
538
539 #[test]
540 fn test_segmented_prefix_sum_single_segment() {
541 let data = vec![1, 2, 3, 4];
542 let flags = vec![true, false, false, false];
543 let result = segmented_prefix_sum(&data, &flags);
544 assert_eq!(result, vec![1, 3, 6, 10]);
545 }
546
547 #[test]
548 fn test_segmented_prefix_sum_all_segments() {
549 let data = vec![10, 20, 30];
550 let flags = vec![true, true, true];
551 let result = segmented_prefix_sum(&data, &flags);
552 assert_eq!(result, vec![10, 20, 30]);
553 }
554
555 #[test]
556 fn test_f64_prefix_sum() {
557 let data = vec![1.0, 2.0, 3.0, 4.0];
558 let result = parallel_prefix_sum_f64(&data);
559 assert!((result[0] - 1.0).abs() < 1e-10);
560 assert!((result[1] - 3.0).abs() < 1e-10);
561 assert!((result[2] - 6.0).abs() < 1e-10);
562 assert!((result[3] - 10.0).abs() < 1e-10);
563 }
564
565 #[test]
566 fn test_try_parallel_scan_empty_error() {
567 let data: Vec<i32> = Vec::new();
568 let result = try_parallel_scan(&data, 0, |a, b| a + b);
569 assert!(result.is_err());
570 }
571
572 #[test]
573 fn test_try_parallel_prefix_sum() {
574 let data = vec![1, 2, 3];
575 let result = try_parallel_prefix_sum(&data).expect("should succeed");
576 assert_eq!(result, vec![1, 3, 6]);
577 }
578
579 #[test]
580 fn test_consistency_inclusive_vs_exclusive() {
581 let data: Vec<i32> = (1..=100).collect();
582 let inclusive = parallel_prefix_sum(&data);
583 let exclusive = parallel_prefix_sum_exclusive(&data);
584
585 for i in 0..data.len() {
587 assert_eq!(exclusive[i] + data[i], inclusive[i]);
588 }
589 }
590
591 #[test]
592 fn test_string_concat_scan() {
593 let data = vec!["a".to_string(), "b".to_string(), "c".to_string()];
594 let result = parallel_scan(&data, String::new(), |mut a, b| {
595 a.push_str(&b);
596 a
597 });
598 assert_eq!(result, vec!["a", "ab", "abc"]);
599 }
600
601 #[test]
602 fn test_large_parallel_correctness() {
603 let n = 10_000;
605 let data: Vec<i64> = (0..n).collect();
606 let par_result = parallel_prefix_sum(&data);
607 let seq_result = sequential_inclusive_scan(&data, &0i64, &|a, b| a + b);
608 assert_eq!(par_result, seq_result);
609 }
610}