polars_compute/
float_sum.rs1use std::ops::{Add, IndexMut};
2#[cfg(feature = "simd")]
3use std::simd::{prelude::*, *};
4
5use arrow::array::{Array, PrimitiveArray};
6use arrow::bitmap::Bitmap;
7use arrow::bitmap::bitmask::BitMask;
8use arrow::types::NativeType;
9use num_traits::{AsPrimitive, Float};
10#[cfg(feature = "simd")]
11use polars_utils::float16::pf16;
12
13const STRIPE: usize = 16;
14const PAIRWISE_RECURSION_LIMIT: usize = 128;
15
16#[cfg(feature = "simd")]
18pub trait SimdCastGeneric<const N: usize> {
19 fn cast_generic<U: SimdCast>(self) -> Simd<U, N>;
20}
21
22macro_rules! impl_cast_custom {
23 ($_type:ty) => {
24 #[cfg(feature = "simd")]
25 impl<const N: usize> SimdCastGeneric<N> for Simd<$_type, N> {
26 fn cast_generic<U: SimdCast>(self) -> Simd<U, N> {
27 self.cast::<U>()
28 }
29 }
30 };
31}
32
33impl_cast_custom!(u8);
34impl_cast_custom!(u16);
35impl_cast_custom!(u32);
36impl_cast_custom!(u64);
37impl_cast_custom!(i8);
38impl_cast_custom!(i16);
39impl_cast_custom!(i32);
40impl_cast_custom!(i64);
41impl_cast_custom!(f32);
42impl_cast_custom!(f64);
43
44fn vector_horizontal_sum<V, T>(mut v: V) -> T
45where
46 V: IndexMut<usize, Output = T>,
47 T: Add<T, Output = T> + Sized + Copy,
48{
49 let mut width = STRIPE;
55 while width > 4 {
56 for j in 0..width / 2 {
57 v[j] = v[j] + v[width / 2 + j];
58 }
59 width /= 2;
60 }
61
62 (v[0] + v[2]) + (v[1] + v[3])
63}
64
65pub trait SumBlock<F> {
67 fn sum_block_vectorized(&self) -> F;
68 fn sum_block_vectorized_with_mask(&self, mask: BitMask<'_>) -> F;
69}
70
71#[cfg(feature = "simd")]
72impl<T, F> SumBlock<F> for [T; PAIRWISE_RECURSION_LIMIT]
73where
74 T: SimdElement,
75 F: SimdElement + SimdCast + Add<Output = F> + Default,
76 Simd<T, STRIPE>: SimdCastGeneric<STRIPE>,
77 Simd<F, STRIPE>: std::iter::Sum,
78{
79 fn sum_block_vectorized(&self) -> F {
80 let vsum = self
81 .chunks_exact(STRIPE)
82 .map(|a| Simd::<T, STRIPE>::from_slice(a).cast_generic::<F>())
83 .sum::<Simd<F, STRIPE>>();
84 vector_horizontal_sum(vsum)
85 }
86
87 fn sum_block_vectorized_with_mask(&self, mask: BitMask<'_>) -> F {
88 let zero = Simd::default();
89 let vsum = self
90 .chunks_exact(STRIPE)
91 .enumerate()
92 .map(|(i, a)| {
93 let m: Mask<T::Mask, STRIPE> = mask.get_simd(i * STRIPE);
94 m.select(Simd::from_slice(a).cast_generic::<F>(), zero)
95 })
96 .sum::<Simd<F, STRIPE>>();
97 vector_horizontal_sum(vsum)
98 }
99}
100
101#[cfg(feature = "simd")]
102impl<F> SumBlock<F> for [i128; PAIRWISE_RECURSION_LIMIT]
103where
104 i128: AsPrimitive<F>,
105 F: Float + std::iter::Sum + 'static,
106{
107 fn sum_block_vectorized(&self) -> F {
108 self.iter().map(|x| x.as_()).sum()
109 }
110
111 fn sum_block_vectorized_with_mask(&self, mask: BitMask<'_>) -> F {
112 self.iter()
113 .enumerate()
114 .map(|(idx, x)| if mask.get(idx) { x.as_() } else { F::zero() })
115 .sum()
116 }
117}
118
119#[cfg(feature = "simd")]
120impl<F> SumBlock<F> for [u128; PAIRWISE_RECURSION_LIMIT]
121where
122 u128: AsPrimitive<F>,
123 F: Float + std::iter::Sum + 'static,
124{
125 fn sum_block_vectorized(&self) -> F {
126 self.iter().map(|x| x.as_()).sum()
127 }
128
129 fn sum_block_vectorized_with_mask(&self, mask: BitMask<'_>) -> F {
130 self.iter()
131 .enumerate()
132 .map(|(idx, x)| if mask.get(idx) { x.as_() } else { F::zero() })
133 .sum()
134 }
135}
136
137#[cfg(feature = "simd")]
138impl<F> SumBlock<F> for [pf16; PAIRWISE_RECURSION_LIMIT]
139where
140 pf16: AsPrimitive<F>,
141 F: Float + std::iter::Sum + 'static,
142{
143 fn sum_block_vectorized(&self) -> F {
144 self.iter().map(|x| x.as_()).sum()
145 }
146
147 fn sum_block_vectorized_with_mask(&self, mask: BitMask<'_>) -> F {
148 self.iter()
149 .enumerate()
150 .map(|(idx, x)| if mask.get(idx) { x.as_() } else { F::zero() })
151 .sum()
152 }
153}
154
155#[cfg(not(feature = "simd"))]
156impl<T, F> SumBlock<F> for [T; PAIRWISE_RECURSION_LIMIT]
157where
158 T: AsPrimitive<F> + 'static,
159 F: Default + Add<Output = F> + Copy + 'static,
160{
161 fn sum_block_vectorized(&self) -> F {
162 let mut vsum = [F::default(); STRIPE];
163 for chunk in self.chunks_exact(STRIPE) {
164 for j in 0..STRIPE {
165 vsum[j] = vsum[j] + chunk[j].as_();
166 }
167 }
168 vector_horizontal_sum(vsum)
169 }
170
171 fn sum_block_vectorized_with_mask(&self, mask: BitMask<'_>) -> F {
172 let mut vsum = [F::default(); STRIPE];
173 for (i, chunk) in self.chunks_exact(STRIPE).enumerate() {
174 for j in 0..STRIPE {
175 let addend = if mask.get(i * STRIPE + j) {
177 chunk[j].as_()
178 } else {
179 F::default()
180 };
181 vsum[j] = vsum[j] + addend;
182 }
183 }
184 vector_horizontal_sum(vsum)
185 }
186}
187
188unsafe fn pairwise_sum<F, T>(f: &[T]) -> F
190where
191 [T; PAIRWISE_RECURSION_LIMIT]: SumBlock<F>,
192 F: Add<Output = F>,
193{
194 debug_assert!(!f.is_empty() && f.len().is_multiple_of(PAIRWISE_RECURSION_LIMIT));
195
196 let block: Option<&[T; PAIRWISE_RECURSION_LIMIT]> = f.try_into().ok();
197 if let Some(block) = block {
198 return block.sum_block_vectorized();
199 }
200
201 unsafe {
206 let blocks = f.len() / PAIRWISE_RECURSION_LIMIT;
207 let left_len = (blocks / 2) * PAIRWISE_RECURSION_LIMIT;
208 let (left, right) = (f.get_unchecked(..left_len), f.get_unchecked(left_len..));
209 pairwise_sum(left) + pairwise_sum(right)
210 }
211}
212
213unsafe fn pairwise_sum_with_mask<F, T>(f: &[T], mask: BitMask<'_>) -> F
216where
217 [T; PAIRWISE_RECURSION_LIMIT]: SumBlock<F>,
218 F: Add<Output = F>,
219{
220 debug_assert!(!f.is_empty() && f.len().is_multiple_of(PAIRWISE_RECURSION_LIMIT));
221 debug_assert!(f.len() == mask.len());
222
223 let block: Option<&[T; PAIRWISE_RECURSION_LIMIT]> = f.try_into().ok();
224 if let Some(block) = block {
225 return block.sum_block_vectorized_with_mask(mask);
226 }
227
228 unsafe {
230 let blocks = f.len() / PAIRWISE_RECURSION_LIMIT;
231 let left_len = (blocks / 2) * PAIRWISE_RECURSION_LIMIT;
232 let (left, right) = (f.get_unchecked(..left_len), f.get_unchecked(left_len..));
233 let (left_mask, right_mask) = mask.split_at_unchecked(left_len);
234 pairwise_sum_with_mask(left, left_mask) + pairwise_sum_with_mask(right, right_mask)
235 }
236}
237
238pub trait FloatSum<F>: Sized {
239 fn sum(f: &[Self]) -> F;
240 fn sum_with_validity(f: &[Self], validity: &Bitmap) -> F;
241}
242
243impl<T, F> FloatSum<F> for T
244where
245 F: Float + std::iter::Sum + 'static,
246 T: AsPrimitive<F>,
247 [T; PAIRWISE_RECURSION_LIMIT]: SumBlock<F>,
248{
249 fn sum(f: &[Self]) -> F {
250 let remainder = f.len() % PAIRWISE_RECURSION_LIMIT;
251 let (rest, main) = f.split_at(remainder);
252 let mainsum = if f.len() > remainder {
253 unsafe { pairwise_sum(main) }
254 } else {
255 F::zero()
256 };
257 let restsum: F = rest.iter().map(|x| x.as_()).sum();
259 mainsum + restsum
260 }
261
262 fn sum_with_validity(f: &[Self], validity: &Bitmap) -> F {
263 let mask = BitMask::from_bitmap(validity);
264 assert!(f.len() == mask.len());
265
266 let remainder = f.len() % PAIRWISE_RECURSION_LIMIT;
267 let (rest, main) = f.split_at(remainder);
268 let (rest_mask, main_mask) = mask.split_at(remainder);
269 let mainsum = if f.len() > remainder {
270 unsafe { pairwise_sum_with_mask(main, main_mask) }
271 } else {
272 F::zero()
273 };
274 let restsum: F = rest
276 .iter()
277 .enumerate()
278 .map(|(i, x)| {
279 if rest_mask.get(i) { x.as_() } else { F::zero() }
281 })
282 .sum();
283 mainsum + restsum
284 }
285}
286
287pub fn sum_arr_as_f32<T>(arr: &PrimitiveArray<T>) -> f32
288where
289 T: NativeType + FloatSum<f32>,
290{
291 let validity = arr.validity().filter(|_| arr.null_count() > 0);
292 if let Some(mask) = validity {
293 FloatSum::sum_with_validity(arr.values(), mask)
294 } else {
295 FloatSum::sum(arr.values())
296 }
297}
298
299pub fn sum_arr_as_f64<T>(arr: &PrimitiveArray<T>) -> f64
300where
301 T: NativeType + FloatSum<f64>,
302{
303 let validity = arr.validity().filter(|_| arr.null_count() > 0);
304 if let Some(mask) = validity {
305 FloatSum::sum_with_validity(arr.values(), mask)
306 } else {
307 FloatSum::sum(arr.values())
308 }
309}