#![cfg(feature = "par")]
use alloc::sync::Arc;
use alloc::vec;
use alloc::vec::Vec;
pub mod backend;
#[cfg(feature = "gpu-wgpu")]
pub mod codegen;
#[cfg(feature = "gpu-wgpu")]
pub mod opt;
pub mod simd;
mod nodes;
use nodes::{
NodusChain, NodusEnumerate, NodusFilter, NodusFilterMap, NodusInit, NodusInspect, NodusMap,
NodusScan, NodusSkip, NodusTake, NodusZip,
};
pub mod fast;
pub use fast::FlumenParallelumFast;
#[cfg(feature = "gpu-wgpu")]
mod gpu;
#[cfg(feature = "gpu-wgpu")]
pub use gpu::GpuMapChain;
pub type ParFlumen<T> = FlumenParallelum<T>;
#[derive(Clone)]
pub struct FlumenParallelum<T> {
node: Arc<dyn Nodus<Item = T>>,
}
impl<T> FlumenParallelum<T>
where
T: Clone + Send + Sync + 'static,
{
#[doc(hidden)]
#[inline(always)]
pub fn __from_node(node: Arc<dyn Nodus<Item = T>>) -> Self {
Self { node }
}
#[inline(always)]
pub fn from_vec(vec: Vec<T>) -> Self {
Self {
node: Arc::new(NodusInit { data: vec }),
}
}
#[inline(always)]
pub fn from_slice(slice: &[T]) -> Self {
Self::from_vec(slice.to_vec())
}
#[inline(always)]
pub fn empty() -> Self {
Self::from_vec(Vec::new())
}
#[inline(always)]
pub fn singleton(value: T) -> Self {
Self::from_vec(vec![value])
}
#[inline(always)]
pub fn len(&self) -> usize {
self.node.len()
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline(always)]
pub fn map<U, F>(self, f: F) -> FlumenParallelum<U>
where
U: Clone + Send + Sync + 'static,
F: Fn(T) -> U + Send + Sync + 'static,
{
FlumenParallelum {
node: Arc::new(NodusMap {
prev: self.node,
f: Arc::new(f),
}),
}
}
#[inline(always)]
pub fn filter<F>(self, predicate: F) -> FlumenParallelum<T>
where
F: Fn(&T) -> bool + Send + Sync + 'static,
{
FlumenParallelum {
node: Arc::new(NodusFilter {
prev: self.node,
predicate: Arc::new(predicate),
}),
}
}
#[inline(always)]
pub fn filter_map<U, F>(self, f: F) -> FlumenParallelum<U>
where
U: Clone + Send + Sync + 'static,
F: Fn(T) -> Option<U> + Send + Sync + 'static,
{
FlumenParallelum {
node: Arc::new(NodusFilterMap {
prev: self.node,
f: Arc::new(f),
}),
}
}
#[inline(always)]
pub fn scan<B, F>(self, init: B, f: F) -> FlumenParallelum<B>
where
B: Clone + Send + Sync + 'static,
F: Fn(B, T) -> B + Send + Sync + 'static,
{
FlumenParallelum {
node: Arc::new(NodusScan {
prev: self.node,
init,
f: Arc::new(f),
}),
}
}
#[inline(always)]
pub fn take(self, n: usize) -> FlumenParallelum<T> {
FlumenParallelum {
node: Arc::new(NodusTake {
prev: self.node,
count: n,
}),
}
}
#[inline(always)]
pub fn skip(self, n: usize) -> FlumenParallelum<T> {
FlumenParallelum {
node: Arc::new(NodusSkip {
prev: self.node,
count: n,
}),
}
}
#[inline(always)]
pub fn enumerate(self) -> FlumenParallelum<(usize, T)> {
FlumenParallelum {
node: Arc::new(NodusEnumerate { prev: self.node }),
}
}
#[inline(always)]
pub fn inspect<F>(self, f: F) -> FlumenParallelum<T>
where
F: Fn(&T) + Send + Sync + 'static,
{
FlumenParallelum {
node: Arc::new(NodusInspect {
prev: self.node,
f: Arc::new(f),
}),
}
}
#[inline(always)]
pub fn chain(self, other: FlumenParallelum<T>) -> FlumenParallelum<T> {
FlumenParallelum {
node: Arc::new(NodusChain {
first: self.node,
second: other.node,
}),
}
}
#[inline(always)]
pub fn zip<U>(self, other: FlumenParallelum<U>) -> FlumenParallelum<(T, U)>
where
U: Clone + Send + Sync + 'static,
{
FlumenParallelum {
node: Arc::new(NodusZip {
first: self.node,
second: other.node,
}),
}
}
#[inline(always)]
pub fn collect_vec<Bk>(&self, backend: &Bk) -> Vec<T>
where
Bk: backend::Backend,
{
backend.collect(&*self.node)
}
#[inline(always)]
pub fn reduce<Bk, F>(&self, backend: &Bk, f: F) -> Option<T>
where
Bk: backend::Backend,
F: Fn(T, T) -> T + Send + Sync,
{
backend.reduce(&*self.node, f)
}
#[inline(always)]
pub fn reduce_gpu<Bk, F>(&self, backend: &Bk, wgsl_op: &str, fallback: F) -> Option<T>
where
Bk: backend::Backend,
F: Fn(T, T) -> T + Send + Sync,
{
backend.reduce_gpu(&*self.node, wgsl_op, fallback)
}
#[inline(always)]
pub fn fold<Bk, B, F>(&self, backend: &Bk, init: B, f: F) -> B
where
Bk: backend::Backend,
B: Clone + Send + Sync + 'static,
F: Fn(B, T) -> B + Send + Sync,
{
backend.fold(&*self.node, init, f)
}
#[inline(always)]
pub fn for_each<Bk, F>(&self, backend: &Bk, f: F)
where
Bk: backend::Backend,
F: Fn(T) + Send + Sync,
{
backend.for_each(&*self.node, f);
}
#[inline(always)]
pub fn any<Bk, F>(&self, backend: &Bk, predicate: F) -> bool
where
Bk: backend::Backend,
F: Fn(&T) -> bool + Send + Sync,
{
backend.any(&*self.node, predicate)
}
#[inline(always)]
pub fn all<Bk, F>(&self, backend: &Bk, predicate: F) -> bool
where
Bk: backend::Backend,
F: Fn(&T) -> bool + Send + Sync,
{
backend.all(&*self.node, predicate)
}
#[inline(always)]
pub fn find<Bk, F>(&self, backend: &Bk, predicate: F) -> Option<T>
where
Bk: backend::Backend,
F: Fn(&T) -> bool + Send + Sync,
{
backend.find(&*self.node, predicate)
}
#[inline(always)]
pub fn count<Bk>(&self, backend: &Bk) -> usize
where
Bk: backend::Backend,
{
backend.count(&*self.node)
}
#[inline(always)]
pub fn sum<Bk>(&self, backend: &Bk) -> T
where
Bk: backend::Backend,
T: core::ops::Add<Output = T> + Default,
{
self.fold(backend, T::default(), |acc, x| acc + x)
}
#[inline(always)]
pub fn product<Bk>(&self, backend: &Bk) -> T
where
Bk: backend::Backend,
T: core::ops::Mul<Output = T> + From<u8>,
{
self.fold(backend, T::from(1u8), |acc, x| acc * x)
}
#[inline]
pub fn into_vec_scalar(mut self) -> Vec<T> {
if let Some(inner) = Arc::get_mut(&mut self.node)
&& let Some(v) = inner.try_drain_vec()
{
return v;
}
self.node.collect_scalar()
}
}
impl FlumenParallelum<f32> {
#[inline]
pub fn simd_sum(&self, backend: &backend::CpuSimd) -> f32 {
backend.sum_stream_f32(&*self.node)
}
#[inline]
pub fn simd_min(&self, backend: &backend::CpuSimd) -> Option<f32> {
backend.min_stream_f32(&*self.node)
}
#[inline]
pub fn simd_max(&self, backend: &backend::CpuSimd) -> Option<f32> {
backend.max_stream_f32(&*self.node)
}
#[inline]
pub fn simd_scale(&self, backend: &backend::CpuSimd, factor: f32) -> alloc::vec::Vec<f32> {
backend.scale_stream_f32(&*self.node, factor)
}
}
#[cfg(feature = "gpu-wgpu")]
impl<T: backend::wgpu::GpuScalar> FlumenParallelum<T> {
#[inline(always)]
pub fn from_vec_gpu(vec: Vec<T>) -> Self {
Self {
node: Arc::new(gpu::NodusInitGpu { data: vec }),
}
}
#[inline(always)]
pub fn map_gpu<F>(self, wgsl_expr: &str, fallback: F) -> FlumenParallelum<T>
where
F: Fn(T) -> T + Send + Sync + 'static,
{
FlumenParallelum {
node: Arc::new(gpu::NodusGpuMap {
prev: self.node,
wgsl_expr: alloc::string::String::from(wgsl_expr),
fallback: Arc::new(fallback),
}),
}
}
pub fn is_gpu_capable(&self) -> bool {
self.node.try_gpu_map_chain().is_some()
}
pub fn gpu_chain(&self) -> Option<GpuMapChain> {
self.node.try_gpu_map_chain()
}
pub fn collect_gpu(&self, backend: &backend::wgpu::GpuWgpu) -> Vec<T> {
use crate::par::backend::Backend;
backend.collect(&*self.node)
}
}
#[doc(hidden)]
pub trait Nodus: Send + Sync {
type Item: Clone + Send + Sync + 'static;
fn len(&self) -> usize;
#[inline]
fn is_empty(&self) -> bool {
self.len() == 0
}
fn visit_scalar(&self, sink: &mut dyn FnMut(Self::Item));
#[inline]
fn visit_scalar_ref(&self, sink: &mut dyn FnMut(&Self::Item)) {
self.visit_scalar(&mut |x| sink(&x));
}
#[inline]
fn try_visit_scalar_ref(&self, f: &mut dyn FnMut(&Self::Item) -> core::ops::ControlFlow<()>) {
self.visit_scalar_ref(&mut |x| {
let _ = f(x);
});
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let n = self.len();
(n, Some(n))
}
#[inline]
fn collect_scalar(&self) -> Vec<Self::Item> {
let (lo, _) = self.size_hint();
let mut out = Vec::with_capacity(lo);
self.visit_scalar_ref(&mut |x| out.push(x.clone()));
out
}
#[inline(always)]
fn is_indexed(&self) -> bool {
false
}
#[inline(always)]
fn get(&self, _index: usize) -> Self::Item {
crate::cold_panic!("Nodus::get called on a non-indexed node")
}
#[cfg(feature = "gpu-wgpu")]
fn try_gpu_map_chain(&self) -> Option<GpuMapChain> {
None }
#[cfg(feature = "gpu-wgpu")]
fn try_as_gpu_source(&self) -> Option<(&[u8], &'static str)> {
None
}
#[cfg(feature = "rayon")]
#[inline]
fn collect_rayon(&self) -> Vec<Self::Item> {
use rayon::prelude::*;
if self.is_indexed() {
(0..self.len())
.into_par_iter()
.map(|i| self.get(i))
.collect()
} else {
self.collect_scalar()
}
}
#[cfg(feature = "rayon")]
#[inline]
fn reduce_rayon(
&self,
f: &(dyn Fn(Self::Item, Self::Item) -> Self::Item + Send + Sync),
) -> Option<Self::Item> {
use rayon::prelude::*;
if self.is_indexed() {
(0..self.len())
.into_par_iter()
.map(|i| self.get(i))
.reduce_with(f)
} else {
self.collect_rayon().into_par_iter().reduce_with(f)
}
}
#[cfg(feature = "rayon")]
#[inline]
fn for_each_rayon(&self, f: &(dyn Fn(Self::Item) + Send + Sync)) {
use rayon::prelude::*;
if self.is_indexed() {
(0..self.len()).into_par_iter().for_each(|i| f(self.get(i)));
} else {
self.collect_rayon().into_par_iter().for_each(f);
}
}
#[inline]
fn try_drain_vec(&mut self) -> Option<Vec<Self::Item>> {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use backend::CpuScalar;
#[test]
fn test_par_flumen_map() {
let data = vec![1, 2, 3, 4, 5];
let result = ParFlumen::from_vec(data)
.map(|x| x * 2)
.collect_vec(&CpuScalar);
assert_eq!(result, vec![2, 4, 6, 8, 10]);
}
#[test]
fn test_par_flumen_filter() {
let data = vec![1, 2, 3, 4, 5, 6];
let result = ParFlumen::from_vec(data)
.filter(|x| x % 2 == 0)
.collect_vec(&CpuScalar);
assert_eq!(result, vec![2, 4, 6]);
}
#[cfg(feature = "rayon")]
#[test]
fn test_par_reduce_rayon_fused_matches_scalar() {
use backend::CpuRayon;
let n = 10_000i64;
let build = || {
ParFlumen::from_vec((0..n).collect::<Vec<_>>())
.filter(|x| x % 2 == 0)
.map(|x| x * 3)
};
let expected: i64 = (0..n).filter(|x| x % 2 == 0).map(|x| x * 3).sum();
let scalar = build().reduce(&CpuScalar, |a, b| a + b);
let parallel = build().reduce(&CpuRayon { min_len: 1 }, |a, b| a + b);
assert_eq!(scalar, Some(expected));
assert_eq!(parallel, scalar, "fused parallel reduce must match scalar");
}
#[cfg(feature = "rayon")]
#[test]
fn test_par_for_each_rayon_fused_matches_scalar() {
use backend::CpuRayon;
use core::sync::atomic::{AtomicI64, Ordering};
let n = 10_000i64;
let build = || {
ParFlumen::from_vec((0..n).collect::<Vec<_>>())
.filter(|x| x % 2 == 0)
.map(|x| x * 3)
};
let expected: i64 = (0..n).filter(|x| x % 2 == 0).map(|x| x * 3).sum();
let scalar = AtomicI64::new(0);
build().for_each(&CpuScalar, |x| {
scalar.fetch_add(x, Ordering::Relaxed);
});
let parallel = AtomicI64::new(0);
build().for_each(&CpuRayon { min_len: 1 }, |x| {
parallel.fetch_add(x, Ordering::Relaxed);
});
assert_eq!(scalar.load(Ordering::Relaxed), expected);
assert_eq!(
parallel.load(Ordering::Relaxed),
expected,
"fused parallel for_each must visit every element exactly once"
);
}
#[cfg(feature = "rayon")]
#[test]
fn test_par_filter_reduce_for_each_rayon_fused_matches_scalar() {
use backend::CpuRayon;
use core::sync::atomic::{AtomicI64, Ordering};
let n = 10_000i64;
let build = || ParFlumen::from_vec((0..n).collect::<Vec<_>>()).filter(|x| x % 3 == 0);
let expected: i64 = (0..n).filter(|x| x % 3 == 0).sum();
assert_eq!(build().reduce(&CpuScalar, |a, b| a + b), Some(expected));
assert_eq!(
build().reduce(&CpuRayon { min_len: 1 }, |a, b| a + b),
Some(expected),
"fused filter reduce must match scalar"
);
let acc = AtomicI64::new(0);
build().for_each(&CpuRayon { min_len: 1 }, |x| {
acc.fetch_add(x, Ordering::Relaxed);
});
assert_eq!(acc.load(Ordering::Relaxed), expected);
}
#[cfg(feature = "rayon")]
#[test]
fn test_par_filter_map_reduce_for_each_rayon_fused_matches_scalar() {
use backend::CpuRayon;
use core::sync::atomic::{AtomicI64, Ordering};
let n = 10_000i64;
let build = || {
ParFlumen::from_vec((0..n).collect::<Vec<_>>())
.filter_map(|x| if x % 2 == 0 { Some(x * 2) } else { None })
};
let expected: i64 = (0..n).filter(|x| x % 2 == 0).map(|x| x * 2).sum();
assert_eq!(build().reduce(&CpuScalar, |a, b| a + b), Some(expected));
assert_eq!(
build().reduce(&CpuRayon { min_len: 1 }, |a, b| a + b),
Some(expected),
"fused filter_map reduce must match scalar"
);
let acc = AtomicI64::new(0);
build().for_each(&CpuRayon { min_len: 1 }, |x| {
acc.fetch_add(x, Ordering::Relaxed);
});
assert_eq!(acc.load(Ordering::Relaxed), expected);
}
#[cfg(feature = "rayon")]
#[test]
fn test_par_chain_reduce_for_each_rayon_fused_matches_scalar() {
use backend::CpuRayon;
use core::sync::atomic::{AtomicI64, Ordering};
let force = CpuRayon { min_len: 1 };
let build = || {
ParFlumen::from_vec((0..5_000i64).collect::<Vec<_>>())
.filter(|x| x % 2 == 0)
.chain(
ParFlumen::from_vec((5_000..10_000i64).collect::<Vec<_>>())
.filter(|x| x % 3 == 0),
)
};
let expected: i64 = (0..5_000).filter(|x| x % 2 == 0).sum::<i64>()
+ (5_000..10_000).filter(|x| x % 3 == 0).sum::<i64>();
assert_eq!(build().reduce(&CpuScalar, |a, b| a + b), Some(expected));
assert_eq!(
build().reduce(&force, |a, b| a + b),
Some(expected),
"fused chain reduce must match scalar"
);
let acc = AtomicI64::new(0);
build().for_each(&force, |x| {
acc.fetch_add(x, Ordering::Relaxed);
});
assert_eq!(acc.load(Ordering::Relaxed), expected);
let empty_first = ParFlumen::from_vec((0..100i64).collect::<Vec<_>>())
.filter(|_| false)
.chain(ParFlumen::from_vec(alloc::vec![1i64, 2, 3]));
assert_eq!(empty_first.reduce(&force, |a, b| a + b), Some(6));
}
#[cfg(feature = "rayon")]
fn assert_fused_par_matches_scalar<F>(label: &str, build: F)
where
F: Fn() -> ParFlumen<i64>,
{
use backend::CpuRayon;
use core::sync::atomic::{AtomicI64, Ordering};
let force = CpuRayon { min_len: 1 };
let scalar_reduce = build().reduce(&CpuScalar, |a, b| a + b);
let par_reduce = build().reduce(&force, |a, b| a + b);
assert_eq!(
par_reduce, scalar_reduce,
"{label}: reduce parallel != scalar"
);
let s_sum = AtomicI64::new(0);
let s_cnt = AtomicI64::new(0);
build().for_each(&CpuScalar, |x| {
s_sum.fetch_add(x, Ordering::Relaxed);
s_cnt.fetch_add(1, Ordering::Relaxed);
});
let p_sum = AtomicI64::new(0);
let p_cnt = AtomicI64::new(0);
build().for_each(&force, |x| {
p_sum.fetch_add(x, Ordering::Relaxed);
p_cnt.fetch_add(1, Ordering::Relaxed);
});
assert_eq!(
p_sum.load(Ordering::Relaxed),
s_sum.load(Ordering::Relaxed),
"{label}: for_each sum parallel != scalar"
);
assert_eq!(
p_cnt.load(Ordering::Relaxed),
s_cnt.load(Ordering::Relaxed),
"{label}: for_each count parallel != scalar"
);
assert_eq!(
build().collect_vec(&force),
build().collect_vec(&CpuScalar),
"{label}: collect parallel != scalar"
);
}
#[cfg(feature = "rayon")]
#[test]
fn test_par_fused_terminals_all_paths_match_scalar() {
let n = 2_000i64;
assert_fused_par_matches_scalar("map[indexed]", || {
ParFlumen::from_vec((0..n).collect::<Vec<_>>()).map(|x| x * 2)
});
assert_fused_par_matches_scalar("filter.map[fallback]", || {
ParFlumen::from_vec((0..n).collect::<Vec<_>>())
.filter(|x| x % 2 == 0)
.map(|x| x * 2)
});
assert_fused_par_matches_scalar("filter[indexed]", || {
ParFlumen::from_vec((0..n).collect::<Vec<_>>()).filter(|x| x % 2 == 0)
});
assert_fused_par_matches_scalar("filter.filter[fallback]", || {
ParFlumen::from_vec((0..n).collect::<Vec<_>>())
.filter(|x| x % 2 == 0)
.filter(|x| x % 4 == 0)
});
assert_fused_par_matches_scalar("filter_map[indexed]", || {
ParFlumen::from_vec((0..n).collect::<Vec<_>>())
.filter_map(|x| if x % 2 == 0 { Some(x * 3) } else { None })
});
assert_fused_par_matches_scalar("filter.filter_map[fallback]", || {
ParFlumen::from_vec((0..n).collect::<Vec<_>>())
.filter(|x| x % 2 == 0)
.filter_map(|x| if x % 4 == 0 { Some(x * 3) } else { None })
});
assert_fused_par_matches_scalar("chain[indexed,indexed]", || {
ParFlumen::from_vec((0..n).collect::<Vec<_>>())
.chain(ParFlumen::from_vec((n..2 * n).collect::<Vec<_>>()))
});
assert_fused_par_matches_scalar("filter.chain(filter)[fallback]", || {
ParFlumen::from_vec((0..n).collect::<Vec<_>>())
.filter(|x| x % 2 == 0)
.chain(ParFlumen::from_vec((n..2 * n).collect::<Vec<_>>()).filter(|x| x % 3 == 0))
});
}
#[cfg(feature = "rayon")]
#[test]
fn test_par_fused_terminals_edge_cases_match_scalar() {
for n in [0i64, 1, 2, 3] {
assert_fused_par_matches_scalar("filter.map[edge]", move || {
ParFlumen::from_vec((0..n).collect::<Vec<_>>())
.filter(|x| x % 2 == 0)
.map(|x| x + 1)
});
assert_fused_par_matches_scalar("filter.filter[edge]", move || {
ParFlumen::from_vec((0..n).collect::<Vec<_>>())
.filter(|x| x % 2 == 0)
.filter(|x| *x >= 0)
});
assert_fused_par_matches_scalar("filter.filter_map[edge]", move || {
ParFlumen::from_vec((0..n).collect::<Vec<_>>())
.filter(|x| x % 2 == 0)
.filter_map(Some)
});
assert_fused_par_matches_scalar("filter.chain(empty)[edge]", move || {
ParFlumen::from_vec((0..n).collect::<Vec<_>>())
.filter(|x| x % 2 == 0)
.chain(ParFlumen::from_vec((0..n).collect::<Vec<_>>()).filter(|_| false))
});
}
assert_fused_par_matches_scalar("all-filtered", || {
ParFlumen::from_vec((0..1000i64).collect::<Vec<_>>()).filter(|_| false)
});
assert_fused_par_matches_scalar("single-survivor[fallback]", || {
ParFlumen::from_vec((0..1000i64).collect::<Vec<_>>())
.filter(|x| x % 2 == 0)
.filter(|x| *x == 500)
});
}
#[test]
fn test_par_flumen_filter_map() {
let data = vec![1, 2, 3, 4, 5];
let result = ParFlumen::from_vec(data)
.filter_map(|x| if x % 2 == 0 { Some(x * 10) } else { None })
.collect_vec(&CpuScalar);
assert_eq!(result, vec![20, 40]);
}
#[test]
fn test_par_flumen_take() {
let data = vec![1, 2, 3, 4, 5];
let result = ParFlumen::from_vec(data).take(3).collect_vec(&CpuScalar);
assert_eq!(result, vec![1, 2, 3]);
}
#[test]
fn test_par_flumen_skip() {
let data = vec![1, 2, 3, 4, 5];
let result = ParFlumen::from_vec(data).skip(2).collect_vec(&CpuScalar);
assert_eq!(result, vec![3, 4, 5]);
}
#[test]
fn test_par_flumen_enumerate() {
let data = vec!["a", "b", "c"];
let result = ParFlumen::from_vec(data)
.enumerate()
.collect_vec(&CpuScalar);
assert_eq!(result, vec![(0, "a"), (1, "b"), (2, "c")]);
}
#[test]
fn test_par_flumen_chain() {
let a = ParFlumen::from_vec(vec![1, 2]);
let b = ParFlumen::from_vec(vec![3, 4, 5]);
let result = a.chain(b).collect_vec(&CpuScalar);
assert_eq!(result, vec![1, 2, 3, 4, 5]);
}
#[test]
fn test_par_flumen_zip() {
let a = ParFlumen::from_vec(vec![1, 2, 3]);
let b = ParFlumen::from_vec(vec!["a", "b", "c"]);
let result = a.zip(b).collect_vec(&CpuScalar);
assert_eq!(result, vec![(1, "a"), (2, "b"), (3, "c")]);
}
#[test]
fn test_par_flumen_reduce() {
let data = vec![1, 2, 3, 4, 5];
let result = ParFlumen::from_vec(data).reduce(&CpuScalar, |a, b| a + b);
assert_eq!(result, Some(15));
}
#[test]
fn test_par_flumen_fold() {
let data = vec![1, 2, 3, 4, 5];
let result = ParFlumen::from_vec(data).fold(&CpuScalar, 0, |acc, x| acc + x);
assert_eq!(result, 15);
}
#[test]
fn test_par_flumen_scan() {
let data = vec![1, 2, 3, 4];
let result = ParFlumen::from_vec(data)
.scan(0, |acc, x| acc + x)
.collect_vec(&CpuScalar);
assert_eq!(result, vec![1, 3, 6, 10]);
}
#[test]
fn test_par_flumen_any() {
let data = vec![1, 2, 3, 4, 5];
assert!(ParFlumen::from_vec(data.clone()).any(&CpuScalar, |x| *x > 3));
assert!(!ParFlumen::from_vec(data).any(&CpuScalar, |x| *x > 10));
}
#[test]
fn test_par_flumen_all() {
let data = vec![2, 4, 6, 8];
assert!(ParFlumen::from_vec(data.clone()).all(&CpuScalar, |x| x % 2 == 0));
assert!(!ParFlumen::from_vec(data).all(&CpuScalar, |x| *x > 5));
}
#[test]
fn test_par_flumen_find() {
let data = vec![1, 2, 3, 4, 5];
let result = ParFlumen::from_vec(data).find(&CpuScalar, |x| *x > 3);
assert_eq!(result, Some(4));
}
#[test]
fn test_par_flumen_count() {
let data = vec![1, 2, 3, 4, 5];
assert_eq!(ParFlumen::from_vec(data).count(&CpuScalar), 5);
}
#[test]
fn test_par_flumen_sum() {
let data = vec![1, 2, 3, 4, 5];
assert_eq!(ParFlumen::from_vec(data).sum(&CpuScalar), 15);
}
#[test]
fn test_par_flumen_empty() {
let empty: ParFlumen<i32> = ParFlumen::empty();
assert!(empty.is_empty());
assert_eq!(empty.len(), 0);
}
#[test]
fn test_par_flumen_singleton() {
let single = ParFlumen::singleton(42);
assert_eq!(single.len(), 1);
assert_eq!(single.collect_vec(&CpuScalar), vec![42]);
}
#[test]
fn test_par_flumen_pipeline() {
let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let result = ParFlumen::from_vec(data)
.map(|x| x * 2) .filter(|x| *x > 6) .take(4) .enumerate() .collect_vec(&CpuScalar);
assert_eq!(result, vec![(0, 8), (1, 10), (2, 12), (3, 14)]);
}
}