1use std::ops::{Add, Div, Mul, Neg, Sub};
9
10use crate::buffer::Buffer;
11use crate::cast::{Canon, Scalar, with_type};
12use crate::primitive::{ArgReduceOp, BinaryOp, Primitive, ReduceOp, UnaryOp};
13use crate::shape::{self, Shape};
14use crate::view::{self, SliceArg};
15use crate::{Array, Category, DType, Error, Result, at_least_float, promote_types};
16
17fn single(primitive: Primitive, inputs: Vec<Array>) -> Result<Array> {
18 let mut outputs = Array::from_op(primitive, inputs)?;
19 Ok(outputs.swap_remove(0))
20}
21
22fn check_inputs(inputs: &[&Array]) -> Result<()> {
25 match inputs.iter().find_map(|a| a.error()) {
26 Some(err) => Err(err),
27 None => Ok(()),
28 }
29}
30
31fn binary(op: BinaryOp, a: &Array, b: &Array) -> Result<Array> {
34 check_inputs(&[a, b])?;
35 let mut dtype = promote_types(a.dtype(), b.dtype());
36 if op == BinaryOp::Divide {
37 dtype = at_least_float(dtype);
38 }
39 let ordered = matches!(
40 op,
41 BinaryOp::Maximum
42 | BinaryOp::Minimum
43 | BinaryOp::Less
44 | BinaryOp::LessEqual
45 | BinaryOp::Greater
46 | BinaryOp::GreaterEqual
47 );
48 if ordered && dtype == DType::Complex64 {
49 return Err(Error::UnsupportedDType {
50 op: op.name(),
51 dtype,
52 });
53 }
54 let shape = view::broadcast_shapes(op.name(), a.shape(), b.shape())?;
55 let a = broadcast_to(&astype(a, dtype)?, &shape)?;
56 let b = broadcast_to(&astype(b, dtype)?, &shape)?;
57 single(Primitive::Binary(op), vec![a, b])
58}
59
60macro_rules! binary_fn {
61 ($($(#[$doc:meta])* $name:ident => $op:ident;)*) => {$(
62 $(#[$doc])*
63 pub fn $name(a: &Array, b: &Array) -> Result<Array> {
64 binary(BinaryOp::$op, a, b)
65 }
66 )*};
67}
68
69binary_fn! {
70 add => Add;
73 subtract => Subtract;
76 multiply => Multiply;
79 divide => Divide;
81 maximum => Maximum;
83 minimum => Minimum;
85 equal => Equal;
87 not_equal => NotEqual;
89 less => Less;
91 less_equal => LessEqual;
93 greater => Greater;
95 greater_equal => GreaterEqual;
97}
98
99fn unary(op: UnaryOp, a: &Array) -> Result<Array> {
102 check_inputs(&[a])?;
103 let dtype = if op.is_transcendental() {
104 at_least_float(a.dtype())
105 } else {
106 a.dtype()
107 };
108 let unsupported = match op {
109 UnaryOp::Negative => dtype == DType::Bool,
110 UnaryOp::Abs => false,
111 _ => dtype == DType::Complex64,
112 };
113 if unsupported {
114 return Err(Error::UnsupportedDType {
115 op: op.name(),
116 dtype,
117 });
118 }
119 single(Primitive::Unary(op), vec![astype(a, dtype)?])
120}
121
122macro_rules! unary_fn {
123 ($($(#[$doc:meta])* $name:ident => $op:ident;)*) => {$(
124 $(#[$doc])*
125 pub fn $name(a: &Array) -> Result<Array> {
126 unary(UnaryOp::$op, a)
127 }
128 )*};
129}
130
131unary_fn! {
132 negative => Negative;
134 abs => Abs;
136 exp => Exp;
138 log => Log;
140 sqrt => Sqrt;
142 sin => Sin;
144 cos => Cos;
146 tanh => Tanh;
148 sigmoid => Sigmoid;
150}
151
152pub fn where_(condition: &Array, a: &Array, b: &Array) -> Result<Array> {
157 check_inputs(&[condition, a, b])?;
158 let dtype = promote_types(a.dtype(), b.dtype());
159 let shape = view::broadcast_shapes("where", condition.shape(), a.shape())?;
160 let shape = view::broadcast_shapes("where", &shape, b.shape())?;
161 let condition = broadcast_to(&astype(condition, DType::Bool)?, &shape)?;
162 let a = broadcast_to(&astype(a, dtype)?, &shape)?;
163 let b = broadcast_to(&astype(b, dtype)?, &shape)?;
164 single(Primitive::Select, vec![condition, a, b])
165}
166
167pub fn astype(a: &Array, dtype: DType) -> Result<Array> {
170 if a.dtype() == dtype {
171 return Ok(a.clone());
172 }
173 single(Primitive::AsType(dtype), vec![a.clone()])
174}
175
176pub fn reshape(a: &Array, shape: &[isize]) -> Result<Array> {
181 let shape = view::infer_shape(a.size(), shape)?;
182 reshape_to(a, shape)
183}
184
185fn reshape_to(a: &Array, shape: Shape) -> Result<Array> {
186 if a.shape() == shape.as_slice() {
187 return Ok(a.clone());
188 }
189 single(Primitive::Reshape(shape.to_vec()), vec![a.clone()])
190}
191
192pub fn transpose(a: &Array, axes: &[usize]) -> Result<Array> {
194 view::permute(a.shape(), a.strides(), axes)?;
195 if axes.iter().enumerate().all(|(i, &axis)| i == axis) {
196 return Ok(a.clone());
197 }
198 single(Primitive::Transpose(axes.to_vec()), vec![a.clone()])
199}
200
201pub fn slice(a: &Array, args: &[SliceArg]) -> Result<Array> {
204 let dims = view::normalize_slice(a.shape(), args)?;
205 let full = dims
206 .iter()
207 .zip(a.shape())
208 .all(|(d, &n)| d.start == 0 && d.step == 1 && d.len == n);
209 if full {
210 return Ok(a.clone());
211 }
212 let primitive = Primitive::Slice {
213 start: dims.iter().map(|d| d.start).collect(),
214 step: dims.iter().map(|d| d.step).collect(),
215 len: dims.iter().map(|d| d.len).collect(),
216 };
217 single(primitive, vec![a.clone()])
218}
219
220pub fn broadcast_to(a: &Array, shape: &[usize]) -> Result<Array> {
222 if a.shape() == shape {
223 return Ok(a.clone());
224 }
225 single(Primitive::Broadcast(shape.to_vec()), vec![a.clone()])
226}
227
228pub fn expand_dims(a: &Array, axis: isize) -> Result<Array> {
231 let axis = view::normalize_axis(axis, a.ndim() + 1)?;
232 let mut shape: Shape = a.shape().into();
233 shape.insert(axis, 1);
234 reshape_to(a, shape)
235}
236
237pub fn squeeze(a: &Array, axes: &[isize]) -> Result<Array> {
240 let mut remove = vec![axes.is_empty(); a.ndim()];
241 for &axis in axes {
242 let i = view::normalize_axis(axis, a.ndim())?;
243 if a.shape()[i] != 1 {
244 return Err(Error::InvalidArgument(format!(
245 "cannot squeeze axis {axis} of size {} in shape {:?}",
246 a.shape()[i],
247 a.shape()
248 )));
249 }
250 remove[i] = true;
251 }
252 let shape = a
253 .shape()
254 .iter()
255 .zip(&remove)
256 .filter(|&(&d, &r)| !(r && d == 1))
257 .map(|(&d, _)| d)
258 .collect();
259 reshape_to(a, shape)
260}
261
262fn normalize_axes(a: &Array, axes: &[isize]) -> Result<Vec<usize>> {
266 if axes.is_empty() {
267 return Ok((0..a.ndim()).collect());
268 }
269 let mut out = axes
270 .iter()
271 .map(|&axis| view::normalize_axis(axis, a.ndim()))
272 .collect::<Result<Vec<_>>>()?;
273 out.sort_unstable();
274 if out.windows(2).any(|w| w[0] == w[1]) {
275 return Err(Error::InvalidArgument(format!(
276 "duplicate axes in {axes:?}"
277 )));
278 }
279 Ok(out)
280}
281
282fn finish_reduction(out: Array, axes: &[usize], keepdims: bool) -> Result<Array> {
284 if keepdims {
285 return Ok(out);
286 }
287 let shape = out
288 .shape()
289 .iter()
290 .enumerate()
291 .filter(|(d, _)| !axes.contains(d))
292 .map(|(_, &n)| n)
293 .collect();
294 reshape_to(&out, shape)
295}
296
297fn reduction(
298 op: ReduceOp,
299 a: &Array,
300 axes: &[isize],
301 keepdims: bool,
302 dtype: DType,
303) -> Result<Array> {
304 check_inputs(&[a])?;
305 let axes = normalize_axes(a, axes)?;
306 if matches!(op, ReduceOp::Max | ReduceOp::Min) {
307 if a.dtype() == DType::Complex64 {
308 return Err(Error::UnsupportedDType {
309 op: op.name(),
310 dtype: a.dtype(),
311 });
312 }
313 if axes.iter().any(|&d| a.shape()[d] == 0) {
314 return Err(Error::InvalidArgument(format!(
315 "{}: cannot reduce a zero-length axis (no identity)",
316 op.name()
317 )));
318 }
319 }
320 let a = astype(a, dtype)?;
321 if axes.is_empty() {
322 return Ok(a); }
324 let out = single(
325 Primitive::Reduce {
326 op,
327 axes: axes.clone(),
328 },
329 vec![a],
330 )?;
331 finish_reduction(out, &axes, keepdims)
332}
333
334fn widened(dtype: DType) -> DType {
336 match (dtype.category(), dtype.size()) {
337 (Category::Bool, _) => DType::I32,
338 (Category::Signed, 1..=4) => DType::I32,
339 (Category::Signed, _) => DType::I64,
340 (Category::Unsigned, 1..=4) => DType::U32,
341 (Category::Unsigned, _) => DType::U64,
342 _ => dtype,
343 }
344}
345
346pub fn sum(a: &Array, axes: &[isize], keepdims: bool) -> Result<Array> {
349 reduction(ReduceOp::Sum, a, axes, keepdims, widened(a.dtype()))
350}
351
352pub fn prod(a: &Array, axes: &[isize], keepdims: bool) -> Result<Array> {
354 reduction(ReduceOp::Prod, a, axes, keepdims, widened(a.dtype()))
355}
356
357pub fn max(a: &Array, axes: &[isize], keepdims: bool) -> Result<Array> {
359 reduction(ReduceOp::Max, a, axes, keepdims, a.dtype())
360}
361
362pub fn min(a: &Array, axes: &[isize], keepdims: bool) -> Result<Array> {
364 reduction(ReduceOp::Min, a, axes, keepdims, a.dtype())
365}
366
367pub fn all(a: &Array, axes: &[isize], keepdims: bool) -> Result<Array> {
369 reduction(ReduceOp::And, a, axes, keepdims, DType::Bool)
370}
371
372pub fn any(a: &Array, axes: &[isize], keepdims: bool) -> Result<Array> {
374 reduction(ReduceOp::Or, a, axes, keepdims, DType::Bool)
375}
376
377pub fn mean(a: &Array, axes: &[isize], keepdims: bool) -> Result<Array> {
379 let dtype = at_least_float(a.dtype());
380 let count: usize = normalize_axes(a, axes)?
381 .iter()
382 .map(|&d| a.shape()[d])
383 .product();
384 let scale = astype(&Array::scalar(1.0 / count as f64), dtype)?;
385 multiply(&sum(a, axes, keepdims)?, &scale)
386}
387
388fn arg_reduction(op: ArgReduceOp, a: &Array, axis: Option<isize>, keepdims: bool) -> Result<Array> {
389 check_inputs(&[a])?;
390 if a.dtype() == DType::Complex64 {
391 return Err(Error::UnsupportedDType {
392 op: op.name(),
393 dtype: a.dtype(),
394 });
395 }
396 let Some(axis) = axis else {
397 let flat = arg_reduction(op, &reshape(a, &[-1])?, Some(0), false)?;
400 return if keepdims {
401 reshape_to(&flat, smallvec::smallvec![1; a.ndim()])
402 } else {
403 Ok(flat)
404 };
405 };
406 let axis = view::normalize_axis(axis, a.ndim())?;
407 if a.shape()[axis] == 0 {
408 return Err(Error::InvalidArgument(format!(
409 "{}: cannot reduce a zero-length axis",
410 op.name()
411 )));
412 }
413 let out = single(Primitive::ArgReduce { op, axis }, vec![a.clone()])?;
414 finish_reduction(out, &[axis], keepdims)
415}
416
417pub fn argmax(a: &Array, axis: Option<isize>, keepdims: bool) -> Result<Array> {
420 arg_reduction(ArgReduceOp::Max, a, axis, keepdims)
421}
422
423pub fn argmin(a: &Array, axis: Option<isize>, keepdims: bool) -> Result<Array> {
426 arg_reduction(ArgReduceOp::Min, a, axis, keepdims)
427}
428
429pub fn matmul(a: &Array, b: &Array) -> Result<Array> {
436 check_inputs(&[a, b])?;
437 if a.ndim() == 0 || b.ndim() == 0 {
438 return Err(Error::InvalidArgument(
439 "matmul: operands need at least one dimension".into(),
440 ));
441 }
442 let dtype = promote_types(a.dtype(), b.dtype());
443 if !matches!(dtype.category(), Category::Float | Category::Complex) {
444 return Err(Error::UnsupportedDType {
445 op: "matmul",
446 dtype,
447 });
448 }
449 let a2 = if a.ndim() == 1 {
450 expand_dims(a, 0)?
451 } else {
452 a.clone()
453 };
454 let b2 = if b.ndim() == 1 {
455 expand_dims(b, -1)?
456 } else {
457 b.clone()
458 };
459 let (sa, sb) = (a2.shape(), b2.shape());
460 if sa[sa.len() - 1] != sb[sb.len() - 2] {
461 return Err(Error::ShapeMismatch {
462 op: "matmul",
463 lhs: a.shape().to_vec(),
464 rhs: b.shape().to_vec(),
465 });
466 }
467 let (a2, b2) = (astype(&a2, dtype)?, astype(&b2, dtype)?);
468 let out = if a2.ndim() > 2 && b2.ndim() == 2 {
469 let k = a2.shape()[a2.ndim() - 1];
471 let rows = a2
472 .size()
473 .checked_div(k)
474 .unwrap_or_else(|| a2.shape()[..a2.ndim() - 1].iter().product());
475 let flat = single(
476 Primitive::Matmul,
477 vec![reshape_to(&a2, smallvec::smallvec![rows, k])?, b2.clone()],
478 )?;
479 let mut shape: Shape = a2.shape().into();
480 *shape.last_mut().expect("ndim > 2") = b2.shape()[1];
481 reshape_to(&flat, shape)?
482 } else {
483 let (na, nb) = (a2.ndim(), b2.ndim());
484 let batch = view::broadcast_shapes("matmul", &a2.shape()[..na - 2], &b2.shape()[..nb - 2])?;
485 let full = |x: &Array, nd: usize| -> Result<Array> {
486 let mut shape = batch.clone();
487 shape.extend_from_slice(&x.shape()[nd - 2..]);
488 broadcast_to(x, &shape)
489 };
490 single(Primitive::Matmul, vec![full(&a2, na)?, full(&b2, nb)?])?
491 };
492 let mut drop = Vec::new();
494 if a.ndim() == 1 {
495 drop.push(out.ndim() as isize - 2);
496 }
497 if b.ndim() == 1 {
498 drop.push(-1);
499 }
500 if drop.is_empty() {
501 Ok(out)
502 } else {
503 squeeze(&out, &drop)
504 }
505}
506
507pub fn zeros(shape: &[usize], dtype: DType) -> Result<Array> {
511 let buffer = Buffer::try_zeroed(shape::checked_nbytes(shape, dtype.size())?)?;
512 Array::from_buffer(buffer, dtype, shape)
513}
514
515pub fn ones(shape: &[usize], dtype: DType) -> Result<Array> {
517 filled(shape, dtype, |_| Canon::Int(1))
518}
519
520pub fn arange(start: f64, stop: f64, step: f64, dtype: DType) -> Result<Array> {
523 let invalid = |msg: &str| Err(Error::InvalidArgument(format!("arange: {msg}")));
524 if dtype == DType::Bool {
525 return Err(Error::UnsupportedDType {
526 op: "arange",
527 dtype,
528 });
529 }
530 if start.is_nan() || stop.is_nan() || step.is_nan() || start.is_infinite() || stop.is_infinite()
531 {
532 return invalid("cannot compute the length from NaN or infinite bounds");
533 }
534 if step == 0.0 {
535 return invalid("step must be non-zero");
536 }
537 let len = if step.is_infinite() {
538 usize::from((step > 0.0 && start < stop) || (step < 0.0 && start > stop))
539 } else {
540 let real = ((stop - start) / step).ceil();
541 if real > i32::MAX as f64 {
542 return invalid("maximum size exceeded");
543 }
544 real.max(0.0) as usize
545 };
546 filled(&[len], dtype, |i| {
548 Canon::Float(if i == 0 {
549 start
550 } else {
551 start + i as f64 * step
552 })
553 })
554}
555
556fn filled(shape: &[usize], dtype: DType, value: impl Fn(usize) -> Canon) -> Result<Array> {
558 let mut buffer = Buffer::try_alloc(shape::checked_nbytes(shape, dtype.size())?)?;
559 with_type!(dtype, T => {
560 for (i, x) in buffer.typed_mut::<T>().iter_mut().enumerate() {
561 *x = T::from_canon(value(i));
562 }
563 });
564 Array::from_buffer(buffer, dtype, shape)
565}
566
567impl Array {
570 pub fn astype(&self, dtype: DType) -> Result<Array> {
572 astype(self, dtype)
573 }
574
575 pub fn reshape(&self, shape: &[isize]) -> Result<Array> {
577 reshape(self, shape)
578 }
579
580 pub fn t(&self) -> Result<Array> {
582 let axes: Vec<usize> = (0..self.ndim()).rev().collect();
583 transpose(self, &axes)
584 }
585}
586
587fn or_failed(result: Result<Array>, lhs: &Array) -> Array {
592 result.unwrap_or_else(|err| Array::failed(err, lhs.dtype(), lhs.shape()))
593}
594
595macro_rules! operator {
596 ($trait:ident, $method:ident, $op:ident) => {
597 impl $trait<&Array> for &Array {
598 type Output = Array;
599 fn $method(self, rhs: &Array) -> Array {
600 or_failed($op(self, rhs), self)
601 }
602 }
603 impl $trait<Array> for &Array {
604 type Output = Array;
605 fn $method(self, rhs: Array) -> Array {
606 or_failed($op(self, &rhs), self)
607 }
608 }
609 impl $trait<&Array> for Array {
610 type Output = Array;
611 fn $method(self, rhs: &Array) -> Array {
612 or_failed($op(&self, rhs), &self)
613 }
614 }
615 impl $trait<Array> for Array {
616 type Output = Array;
617 fn $method(self, rhs: Array) -> Array {
618 or_failed($op(&self, &rhs), &self)
619 }
620 }
621 };
622}
623
624operator!(Add, add, add);
625operator!(Sub, sub, subtract);
626operator!(Mul, mul, multiply);
627operator!(Div, div, divide);
628
629impl Neg for &Array {
630 type Output = Array;
631 fn neg(self) -> Array {
632 or_failed(negative(self), self)
633 }
634}
635
636impl Neg for Array {
637 type Output = Array;
638 fn neg(self) -> Array {
639 or_failed(negative(&self), &self)
640 }
641}
642
643#[cfg(test)]
644mod tests {
645 use super::*;
646 use crate::buffer::live;
647
648 fn allocations(f: impl FnOnce() -> Array) -> isize {
650 let before = live::count();
651 let out = f();
652 out.eval().unwrap();
653 live::count() - before
654 }
655
656 #[test]
657 fn views_share_data() {
658 let base = arange(0.0, 24.0, 1.0, DType::F32)
659 .unwrap()
660 .reshape(&[2, 3, 4])
661 .unwrap();
662 base.eval().unwrap();
663 assert_eq!(allocations(|| base.reshape(&[6, 4]).unwrap()), 0);
664 assert_eq!(allocations(|| base.t().unwrap()), 0);
665 assert_eq!(
666 allocations(|| slice(
667 &base,
668 &[(..).into(), SliceArg::new(None, None, -2), (1..3).into()]
669 )
670 .unwrap()),
671 0
672 );
673 assert_eq!(
674 allocations(|| broadcast_to(&base, &[5, 2, 3, 4]).unwrap()),
675 0
676 );
677 assert_eq!(allocations(|| expand_dims(&base, 1).unwrap()), 0);
678 assert_eq!(
679 allocations(|| squeeze(&expand_dims(&base, -1).unwrap(), &[]).unwrap()),
680 0
681 );
682 assert_eq!(allocations(|| base.t().unwrap().reshape(&[-1]).unwrap()), 1);
684 }
685
686 #[test]
687 fn noop_views_record_nothing() {
688 let a = Array::from_slice(&[1u8, 2, 3, 4], &[2, 2]).unwrap();
689 assert!(a.reshape(&[2, 2]).unwrap().primitive().is_none());
690 assert!(transpose(&a, &[0, 1]).unwrap().primitive().is_none());
691 assert!(
692 slice(&a, &[SliceArg::full(), SliceArg::full()])
693 .unwrap()
694 .primitive()
695 .is_none()
696 );
697 assert!(astype(&a, DType::U8).unwrap().primitive().is_none());
698 assert!(broadcast_to(&a, &[2, 2]).unwrap().primitive().is_none());
699 }
700
701 #[test]
702 fn view_flags() {
703 let a = zeros(&[2, 3], DType::F32).unwrap();
704 let t = a.t().unwrap();
705 assert!(t.flags().col_contiguous && !t.flags().row_contiguous);
706 assert_eq!(t.strides(), &[1, 3]);
707 let b = broadcast_to(&a, &[4, 2, 3]).unwrap();
708 assert_eq!(b.strides(), &[0, 3, 1]);
709 assert!(!b.flags().contiguous);
710 }
711}