1use std::sync::Arc;
4
5use arrow::array::{Array, ArrayRef, AsArray, BooleanArray, Datum, Scalar};
6use arrow::compute::kernels::zip::zip;
7use arrow::compute::kernels::{boolean, cmp, numeric};
8use arrow::compute::{cast as arrow_cast, is_not_null, is_null};
9use arrow::datatypes::{
10 DataType, Float32Type, Float64Type, Int8Type, Int16Type, Int32Type, Int64Type,
11};
12
13use crate::NdArrowArray;
14use crate::compute::shape::{broadcast_arrays, broadcast_to};
15use crate::dimensions::Dimensions;
16use crate::error::{Result, nd_err};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19enum Kind {
20 Bool,
21 Int(u8),
22 UInt(u8),
23 Float(u8),
24}
25
26fn kind(data_type: &DataType) -> Option<Kind> {
27 Some(match data_type {
28 DataType::Boolean => Kind::Bool,
29 DataType::Int8 => Kind::Int(8),
30 DataType::Int16 => Kind::Int(16),
31 DataType::Int32 => Kind::Int(32),
32 DataType::Int64 => Kind::Int(64),
33 DataType::UInt8 => Kind::UInt(8),
34 DataType::UInt16 => Kind::UInt(16),
35 DataType::UInt32 => Kind::UInt(32),
36 DataType::UInt64 => Kind::UInt(64),
37 DataType::Float16 => Kind::Float(16),
38 DataType::Float32 => Kind::Float(32),
39 DataType::Float64 => Kind::Float(64),
40 _ => return None,
41 })
42}
43
44fn data_type(kind: Kind) -> DataType {
45 match kind {
46 Kind::Bool => DataType::Boolean,
47 Kind::Int(8) => DataType::Int8,
48 Kind::Int(16) => DataType::Int16,
49 Kind::Int(32) => DataType::Int32,
50 Kind::Int(_) => DataType::Int64,
51 Kind::UInt(8) => DataType::UInt8,
52 Kind::UInt(16) => DataType::UInt16,
53 Kind::UInt(32) => DataType::UInt32,
54 Kind::UInt(_) => DataType::UInt64,
55 Kind::Float(16) => DataType::Float16,
56 Kind::Float(32) => DataType::Float32,
57 Kind::Float(_) => DataType::Float64,
58 }
59}
60
61pub fn result_type(a: &DataType, b: &DataType) -> Option<DataType> {
66 if a == b {
67 return Some(a.clone());
68 }
69 let float_bits_for = |bits: u8| match bits {
70 8 => 16,
71 16 => 32,
72 _ => 64,
73 };
74 let kind = match (kind(a)?, kind(b)?) {
75 (Kind::Bool, other) | (other, Kind::Bool) => other,
76 (Kind::Int(x), Kind::Int(y)) => Kind::Int(x.max(y)),
77 (Kind::UInt(x), Kind::UInt(y)) => Kind::UInt(x.max(y)),
78 (Kind::Int(s), Kind::UInt(u)) | (Kind::UInt(u), Kind::Int(s)) => {
79 if s > u {
80 Kind::Int(s)
81 } else if u < 64 {
82 Kind::Int(u * 2)
83 } else {
84 Kind::Float(64)
85 }
86 }
87 (Kind::Float(x), Kind::Float(y)) => Kind::Float(x.max(y)),
88 (Kind::Float(f), Kind::Int(n) | Kind::UInt(n))
89 | (Kind::Int(n) | Kind::UInt(n), Kind::Float(f)) => Kind::Float(f.max(float_bits_for(n))),
90 };
91 Some(data_type(kind))
92}
93
94fn float_type(data_type: &DataType) -> Result<DataType> {
96 match kind(data_type) {
97 Some(Kind::Float(64)) => Ok(DataType::Float64),
98 Some(Kind::Float(_)) => Ok(DataType::Float32),
99 Some(_) => Ok(DataType::Float64),
100 None => Err(nd_err!(
101 NotSupported,
102 "math function on non-numeric type {data_type}"
103 )),
104 }
105}
106
107fn cast_values(values: &ArrayRef, to: &DataType) -> Result<ArrayRef> {
108 if values.data_type() == to {
109 Ok(values.clone())
110 } else {
111 Ok(arrow_cast(values.as_ref(), to)?)
112 }
113}
114
115enum Operand {
116 Array(ArrayRef),
117 Scalar(Scalar<ArrayRef>),
118}
119
120impl Operand {
121 fn datum(&self) -> &dyn Datum {
122 match self {
123 Operand::Array(array) => array,
124 Operand::Scalar(scalar) => scalar,
125 }
126 }
127}
128
129fn prepare(
131 a: &NdArrowArray,
132 b: &NdArrowArray,
133 to: Option<&DataType>,
134) -> Result<(Operand, Operand, Dimensions)> {
135 let cast = |x: &NdArrowArray| match to {
136 Some(t) => cast_values(x.values(), t),
137 None => Ok(x.values().clone()),
138 };
139 if a.dims() == b.dims() {
140 return Ok((
141 Operand::Array(cast(a)?),
142 Operand::Array(cast(b)?),
143 a.dims().clone(),
144 ));
145 }
146 if b.is_scalar() {
147 return Ok((
148 Operand::Array(cast(a)?),
149 Operand::Scalar(Scalar::new(cast(b)?)),
150 a.dims().clone(),
151 ));
152 }
153 if a.is_scalar() {
154 return Ok((
155 Operand::Scalar(Scalar::new(cast(a)?)),
156 Operand::Array(cast(b)?),
157 b.dims().clone(),
158 ));
159 }
160 let aligned = broadcast_arrays(&[a, b])?;
161 Ok((
162 Operand::Array(cast(&aligned[0])?),
163 Operand::Array(cast(&aligned[1])?),
164 aligned[0].dims().clone(),
165 ))
166}
167
168fn wrap(values: ArrayRef, dims: Dimensions) -> Result<NdArrowArray> {
169 NdArrowArray::try_new(values, dims)
170}
171
172fn arithmetic(
173 a: &NdArrowArray,
174 b: &NdArrowArray,
175 to: Option<DataType>,
176 op: impl Fn(&dyn Datum, &dyn Datum) -> std::result::Result<ArrayRef, arrow::error::ArrowError>,
177) -> Result<NdArrowArray> {
178 let (x, y, dims) = prepare(a, b, to.as_ref())?;
179 wrap(op(x.datum(), y.datum())?, dims)
180}
181
182pub fn add(a: &NdArrowArray, b: &NdArrowArray) -> Result<NdArrowArray> {
184 arithmetic(
185 a,
186 b,
187 result_type(a.data_type(), b.data_type()),
188 numeric::add_wrapping,
189 )
190}
191
192pub fn sub(a: &NdArrowArray, b: &NdArrowArray) -> Result<NdArrowArray> {
194 arithmetic(
195 a,
196 b,
197 result_type(a.data_type(), b.data_type()),
198 numeric::sub_wrapping,
199 )
200}
201
202pub fn mul(a: &NdArrowArray, b: &NdArrowArray) -> Result<NdArrowArray> {
204 arithmetic(
205 a,
206 b,
207 result_type(a.data_type(), b.data_type()),
208 numeric::mul_wrapping,
209 )
210}
211
212pub fn div(a: &NdArrowArray, b: &NdArrowArray) -> Result<NdArrowArray> {
214 let to = match result_type(a.data_type(), b.data_type()) {
215 Some(t) if matches!(kind(&t), Some(Kind::Float(_))) => Some(t),
216 Some(_) => Some(DataType::Float64),
217 None => None,
218 };
219 arithmetic(a, b, to, numeric::div)
220}
221
222pub fn rem(a: &NdArrowArray, b: &NdArrowArray) -> Result<NdArrowArray> {
224 arithmetic(
225 a,
226 b,
227 result_type(a.data_type(), b.data_type()),
228 numeric::rem,
229 )
230}
231
232pub fn floor_divide(a: &NdArrowArray, b: &NdArrowArray) -> Result<NdArrowArray> {
235 floor_div_mod(a, b, false)
236}
237
238pub fn mod_(a: &NdArrowArray, b: &NdArrowArray) -> Result<NdArrowArray> {
241 floor_div_mod(a, b, true)
242}
243
244fn floor_div_mod(a: &NdArrowArray, b: &NdArrowArray, modulo: bool) -> Result<NdArrowArray> {
245 let to = result_type(a.data_type(), b.data_type()).ok_or_else(|| {
246 nd_err!(
247 NotSupported,
248 "floor division on {} and {}",
249 a.data_type(),
250 b.data_type()
251 )
252 })?;
253 let aligned = broadcast_arrays(&[a, b])?;
254 let dims = aligned[0].dims().clone();
255 let values: ArrayRef = match kind(&to) {
256 Some(Kind::Float(bits)) => {
257 let x = cast_values(aligned[0].values(), &DataType::Float64)?;
258 let y = cast_values(aligned[1].values(), &DataType::Float64)?;
259 let out = arrow::compute::binary::<_, _, _, Float64Type>(
260 x.as_primitive::<Float64Type>(),
261 y.as_primitive::<Float64Type>(),
262 |x, y| {
263 if modulo {
264 let r = x % y;
265 if r != 0.0 && (r < 0.0) != (y < 0.0) {
266 r + y
267 } else {
268 r
269 }
270 } else {
271 (x / y).floor()
272 }
273 },
274 )?;
275 let to = if bits == 64 {
276 DataType::Float64
277 } else {
278 to.clone()
279 };
280 cast_values(&(Arc::new(out) as ArrayRef), &to)?
281 }
282 Some(Kind::Int(_) | Kind::UInt(_) | Kind::Bool) => {
283 let x = cast_values(aligned[0].values(), &DataType::Int64)?;
284 let y = cast_values(aligned[1].values(), &DataType::Int64)?;
285 let (x, y) = (x.as_primitive::<Int64Type>(), y.as_primitive::<Int64Type>());
286 let out: arrow::array::Int64Array = x
287 .iter()
288 .zip(y.iter())
289 .map(|(x, y)| match (x, y) {
290 (Some(_), Some(0)) | (None, _) | (_, None) => None,
291 (Some(x), Some(y)) => {
292 let q = x.wrapping_div(y) - i64::from(x % y != 0 && ((x < 0) != (y < 0)));
293 Some(if modulo {
294 x.wrapping_sub(q.wrapping_mul(y))
295 } else {
296 q
297 })
298 }
299 })
300 .collect();
301 cast_values(&(Arc::new(out) as ArrayRef), &to)?
302 }
303 None => unreachable!("result_type returned a numeric type"),
304 };
305 wrap(values, dims)
306}
307
308pub fn pow(a: &NdArrowArray, b: &NdArrowArray) -> Result<NdArrowArray> {
310 let to = result_type(a.data_type(), b.data_type()).ok_or_else(|| {
311 nd_err!(
312 NotSupported,
313 "pow on {} and {}",
314 a.data_type(),
315 b.data_type()
316 )
317 })?;
318 let to = float_type(&to)?;
319 let aligned = broadcast_arrays(&[a, b])?;
320 let x = cast_values(aligned[0].values(), &to)?;
321 let y = cast_values(aligned[1].values(), &to)?;
322 let values: ArrayRef = match to {
323 DataType::Float32 => Arc::new(arrow::compute::binary::<_, _, _, Float32Type>(
324 x.as_primitive::<Float32Type>(),
325 y.as_primitive::<Float32Type>(),
326 f32::powf,
327 )?),
328 _ => Arc::new(arrow::compute::binary::<_, _, _, Float64Type>(
329 x.as_primitive::<Float64Type>(),
330 y.as_primitive::<Float64Type>(),
331 f64::powf,
332 )?),
333 };
334 wrap(values, aligned[0].dims().clone())
335}
336
337fn comparison(
338 a: &NdArrowArray,
339 b: &NdArrowArray,
340 op: impl Fn(&dyn Datum, &dyn Datum) -> std::result::Result<BooleanArray, arrow::error::ArrowError>,
341) -> Result<NdArrowArray> {
342 let to = result_type(a.data_type(), b.data_type());
343 let (x, y, dims) = prepare(a, b, to.as_ref())?;
344 wrap(Arc::new(op(x.datum(), y.datum())?), dims)
345}
346
347pub fn eq(a: &NdArrowArray, b: &NdArrowArray) -> Result<NdArrowArray> {
349 comparison(a, b, cmp::eq)
350}
351
352pub fn ne(a: &NdArrowArray, b: &NdArrowArray) -> Result<NdArrowArray> {
354 comparison(a, b, cmp::neq)
355}
356
357pub fn lt(a: &NdArrowArray, b: &NdArrowArray) -> Result<NdArrowArray> {
359 comparison(a, b, cmp::lt)
360}
361
362pub fn le(a: &NdArrowArray, b: &NdArrowArray) -> Result<NdArrowArray> {
364 comparison(a, b, cmp::lt_eq)
365}
366
367pub fn gt(a: &NdArrowArray, b: &NdArrowArray) -> Result<NdArrowArray> {
369 comparison(a, b, cmp::gt)
370}
371
372pub fn ge(a: &NdArrowArray, b: &NdArrowArray) -> Result<NdArrowArray> {
374 comparison(a, b, cmp::gt_eq)
375}
376
377fn boolean_pair(
378 a: &NdArrowArray,
379 b: &NdArrowArray,
380 op: impl Fn(
381 &BooleanArray,
382 &BooleanArray,
383 ) -> std::result::Result<BooleanArray, arrow::error::ArrowError>,
384) -> Result<NdArrowArray> {
385 for x in [a, b] {
386 if x.data_type() != &DataType::Boolean {
387 return Err(nd_err!(
388 InvalidArgument,
389 "logical operation needs Boolean values, got {}",
390 x.data_type()
391 ));
392 }
393 }
394 let aligned = broadcast_arrays(&[a, b])?;
395 let values = op(
396 aligned[0].values().as_boolean(),
397 aligned[1].values().as_boolean(),
398 )?;
399 wrap(Arc::new(values), aligned[0].dims().clone())
400}
401
402pub fn logical_and(a: &NdArrowArray, b: &NdArrowArray) -> Result<NdArrowArray> {
404 boolean_pair(a, b, boolean::and_kleene)
405}
406
407pub fn logical_or(a: &NdArrowArray, b: &NdArrowArray) -> Result<NdArrowArray> {
409 boolean_pair(a, b, boolean::or_kleene)
410}
411
412pub fn logical_xor(a: &NdArrowArray, b: &NdArrowArray) -> Result<NdArrowArray> {
414 boolean_pair(a, b, |x, y| cmp::neq(x, y))
415}
416
417pub fn logical_not(a: &NdArrowArray) -> Result<NdArrowArray> {
419 let values = a
420 .values()
421 .as_boolean_opt()
422 .ok_or_else(|| nd_err!(InvalidArgument, "logical_not needs Boolean values"))?;
423 wrap(Arc::new(boolean::not(values)?), a.dims().clone())
424}
425
426pub fn is_null_mask(a: &NdArrowArray) -> Result<NdArrowArray> {
428 wrap(Arc::new(is_null(a.values().as_ref())?), a.dims().clone())
429}
430
431pub fn is_not_null_mask(a: &NdArrowArray) -> Result<NdArrowArray> {
433 wrap(
434 Arc::new(is_not_null(a.values().as_ref())?),
435 a.dims().clone(),
436 )
437}
438
439pub fn neg(a: &NdArrowArray) -> Result<NdArrowArray> {
441 wrap(
442 numeric::neg_wrapping(a.values().as_ref())?,
443 a.dims().clone(),
444 )
445}
446
447pub fn abs(a: &NdArrowArray) -> Result<NdArrowArray> {
449 let v = a.values();
450 let values: ArrayRef = match v.data_type() {
451 DataType::Int8 => Arc::new(
452 v.as_primitive::<Int8Type>()
453 .unary::<_, Int8Type>(i8::wrapping_abs),
454 ),
455 DataType::Int16 => Arc::new(
456 v.as_primitive::<Int16Type>()
457 .unary::<_, Int16Type>(i16::wrapping_abs),
458 ),
459 DataType::Int32 => Arc::new(
460 v.as_primitive::<Int32Type>()
461 .unary::<_, Int32Type>(i32::wrapping_abs),
462 ),
463 DataType::Int64 => Arc::new(
464 v.as_primitive::<Int64Type>()
465 .unary::<_, Int64Type>(i64::wrapping_abs),
466 ),
467 DataType::UInt8 | DataType::UInt16 | DataType::UInt32 | DataType::UInt64 => v.clone(),
468 DataType::Float32 => Arc::new(
469 v.as_primitive::<Float32Type>()
470 .unary::<_, Float32Type>(f32::abs),
471 ),
472 DataType::Float64 => Arc::new(
473 v.as_primitive::<Float64Type>()
474 .unary::<_, Float64Type>(f64::abs),
475 ),
476 other => return Err(nd_err!(NotSupported, "abs on {other}")),
477 };
478 wrap(values, a.dims().clone())
479}
480
481pub fn map_float(
483 a: &NdArrowArray,
484 f32_op: impl Fn(f32) -> f32,
485 f64_op: impl Fn(f64) -> f64,
486) -> Result<NdArrowArray> {
487 let to = float_type(a.data_type())?;
488 let values = cast_values(a.values(), &to)?;
489 let out: ArrayRef = match to {
490 DataType::Float32 => Arc::new(
491 values
492 .as_primitive::<Float32Type>()
493 .unary::<_, Float32Type>(f32_op),
494 ),
495 _ => Arc::new(
496 values
497 .as_primitive::<Float64Type>()
498 .unary::<_, Float64Type>(f64_op),
499 ),
500 };
501 wrap(out, a.dims().clone())
502}
503
504fn float_predicate(
505 a: &NdArrowArray,
506 f32_op: impl Fn(f32) -> bool,
507 f64_op: impl Fn(f64) -> bool,
508) -> Result<NdArrowArray> {
509 let to = float_type(a.data_type())?;
510 let values = cast_values(a.values(), &to)?;
511 let out = match to {
512 DataType::Float32 => BooleanArray::from_unary(values.as_primitive::<Float32Type>(), f32_op),
513 _ => BooleanArray::from_unary(values.as_primitive::<Float64Type>(), f64_op),
514 };
515 wrap(Arc::new(out), a.dims().clone())
516}
517
518macro_rules! float_functions {
519 ($($(#[$doc:meta])* $name:ident => $method:ident),* $(,)?) => {$(
520 $(#[$doc])*
521 pub fn $name(a: &NdArrowArray) -> Result<NdArrowArray> {
522 map_float(a, f32::$method, f64::$method)
523 }
524 )*};
525}
526
527float_functions!(
528 sqrt => sqrt,
530 cbrt => cbrt,
532 exp => exp,
534 log => ln,
536 log2 => log2,
538 log10 => log10,
540 log1p => ln_1p,
542 expm1 => exp_m1,
544 sin => sin,
546 cos => cos,
548 tan => tan,
550 arcsin => asin,
552 arccos => acos,
554 arctan => atan,
556 sinh => sinh,
558 cosh => cosh,
560 tanh => tanh,
562 floor => floor,
564 ceil => ceil,
566 trunc => trunc,
568 round => round_ties_even,
570);
571
572pub fn deg2rad(a: &NdArrowArray) -> Result<NdArrowArray> {
574 map_float(a, f32::to_radians, f64::to_radians)
575}
576
577pub fn rad2deg(a: &NdArrowArray) -> Result<NdArrowArray> {
579 map_float(a, f32::to_degrees, f64::to_degrees)
580}
581
582pub fn isnan(a: &NdArrowArray) -> Result<NdArrowArray> {
584 float_predicate(a, f32::is_nan, f64::is_nan)
585}
586
587pub fn isinf(a: &NdArrowArray) -> Result<NdArrowArray> {
589 float_predicate(a, f32::is_infinite, f64::is_infinite)
590}
591
592pub fn isfinite(a: &NdArrowArray) -> Result<NdArrowArray> {
594 float_predicate(a, f32::is_finite, f64::is_finite)
595}
596
597pub fn nan_to_null(a: &NdArrowArray) -> Result<NdArrowArray> {
599 if !matches!(kind(a.data_type()), Some(Kind::Float(_))) {
600 return Ok(a.clone());
601 }
602 let mask = isnan(a)?;
603 let values = arrow::compute::nullif(a.values().as_ref(), mask.values().as_boolean())?;
604 Ok(wrap(values, a.dims().clone())?.with_attributes(a.attributes().clone()))
605}
606
607pub fn cast(a: &NdArrowArray, to: &DataType) -> Result<NdArrowArray> {
609 Ok(wrap(cast_values(a.values(), to)?, a.dims().clone())?
610 .with_attributes(a.attributes().clone()))
611}
612
613fn scalar_operand(x: &NdArrowArray, target: &Dimensions, to: &DataType) -> Result<Operand> {
614 if x.is_scalar() {
615 return Ok(Operand::Scalar(Scalar::new(cast_values(x.values(), to)?)));
616 }
617 let aligned = broadcast_to(x, target)?;
618 Ok(Operand::Array(cast_values(aligned.values(), to)?))
619}
620
621pub fn where_(cond: &NdArrowArray, x: &NdArrowArray, y: &NdArrowArray) -> Result<NdArrowArray> {
624 if cond.data_type() != &DataType::Boolean {
625 return Err(nd_err!(
626 InvalidArgument,
627 "where condition must be Boolean, got {}",
628 cond.data_type()
629 ));
630 }
631 let target = Dimensions::broadcast(&[cond.dims(), x.dims(), y.dims()])?;
632 let to = result_type(x.data_type(), y.data_type()).unwrap_or_else(|| x.data_type().clone());
633 let mask = broadcast_to(cond, &target)?;
634 let truthy = scalar_operand(x, &target, &to)?;
635 let falsy = scalar_operand(y, &target, &to)?;
636 let values = if target.is_scalar() {
637 let pick = if mask.values().as_boolean().is_valid(0) && mask.values().as_boolean().value(0)
638 {
639 &truthy
640 } else {
641 &falsy
642 };
643 match pick {
644 Operand::Array(a) => a.clone(),
645 Operand::Scalar(s) => s.clone().into_inner(),
646 }
647 } else {
648 zip(mask.values().as_boolean(), truthy.datum(), falsy.datum())?
649 };
650 Ok(wrap(values, target)?.with_attributes(x.attributes().clone()))
651}
652
653pub fn fill_null(a: &NdArrowArray, value: &ArrayRef) -> Result<NdArrowArray> {
655 if value.len() != 1 {
656 return Err(nd_err!(InvalidArgument, "fill value must have length 1"));
657 }
658 if a.values().null_count() == 0 {
659 return Ok(a.clone());
660 }
661 let fill = Scalar::new(cast_values(value, a.data_type())?);
662 let valid = is_not_null(a.values().as_ref())?;
663 Ok(wrap(zip(&valid, a.values(), &fill)?, a.dims().clone())?
664 .with_attributes(a.attributes().clone()))
665}
666
667pub fn clip(
669 a: &NdArrowArray,
670 min: Option<&ArrayRef>,
671 max: Option<&ArrayRef>,
672) -> Result<NdArrowArray> {
673 let mut values = a.values().clone();
674 for (bound, below) in [(min, true), (max, false)] {
675 let Some(bound) = bound else { continue };
676 let bound = Scalar::new(cast_values(bound, a.data_type())?);
677 let outside = if below {
678 cmp::lt(&values, &bound)?
679 } else {
680 cmp::gt(&values, &bound)?
681 };
682 values = zip(&outside, &bound, &values)?;
683 }
684 Ok(wrap(values, a.dims().clone())?.with_attributes(a.attributes().clone()))
685}
686
687#[cfg(test)]
688mod tests {
689 use arrow::array::{Float32Array, Float64Array, Int8Array, Int32Array, UInt8Array};
690 use arrow::datatypes::{Float64Type, Int32Type};
691
692 use super::*;
693 use crate::{Attributes, IntoScalar};
694
695 fn nd(values: ArrayRef, dims: &[(&str, usize)]) -> NdArrowArray {
696 NdArrowArray::try_new(values, Dimensions::try_new(dims.iter().copied()).unwrap()).unwrap()
697 }
698
699 fn scalar(value: impl IntoScalar) -> NdArrowArray {
700 NdArrowArray::try_new_scalar(value.into_scalar()).unwrap()
701 }
702
703 fn f64s(a: &NdArrowArray) -> Vec<Option<f64>> {
704 a.values().as_primitive::<Float64Type>().iter().collect()
705 }
706
707 #[test]
708 fn result_type_follows_numpy() {
709 use DataType::*;
710 let cases = [
711 (Int8, Int16, Int16),
712 (Int8, UInt8, Int16),
713 (Int64, UInt64, Float64),
714 (UInt8, Int32, Int32),
715 (Float32, Int16, Float32),
716 (Float32, Int32, Float64),
717 (Float16, Int8, Float16),
718 (Boolean, Int8, Int8),
719 (Float32, Float64, Float64),
720 ];
721 for (a, b, expected) in cases {
722 assert_eq!(result_type(&a, &b), Some(expected.clone()), "{a} + {b}");
723 assert_eq!(result_type(&b, &a), Some(expected), "{b} + {a}");
724 }
725 assert_eq!(result_type(&Utf8, &Int8), None);
726 }
727
728 #[test]
729 fn add_broadcasts_by_name() {
730 let a = nd(Arc::new(Int32Array::from(vec![1, 2, 3])), &[("lat", 3)]);
731 let b = nd(Arc::new(Int32Array::from(vec![10, 20])), &[("time", 2)]);
732 let c = add(&a, &b).unwrap();
733 assert_eq!(c.dims().names(), vec!["lat", "time"]);
734 assert_eq!(
735 c.values().as_primitive::<Int32Type>().values(),
736 &[11, 21, 12, 22, 13, 23]
737 );
738 }
739
740 #[test]
741 fn scalar_operands_and_promotion() {
742 let a = nd(Arc::new(Int8Array::from(vec![1, 2])), &[("x", 2)]);
743 let c = mul(&a, &scalar(1.5f32)).unwrap();
744 assert_eq!(c.data_type(), &DataType::Float32);
745 let d = div(&a, &scalar(2i8)).unwrap();
746 assert_eq!(f64s(&d), vec![Some(0.5), Some(1.0)]);
747 let wrapped = add(&nd(Arc::new(Int8Array::from(vec![127])), &[]), &scalar(1i8)).unwrap();
748 assert_eq!(wrapped.values().as_primitive::<Int8Type>().value(0), -128);
749 let u = sub(&nd(Arc::new(UInt8Array::from(vec![1])), &[]), &scalar(2i8)).unwrap();
750 assert_eq!(u.data_type(), &DataType::Int16);
751 }
752
753 #[test]
754 fn nulls_propagate_and_fill() {
755 let a = nd(
756 Arc::new(Float64Array::from(vec![Some(1.0), None])),
757 &[("x", 2)],
758 );
759 let b = add(&a, &scalar(1.0f64)).unwrap();
760 assert_eq!(f64s(&b), vec![Some(2.0), None]);
761 let filled = fill_null(&b, &0.0f64.into_scalar()).unwrap();
762 assert_eq!(f64s(&filled), vec![Some(2.0), Some(0.0)]);
763 }
764
765 #[test]
766 fn comparison_and_logic() {
767 let a = nd(Arc::new(Int32Array::from(vec![1, 5, 3])), &[("x", 3)]);
768 let gt3 = gt(&a, &scalar(2i32)).unwrap();
769 let lt5 = lt(&a, &scalar(5i64)).unwrap();
770 let both = logical_and(>3, <5).unwrap();
771 let v: Vec<bool> = both
772 .values()
773 .as_boolean()
774 .iter()
775 .map(Option::unwrap)
776 .collect();
777 assert_eq!(v, vec![false, false, true]);
778 let not = logical_not(&both).unwrap();
779 assert!(not.values().as_boolean().value(0));
780 assert!(logical_and(&a, &a).is_err());
781 }
782
783 #[test]
784 fn math_functions_promote_to_float() {
785 let a = nd(Arc::new(Int32Array::from(vec![1, 4, 9])), &[("x", 3)]);
786 assert_eq!(
787 f64s(&sqrt(&a).unwrap()),
788 vec![Some(1.0), Some(2.0), Some(3.0)]
789 );
790 let f = nd(
791 Arc::new(Float32Array::from(vec![2.5, -0.5, 1.5])),
792 &[("x", 3)],
793 );
794 let r = round(&f).unwrap();
795 assert_eq!(
796 r.values().as_primitive::<Float32Type>().values(),
797 &[2.0, -0.0, 2.0]
798 );
799 let ab = abs(&nd(Arc::new(Int32Array::from(vec![-3, 2])), &[("x", 2)])).unwrap();
800 assert_eq!(ab.values().as_primitive::<Int32Type>().values(), &[3, 2]);
801 }
802
803 #[test]
804 fn where_clip_and_nan() {
805 let a = nd(
806 Arc::new(Float64Array::from(vec![1.0, f64::NAN, 7.0])),
807 &[("x", 3)],
808 );
809 let a = a.with_attributes(Attributes::new().with("units", "K"));
810 let cond = gt(&a, &scalar(2.0f64)).unwrap();
811 let w = where_(&cond, &a, &scalar(0.0f64)).unwrap();
812 assert_eq!(w.attributes().get_str("units"), Some("K"));
813 let v = f64s(&w);
814 assert_eq!(v[0], Some(0.0));
815 assert_eq!(v[2], Some(7.0));
816
817 let c = clip(&a, Some(&2.0f64.into_scalar()), Some(&5.0f64.into_scalar())).unwrap();
818 assert_eq!(f64s(&c)[0], Some(2.0));
819 assert_eq!(f64s(&c)[2], Some(5.0));
820
821 let n = nan_to_null(&a).unwrap();
822 assert_eq!(n.values().null_count(), 1);
823 }
824
825 #[test]
826 fn pow_broadcasts() {
827 let a = nd(Arc::new(Int32Array::from(vec![2, 3])), &[("x", 2)]);
828 let p = pow(&a, &scalar(2i32)).unwrap();
829 assert_eq!(f64s(&p), vec![Some(4.0), Some(9.0)]);
830 }
831}