1use polars_error::{PolarsResult, feature_gated};
5
6use super::list_utils::NumericOp;
7use super::{IntoSeries, ListChunked, ListType, NumOpsDispatchInner, Series};
8use crate::prelude::DataType;
9
10impl NumOpsDispatchInner for ListType {
11 fn add_to(lhs: &ListChunked, rhs: &Series) -> PolarsResult<Series> {
12 NumericListOp::add().execute(&lhs.clone().into_series(), rhs)
13 }
14
15 fn subtract(lhs: &ListChunked, rhs: &Series) -> PolarsResult<Series> {
16 NumericListOp::sub().execute(&lhs.clone().into_series(), rhs)
17 }
18
19 fn multiply(lhs: &ListChunked, rhs: &Series) -> PolarsResult<Series> {
20 NumericListOp::mul().execute(&lhs.clone().into_series(), rhs)
21 }
22
23 fn divide(lhs: &ListChunked, rhs: &Series) -> PolarsResult<Series> {
24 NumericListOp::div().execute(&lhs.clone().into_series(), rhs)
25 }
26
27 fn remainder(lhs: &ListChunked, rhs: &Series) -> PolarsResult<Series> {
28 NumericListOp::rem().execute(&lhs.clone().into_series(), rhs)
29 }
30}
31
32#[cfg_attr(not(feature = "list_arithmetic"), allow(unused))]
33#[derive(Clone)]
34pub struct NumericListOp(NumericOp);
35
36impl NumericListOp {
37 pub fn add() -> Self {
38 Self(NumericOp::Add)
39 }
40
41 pub fn sub() -> Self {
42 Self(NumericOp::Sub)
43 }
44
45 pub fn mul() -> Self {
46 Self(NumericOp::Mul)
47 }
48
49 pub fn div() -> Self {
50 Self(NumericOp::Div)
51 }
52
53 pub fn rem() -> Self {
54 Self(NumericOp::Rem)
55 }
56
57 pub fn floor_div() -> Self {
58 Self(NumericOp::FloorDiv)
59 }
60
61 pub fn trunc_div() -> Self {
62 Self(NumericOp::TruncDiv)
63 }
64
65 pub fn try_get_leaf_supertype(
66 &self,
67 prim_dtype_lhs: &DataType,
68 prim_dtype_rhs: &DataType,
69 ) -> PolarsResult<DataType> {
70 self.0
71 .try_get_leaf_supertype(prim_dtype_lhs, prim_dtype_rhs)
72 }
73}
74
75impl NumericListOp {
76 #[cfg_attr(not(feature = "list_arithmetic"), allow(unused))]
77 pub fn execute(&self, lhs: &Series, rhs: &Series) -> PolarsResult<Series> {
78 feature_gated!("list_arithmetic", {
79 use std::borrow::Cow;
80
81 use either::Either;
82
83 let lhs = lhs
87 .trim_lists_to_normalized_offsets()
88 .map_or(Cow::Borrowed(lhs), Cow::Owned);
89 let rhs = rhs
90 .trim_lists_to_normalized_offsets()
91 .map_or(Cow::Borrowed(rhs), Cow::Owned);
92
93 let lhs = lhs.rechunk();
94 let rhs = rhs.rechunk();
95
96 let binary_op_exec = match ListNumericOpHelper::try_new(
97 self.clone(),
98 lhs.name().clone(),
99 lhs.dtype(),
100 rhs.dtype(),
101 lhs.len(),
102 rhs.len(),
103 {
104 let (a, b) = lhs.list_offsets_and_validities_recursive();
105 debug_assert!(a.iter().all(|x| *x.first() as usize == 0));
106 (a, b, lhs.clone())
107 },
108 {
109 let (a, b) = rhs.list_offsets_and_validities_recursive();
110 debug_assert!(a.iter().all(|x| *x.first() as usize == 0));
111 (a, b, rhs.clone())
112 },
113 lhs.rechunk_validity(),
114 rhs.rechunk_validity(),
115 )? {
116 Either::Left(v) => v,
117 Either::Right(ca) => return Ok(ca.into_series()),
118 };
119
120 Ok(binary_op_exec.finish()?.into_series())
121 })
122 }
123}
124
125#[cfg(feature = "list_arithmetic")]
126use inner::ListNumericOpHelper;
127
128#[cfg(feature = "list_arithmetic")]
129mod inner {
130 use either::Either;
131 use list_utils::with_match_pl_num_arith;
132 use num_traits::Zero;
133 use polars_arrow::bitmap::Bitmap;
134 use polars_arrow::compute::utils::combine_validities_and;
135 use polars_arrow::offset::OffsetsBuffer;
136 use polars_compute::arithmetic::pl_num::PlNumArithmetic;
137 use polars_utils::float::IsFloat;
138
139 use super::super::list_utils::{BinaryOpApplyType, Broadcast, NumericOp};
140 use super::super::*;
141
142 pub(super) struct ListNumericOpHelper {
145 op: NumericListOp,
146 output_name: PlSmallStr,
147 op_apply_type: BinaryOpApplyType,
148 broadcast: Broadcast,
149 output_dtype: DataType,
150 output_primitive_dtype: DataType,
151 output_len: usize,
152 outer_validity: Bitmap,
155 data_lhs: (Vec<OffsetsBuffer<i64>>, Vec<Option<Bitmap>>, Series),
157 data_rhs: (Vec<OffsetsBuffer<i64>>, Vec<Option<Bitmap>>, Series),
158 list_to_prim_lhs: Option<(Box<dyn Array>, usize)>,
159 swapped: bool,
160 }
161
162 impl ListNumericOpHelper {
165 #[allow(clippy::too_many_arguments)]
181 pub(super) fn try_new(
182 op: NumericListOp,
183 output_name: PlSmallStr,
184 dtype_lhs: &DataType,
185 dtype_rhs: &DataType,
186 len_lhs: usize,
187 len_rhs: usize,
188 data_lhs: (Vec<OffsetsBuffer<i64>>, Vec<Option<Bitmap>>, Series),
189 data_rhs: (Vec<OffsetsBuffer<i64>>, Vec<Option<Bitmap>>, Series),
190 validity_lhs: Option<Bitmap>,
191 validity_rhs: Option<Bitmap>,
192 ) -> PolarsResult<Either<Self, ListChunked>> {
193 let prim_dtype_lhs = dtype_lhs.leaf_dtype();
194 let prim_dtype_rhs = dtype_rhs.leaf_dtype();
195
196 let output_primitive_dtype =
197 op.0.try_get_leaf_supertype(prim_dtype_lhs, prim_dtype_rhs)?;
198
199 fn is_list_type_at_all_levels(dtype: &DataType) -> bool {
200 match dtype {
201 DataType::List(inner) => is_list_type_at_all_levels(inner),
202 dt if dt.is_supported_list_arithmetic_input() => true,
203 _ => false,
204 }
205 }
206
207 let op_err_msg = |err_reason: &str| {
208 polars_err!(
209 InvalidOperation:
210 "cannot {} columns: {}: (left: {}, right: {})",
211 op.0.name(), err_reason, dtype_lhs, dtype_rhs,
212 )
213 };
214
215 let ensure_list_type_at_all_levels = |dtype: &DataType| {
216 if !is_list_type_at_all_levels(dtype) {
217 Err(op_err_msg("dtype was not list on all nesting levels"))
218 } else {
219 Ok(())
220 }
221 };
222
223 let (op_apply_type, output_dtype) = match (dtype_lhs, dtype_rhs) {
224 (l @ DataType::List(a), r @ DataType::List(b)) => {
225 if ![a, b]
230 .into_iter()
231 .all(|x| x.is_supported_list_arithmetic_input())
232 {
233 polars_bail!(
234 InvalidOperation:
235 "cannot {} two list columns with non-numeric inner types: (left: {}, right: {})",
236 op.0.name(), l, r,
237 );
238 }
239 (BinaryOpApplyType::ListToList, l)
240 },
241 (list_dtype @ DataType::List(_), x) if x.is_supported_list_arithmetic_input() => {
242 ensure_list_type_at_all_levels(list_dtype)?;
243 (BinaryOpApplyType::ListToPrimitive, list_dtype)
244 },
245 (x, list_dtype @ DataType::List(_)) if x.is_supported_list_arithmetic_input() => {
246 ensure_list_type_at_all_levels(list_dtype)?;
247 (BinaryOpApplyType::PrimitiveToList, list_dtype)
248 },
249 (l, r) => polars_bail!(
250 InvalidOperation:
251 "{} operation not supported for dtypes: {} != {}",
252 op.0.name(), l, r,
253 ),
254 };
255
256 let output_dtype = output_dtype.cast_leaf(output_primitive_dtype.clone());
257
258 let (broadcast, output_len) = match (len_lhs, len_rhs) {
259 (l, r) if l == r => (Broadcast::NoBroadcast, l),
260 (1, v) => (Broadcast::Left, v),
261 (v, 1) => (Broadcast::Right, v),
262 (l, r) => polars_bail!(
263 ShapeMismatch:
264 "cannot {} two columns of differing lengths: {} != {}",
265 op.0.name(), l, r
266 ),
267 };
268
269 let DataType::List(output_inner_dtype) = &output_dtype else {
270 unreachable!()
271 };
272
273 if output_len == 0
280 || (matches!(
281 &op_apply_type,
282 BinaryOpApplyType::ListToList | BinaryOpApplyType::ListToPrimitive
283 ) && validity_lhs.as_ref().is_some_and(|x| x.set_bits() == 0))
284 || (matches!(
285 &op_apply_type,
286 BinaryOpApplyType::ListToList | BinaryOpApplyType::PrimitiveToList
287 ) && validity_rhs.as_ref().is_some_and(|x| x.set_bits() == 0))
288 {
289 return Ok(Either::Right(ListChunked::full_null_with_dtype(
290 output_name,
291 output_len,
292 output_inner_dtype.as_ref(),
293 )));
294 }
295
296 let outer_validity = match (&op_apply_type, &broadcast, validity_lhs, validity_rhs) {
301 (BinaryOpApplyType::ListToList, Broadcast::NoBroadcast, l, r) => {
303 combine_validities_and(l.as_ref(), r.as_ref())
304 },
305 (
307 BinaryOpApplyType::ListToList | BinaryOpApplyType::ListToPrimitive,
308 Broadcast::NoBroadcast | Broadcast::Right,
309 v,
310 _,
311 )
312 | (
313 BinaryOpApplyType::ListToList | BinaryOpApplyType::PrimitiveToList,
314 Broadcast::NoBroadcast | Broadcast::Left,
315 _,
316 v,
317 ) => v,
318 _ => None,
319 }
320 .unwrap_or_else(|| Bitmap::new_with_value(true, output_len));
321
322 Ok(Either::Left(Self {
323 op,
324 output_name,
325 op_apply_type,
326 broadcast,
327 output_dtype: output_dtype.clone(),
328 output_primitive_dtype,
329 output_len,
330 outer_validity,
331 data_lhs,
332 data_rhs,
333 list_to_prim_lhs: None,
334 swapped: false,
335 }))
336 }
337
338 pub(super) fn finish(mut self) -> PolarsResult<ListChunked> {
339 self.swapped = true;
354
355 match (&self.op_apply_type, &self.broadcast) {
356 (BinaryOpApplyType::ListToList, Broadcast::NoBroadcast)
357 | (BinaryOpApplyType::ListToList, Broadcast::Right)
358 | (BinaryOpApplyType::ListToPrimitive, Broadcast::NoBroadcast)
359 | (BinaryOpApplyType::ListToPrimitive, Broadcast::Right) => {
360 self.swapped = false;
361 self._finish_impl_dispatch()
362 },
363 (BinaryOpApplyType::ListToList, Broadcast::Left) => {
364 self.broadcast = Broadcast::Right;
365
366 std::mem::swap(&mut self.data_lhs, &mut self.data_rhs);
367 self._finish_impl_dispatch()
368 },
369 (BinaryOpApplyType::ListToPrimitive, Broadcast::Left) => {
370 self.list_to_prim_lhs
371 .replace(Self::materialize_broadcasted_list(
372 &mut self.data_lhs,
373 self.output_len,
374 &self.output_primitive_dtype,
375 ));
376
377 self.broadcast = Broadcast::NoBroadcast;
378
379 self.swapped = false;
382 self._finish_impl_dispatch()
383 },
384 (BinaryOpApplyType::PrimitiveToList, Broadcast::NoBroadcast) => {
385 self.op_apply_type = BinaryOpApplyType::ListToPrimitive;
386
387 std::mem::swap(&mut self.data_lhs, &mut self.data_rhs);
388 self._finish_impl_dispatch()
389 },
390 (BinaryOpApplyType::PrimitiveToList, Broadcast::Right) => {
391 self.list_to_prim_lhs
396 .replace(Self::materialize_broadcasted_list(
397 &mut self.data_rhs,
398 self.output_len,
399 &self.output_primitive_dtype,
400 ));
401
402 self.op_apply_type = BinaryOpApplyType::ListToPrimitive;
403 self.broadcast = Broadcast::NoBroadcast;
404
405 std::mem::swap(&mut self.data_lhs, &mut self.data_rhs);
406 self._finish_impl_dispatch()
407 },
408 (BinaryOpApplyType::PrimitiveToList, Broadcast::Left) => {
409 self.op_apply_type = BinaryOpApplyType::ListToPrimitive;
410 self.broadcast = Broadcast::Right;
411
412 std::mem::swap(&mut self.data_lhs, &mut self.data_rhs);
413 self._finish_impl_dispatch()
414 },
415 }
416 }
417
418 fn _finish_impl_dispatch(&mut self) -> PolarsResult<ListChunked> {
419 let output_dtype = self.output_dtype.clone();
420 let output_len = self.output_len;
421
422 let prim_lhs = self
423 .data_lhs
424 .2
425 .get_leaf_array()
426 .cast(&self.output_primitive_dtype)?
427 .rechunk();
428 let prim_rhs = self
429 .data_rhs
430 .2
431 .get_leaf_array()
432 .cast(&self.output_primitive_dtype)?
433 .rechunk();
434
435 debug_assert_eq!(prim_lhs.dtype(), prim_rhs.dtype());
436 let prim_dtype = prim_lhs.dtype();
437 debug_assert_eq!(prim_dtype, &self.output_primitive_dtype);
438
439 let out = with_match_physical_numeric_polars_type!(&prim_dtype, |$T| {
441 self._finish_impl::<$T>(prim_lhs, prim_rhs)
442 })?;
443
444 debug_assert_eq!(out.dtype(), &output_dtype);
445 assert_eq!(out.len(), output_len);
446
447 Ok(out)
448 }
449
450 fn _finish_impl<T: PolarsNumericType>(
452 &mut self,
453 prim_s_lhs: Series,
454 prim_s_rhs: Series,
455 ) -> PolarsResult<ListChunked>
456 where
457 T::Native: PlNumArithmetic,
458 PrimitiveArray<T::Native>:
459 polars_compute::comparisons::TotalEqKernel<Scalar = T::Native>,
460 T::Native: Zero + IsFloat,
461 {
462 #[inline(never)]
463 fn check_mismatch_pos(
464 mismatch_pos: usize,
465 offsets_lhs: &OffsetsBuffer<i64>,
466 offsets_rhs: &OffsetsBuffer<i64>,
467 ) -> PolarsResult<()> {
468 if mismatch_pos < offsets_lhs.len_proxy() {
469 let len_r = offsets_rhs.length_at(if offsets_rhs.len_proxy() == 1 {
471 0
472 } else {
473 mismatch_pos
474 });
475 polars_bail!(
476 ShapeMismatch:
477 "list lengths differed at index {}: {} != {}",
478 mismatch_pos,
479 offsets_lhs.length_at(mismatch_pos), len_r
480 )
481 }
482 Ok(())
483 }
484
485 let mut arr_lhs = {
486 let ca: &ChunkedArray<T> = prim_s_lhs.as_ref().as_ref();
487 assert_eq!(ca.chunks().len(), 1);
488 ca.downcast_get(0).unwrap().clone()
489 };
490
491 let mut arr_rhs = {
492 let ca: &ChunkedArray<T> = prim_s_rhs.as_ref().as_ref();
493 assert_eq!(ca.chunks().len(), 1);
494 ca.downcast_get(0).unwrap().clone()
495 };
496
497 match (&self.op_apply_type, &self.broadcast) {
498 (BinaryOpApplyType::ListToPrimitive, Broadcast::Right) => {},
501 _ if self.list_to_prim_lhs.is_none() => {
502 self.op.0.prepare_numeric_op_side_validities::<T>(
503 &mut arr_lhs,
504 &mut arr_rhs,
505 self.swapped,
506 )
507 },
508 (BinaryOpApplyType::ListToPrimitive, Broadcast::NoBroadcast) => {
509 },
511 _ => unreachable!(),
512 }
513
514 let out = match (&self.op_apply_type, &self.broadcast) {
523 (BinaryOpApplyType::ListToList, Broadcast::NoBroadcast) => {
524 let offsets_lhs = &self.data_lhs.0[0];
525 let offsets_rhs = &self.data_rhs.0[0];
526
527 assert_eq!(offsets_lhs.len_proxy(), offsets_rhs.len_proxy());
528
529 let n_values = arr_lhs.len();
531 let mut out_vec: Vec<T::Native> = Vec::with_capacity(n_values);
532 let out_ptr: *mut T::Native = out_vec.as_mut_ptr();
533
534 let mut mismatch_pos = 0;
537
538 with_match_pl_num_arith!(&self.op.0, self.swapped, |$OP| {
539 for (i, ((lhs_start, lhs_len), (rhs_start, rhs_len))) in offsets_lhs
540 .offset_and_length_iter()
541 .zip(offsets_rhs.offset_and_length_iter())
542 .enumerate()
543 {
544 if
545 (mismatch_pos == i)
546 & (
547 (lhs_len == rhs_len)
548 | unsafe { !self.outer_validity.get_bit_unchecked(i) }
549 )
550 {
551 mismatch_pos += 1;
552 }
553
554 let len: usize = lhs_len.min(rhs_len);
557
558 for i in 0..len {
559 let l_idx = i + lhs_start;
560 let r_idx = i + rhs_start;
561
562 let l = unsafe { arr_lhs.value_unchecked(l_idx) };
563 let r = unsafe { arr_rhs.value_unchecked(r_idx) };
564 let v = $OP(l, r);
565
566 unsafe { out_ptr.add(l_idx).write(v) };
567 }
568 }
569 });
570
571 check_mismatch_pos(mismatch_pos, offsets_lhs, offsets_rhs)?;
572
573 unsafe { out_vec.set_len(n_values) };
574
575 #[inline(never)]
577 fn combine_validities_list_to_list_no_broadcast(
578 offsets_lhs: &OffsetsBuffer<i64>,
579 offsets_rhs: &OffsetsBuffer<i64>,
580 validity_lhs: Option<&Bitmap>,
581 validity_rhs: Option<&Bitmap>,
582 len_lhs: usize,
583 ) -> Option<Bitmap> {
584 match (validity_lhs, validity_rhs) {
585 (Some(l), Some(r)) => Some((l.clone().make_mut(), r)),
586 (Some(v), None) => return Some(v.clone()),
587 (None, Some(v)) => {
588 Some((Bitmap::new_with_value(true, len_lhs).make_mut(), v))
589 },
590 (None, None) => None,
591 }
592 .map(|(mut validity_out, validity_rhs)| {
593 for ((lhs_start, lhs_len), (rhs_start, rhs_len)) in offsets_lhs
594 .offset_and_length_iter()
595 .zip(offsets_rhs.offset_and_length_iter())
596 {
597 let len: usize = lhs_len.min(rhs_len);
598
599 for i in 0..len {
600 let l_idx = i + lhs_start;
601 let r_idx = i + rhs_start;
602
603 let l_valid = unsafe { validity_out.get_unchecked(l_idx) };
604 let r_valid = unsafe { validity_rhs.get_bit_unchecked(r_idx) };
605 let is_valid = l_valid & r_valid;
606
607 unsafe { validity_out.set_unchecked(l_idx, is_valid) };
609 }
610 }
611
612 validity_out.freeze()
613 })
614 }
615
616 let leaf_validity = combine_validities_list_to_list_no_broadcast(
617 offsets_lhs,
618 offsets_rhs,
619 arr_lhs.validity(),
620 arr_rhs.validity(),
621 arr_lhs.len(),
622 );
623
624 let arr =
625 PrimitiveArray::<T::Native>::from_vec(out_vec).with_validity(leaf_validity);
626
627 let (offsets, validities, _) = std::mem::take(&mut self.data_lhs);
628 assert_eq!(offsets.len(), 1);
629
630 self.finish_offsets_and_validities(Box::new(arr), offsets, validities)
631 },
632 (BinaryOpApplyType::ListToList, Broadcast::Right) => {
633 let offsets_lhs = &self.data_lhs.0[0];
634 let offsets_rhs = &self.data_rhs.0[0];
635
636 let n_values = arr_lhs.len();
638 let mut out_vec: Vec<T::Native> = Vec::with_capacity(n_values);
639 let out_ptr: *mut T::Native = out_vec.as_mut_ptr();
640
641 assert_eq!(offsets_rhs.len_proxy(), 1);
642 let rhs_start = *offsets_rhs.first() as usize;
643 let width = offsets_rhs.range() as usize;
644
645 let mut mismatch_pos = 0;
646
647 with_match_pl_num_arith!(&self.op.0, self.swapped, |$OP| {
648 for (i, (lhs_start, lhs_len)) in offsets_lhs.offset_and_length_iter().enumerate() {
649 if ((lhs_len == width) & (mismatch_pos == i))
650 | unsafe { !self.outer_validity.get_bit_unchecked(i) }
651 {
652 mismatch_pos += 1;
653 }
654
655 let len: usize = lhs_len.min(width);
656
657 for i in 0..len {
658 let l_idx = i + lhs_start;
659 let r_idx = i + rhs_start;
660
661 let l = unsafe { arr_lhs.value_unchecked(l_idx) };
662 let r = unsafe { arr_rhs.value_unchecked(r_idx) };
663 let v = $OP(l, r);
664
665 unsafe {
666 out_ptr.add(l_idx).write(v);
667 }
668 }
669 }
670 });
671
672 check_mismatch_pos(mismatch_pos, offsets_lhs, offsets_rhs)?;
673
674 unsafe { out_vec.set_len(n_values) };
675
676 #[inline(never)]
677 fn combine_validities_list_to_list_broadcast_right(
678 offsets_lhs: &OffsetsBuffer<i64>,
679 validity_lhs: Option<&Bitmap>,
680 validity_rhs: Option<&Bitmap>,
681 len_lhs: usize,
682 width: usize,
683 rhs_start: usize,
684 ) -> Option<Bitmap> {
685 match (validity_lhs, validity_rhs) {
686 (Some(l), Some(r)) => Some((l.clone().make_mut(), r)),
687 (Some(v), None) => return Some(v.clone()),
688 (None, Some(v)) => {
689 Some((Bitmap::new_with_value(true, len_lhs).make_mut(), v))
690 },
691 (None, None) => None,
692 }
693 .map(|(mut validity_out, validity_rhs)| {
694 for (lhs_start, lhs_len) in offsets_lhs.offset_and_length_iter() {
695 let len: usize = lhs_len.min(width);
696
697 for i in 0..len {
698 let l_idx = i + lhs_start;
699 let r_idx = i + rhs_start;
700
701 let l_valid = unsafe { validity_out.get_unchecked(l_idx) };
702 let r_valid = unsafe { validity_rhs.get_bit_unchecked(r_idx) };
703 let is_valid = l_valid & r_valid;
704
705 unsafe { validity_out.set_unchecked(l_idx, is_valid) };
707 }
708 }
709
710 validity_out.freeze()
711 })
712 }
713
714 let leaf_validity = combine_validities_list_to_list_broadcast_right(
715 offsets_lhs,
716 arr_lhs.validity(),
717 arr_rhs.validity(),
718 arr_lhs.len(),
719 width,
720 rhs_start,
721 );
722
723 let arr =
724 PrimitiveArray::<T::Native>::from_vec(out_vec).with_validity(leaf_validity);
725
726 let (offsets, validities, _) = std::mem::take(&mut self.data_lhs);
727 assert_eq!(offsets.len(), 1);
728
729 self.finish_offsets_and_validities(Box::new(arr), offsets, validities)
730 },
731 (BinaryOpApplyType::ListToPrimitive, Broadcast::NoBroadcast)
732 if self.list_to_prim_lhs.is_none() =>
733 {
734 let offsets_lhs = self.data_lhs.0.as_slice();
735
736 let n_values = arr_lhs.len();
741 let mut out_vec = Vec::<T::Native>::with_capacity(n_values);
742 let out_ptr = out_vec.as_mut_ptr();
743
744 with_match_pl_num_arith!(&self.op.0, self.swapped, |$OP| {
745 for (i, l_range) in OffsetsBuffer::<i64>::leaf_ranges_iter(offsets_lhs).enumerate()
746 {
747 let r = unsafe { arr_rhs.value_unchecked(i) };
748 for l_idx in l_range {
749 unsafe {
750 let l = arr_lhs.value_unchecked(l_idx);
751 let v = $OP(l, r);
752 out_ptr.add(l_idx).write(v);
753 }
754 }
755 }
756 });
757
758 unsafe { out_vec.set_len(n_values) }
759
760 let leaf_validity = combine_validities_list_to_primitive_no_broadcast(
761 offsets_lhs,
762 arr_lhs.validity(),
763 arr_rhs.validity(),
764 arr_lhs.len(),
765 );
766
767 let arr =
768 PrimitiveArray::<T::Native>::from_vec(out_vec).with_validity(leaf_validity);
769
770 let (offsets, validities, _) = std::mem::take(&mut self.data_lhs);
771 self.finish_offsets_and_validities(Box::new(arr), offsets, validities)
772 },
773 (BinaryOpApplyType::ListToPrimitive, Broadcast::NoBroadcast) => {
777 let offsets_lhs = self.data_lhs.0.as_slice();
778
779 let (mut arr, n_values) = Option::take(&mut self.list_to_prim_lhs).unwrap();
780 let arr = arr
781 .as_any_mut()
782 .downcast_mut::<PrimitiveArray<T::Native>>()
783 .unwrap();
784 let mut arr_lhs = std::mem::take(arr);
785
786 self.op.0.prepare_numeric_op_side_validities::<T>(
787 &mut arr_lhs,
788 &mut arr_rhs,
789 self.swapped,
790 );
791
792 let arr_lhs_mut_slice = arr_lhs.get_mut_values().unwrap();
793 assert_eq!(arr_lhs_mut_slice.len(), n_values);
794
795 with_match_pl_num_arith!(&self.op.0, self.swapped, |$OP| {
796 for (i, l_range) in OffsetsBuffer::<i64>::leaf_ranges_iter(offsets_lhs).enumerate()
797 {
798 let r = unsafe { arr_rhs.value_unchecked(i) };
799 for l_idx in l_range {
800 unsafe {
801 let l = arr_lhs_mut_slice.get_unchecked_mut(l_idx);
802 *l = $OP(*l, r);
803 }
804 }
805 }
806 });
807
808 let leaf_validity = combine_validities_list_to_primitive_no_broadcast(
809 offsets_lhs,
810 arr_lhs.validity(),
811 arr_rhs.validity(),
812 arr_lhs.len(),
813 );
814
815 let arr = arr_lhs.with_validity(leaf_validity);
816
817 let (offsets, validities, _) = std::mem::take(&mut self.data_lhs);
818 self.finish_offsets_and_validities(Box::new(arr), offsets, validities)
819 },
820 (BinaryOpApplyType::ListToPrimitive, Broadcast::Right) => {
821 assert_eq!(arr_rhs.len(), 1);
822
823 let Some(r) = (unsafe { arr_rhs.get_unchecked(0) }) else {
824 let (offsets, validities, _) = std::mem::take(&mut self.data_lhs);
826 return Ok(self.finish_offsets_and_validities(
827 Box::new(
828 arr_lhs.clone().with_validity(Some(Bitmap::new_with_value(
829 false,
830 arr_lhs.len(),
831 ))),
832 ),
833 offsets,
834 validities,
835 ));
836 };
837
838 let arr = self
839 .op
840 .0
841 .apply_array_to_scalar::<T>(arr_lhs, r, self.swapped);
842 let (offsets, validities, _) = std::mem::take(&mut self.data_lhs);
843
844 self.finish_offsets_and_validities(Box::new(arr), offsets, validities)
845 },
846 v @ (BinaryOpApplyType::PrimitiveToList, Broadcast::Right)
847 | v @ (BinaryOpApplyType::ListToList, Broadcast::Left)
848 | v @ (BinaryOpApplyType::ListToPrimitive, Broadcast::Left)
849 | v @ (BinaryOpApplyType::PrimitiveToList, Broadcast::Left)
850 | v @ (BinaryOpApplyType::PrimitiveToList, Broadcast::NoBroadcast) => {
851 if cfg!(debug_assertions) {
852 panic!("operation was not re-written: {v:?}")
853 } else {
854 unreachable!()
855 }
856 },
857 };
858
859 Ok(out)
860 }
861
862 fn finish_offsets_and_validities(
865 &mut self,
866 leaf_array: Box<dyn Array>,
867 offsets: Vec<OffsetsBuffer<i64>>,
868 validities: Vec<Option<Bitmap>>,
869 ) -> ListChunked {
870 assert!(!offsets.is_empty());
871 assert_eq!(offsets.len(), validities.len());
872 let mut results = leaf_array;
873
874 let mut iter = offsets.into_iter().zip(validities).rev();
875
876 while iter.len() > 1 {
877 let (offsets, validity) = iter.next().unwrap();
878 let dtype = LargeListArray::default_datatype(results.dtype().clone());
879 results = Box::new(LargeListArray::new(dtype, offsets, results, validity));
880 }
881
882 let (offsets, _) = iter.next().unwrap();
884 let validity = std::mem::take(&mut self.outer_validity);
885 let dtype = LargeListArray::default_datatype(results.dtype().clone());
886 let results = LargeListArray::new(dtype, offsets, results, Some(validity));
887
888 ListChunked::with_chunk(std::mem::take(&mut self.output_name), results)
889 }
890
891 fn materialize_broadcasted_list(
892 side_data: &mut (Vec<OffsetsBuffer<i64>>, Vec<Option<Bitmap>>, Series),
893 output_len: usize,
894 output_primitive_dtype: &DataType,
895 ) -> (Box<dyn Array>, usize) {
896 let s = &side_data.2;
897 assert_eq!(s.len(), 1);
898
899 let expected_n_values = {
900 let offsets = s.list_offsets_and_validities_recursive().0;
901 output_len * OffsetsBuffer::<i64>::leaf_full_start_end(&offsets).len()
902 };
903
904 let ca = s.list().unwrap();
905 let ca = ca
907 .cast(&ca.dtype().cast_leaf(output_primitive_dtype.clone()))
908 .unwrap();
909 assert!(output_len > 1); let ca = ca.new_from_index(0, output_len).rechunk();
911
912 let s = ca.into_series();
913
914 *side_data = {
915 let (a, b) = s.list_offsets_and_validities_recursive();
916 (a, b, Series::default())
918 };
919
920 let n_values = OffsetsBuffer::<i64>::leaf_full_start_end(&side_data.0).len();
921 assert_eq!(n_values, expected_n_values);
922
923 let mut s = s.get_leaf_array();
924 let v = unsafe { s.chunks_mut() };
925
926 assert_eq!(v.len(), 1);
927 (v.swap_remove(0), n_values)
928 }
929 }
930
931 #[inline(never)]
933 fn combine_validities_list_to_primitive_no_broadcast(
934 offsets_lhs: &[OffsetsBuffer<i64>],
935 validity_lhs: Option<&Bitmap>,
936 validity_rhs: Option<&Bitmap>,
937 len_lhs: usize,
938 ) -> Option<Bitmap> {
939 match (validity_lhs, validity_rhs) {
940 (Some(l), Some(r)) => Some((l.clone().make_mut(), r)),
941 (Some(v), None) => return Some(v.clone()),
942 (None, Some(v)) => Some((Bitmap::new_with_value(true, len_lhs).make_mut(), v)),
945 (None, None) => None,
946 }
947 .map(|(mut validity_out, validity_rhs)| {
948 for (i, l_range) in OffsetsBuffer::<i64>::leaf_ranges_iter(offsets_lhs).enumerate() {
949 let r_valid = unsafe { validity_rhs.get_bit_unchecked(i) };
950 for l_idx in l_range {
951 let l_valid = unsafe { validity_out.get_unchecked(l_idx) };
952 let is_valid = l_valid & r_valid;
953
954 unsafe { validity_out.set_unchecked(l_idx, is_valid) };
956 }
957 }
958
959 validity_out.freeze()
960 })
961 }
962}