use crate::{
dtype::Dtype,
error::{AsSliceError, ConversionError},
sealed::Sealed,
utils::{guard::Guarded, SUCCESS},
Stream,
};
use element::FromSliceElement;
use mlx_internal_macros::default_device;
use mlx_sys::mlx_array;
use num_complex::Complex;
use std::{
ffi::{c_void, CStr},
iter::Sum,
};
mod element;
mod operators;
cfg_safetensors! {
mod safetensors;
}
pub use element::ArrayElement;
#[allow(non_camel_case_types)]
pub type complex64 = Complex<f32>;
#[repr(transparent)]
pub struct Array {
c_array: mlx_array,
}
impl Sealed for Array {}
impl Sealed for &Array {}
impl std::fmt::Debug for Array {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self}")
}
}
impl std::fmt::Display for Array {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
unsafe {
let mut mlx_str = mlx_sys::mlx_string_new();
let status = mlx_sys::mlx_array_tostring(&mut mlx_str as *mut _, self.as_ptr());
if status != SUCCESS {
return Err(std::fmt::Error);
}
let ptr = mlx_sys::mlx_string_data(mlx_str);
let c_str = CStr::from_ptr(ptr);
write!(f, "{}", c_str.to_str().map_err(|_| std::fmt::Error)?)?;
mlx_sys::mlx_string_free(mlx_str);
Ok(())
}
}
}
impl Drop for Array {
fn drop(&mut self) {
unsafe { mlx_sys::mlx_array_free(self.as_ptr()) };
}
}
unsafe impl Send for Array {}
impl Array {
pub unsafe fn from_ptr(c_array: mlx_array) -> Array {
Self { c_array }
}
pub fn as_ptr(&self) -> mlx_array {
self.c_array
}
pub fn from_bool(val: bool) -> Array {
let c_array = unsafe { mlx_sys::mlx_array_new_bool(val) };
Array { c_array }
}
pub fn from_int(val: i32) -> Array {
let c_array = unsafe { mlx_sys::mlx_array_new_int(val) };
Array { c_array }
}
pub fn from_f32(val: f32) -> Array {
let c_array = unsafe { mlx_sys::mlx_array_new_float32(val) };
Array { c_array }
}
pub fn from_f64(val: f64) -> Array {
let c_array = unsafe { mlx_sys::mlx_array_new_float64(val) };
Array { c_array }
}
pub fn from_complex(val: complex64) -> Array {
let c_array = unsafe { mlx_sys::mlx_array_new_complex(val.re, val.im) };
Array { c_array }
}
pub fn from_slice<T: FromSliceElement>(data: &[T], shape: &[i32]) -> Self {
assert_eq!(data.len(), shape.iter().product::<i32>() as usize);
unsafe { Self::from_raw_data(data.as_ptr() as *const c_void, shape, T::DTYPE) }
}
pub fn from_slice_f64(data: &[f64], shape: &[i32]) -> Self {
assert_eq!(data.len(), shape.iter().product::<i32>() as usize);
unsafe { Self::from_raw_data(data.as_ptr() as *const c_void, shape, Dtype::Float64) }
}
#[inline]
pub unsafe fn from_raw_data(data: *const c_void, shape: &[i32], dtype: Dtype) -> Self {
let dim = if shape.len() > i32::MAX as usize {
panic!("Shape is too large")
} else {
shape.len() as i32
};
let c_array = mlx_sys::mlx_array_new_data(data, shape.as_ptr(), dim, dtype.into());
Array { c_array }
}
pub fn from_iter<I: IntoIterator<Item = T>, T: FromSliceElement>(
iter: I,
shape: &[i32],
) -> Self {
let data: Vec<T> = iter.into_iter().collect();
Self::from_slice(&data, shape)
}
pub fn from_iter_f64<I: IntoIterator<Item = f64>>(iter: I, shape: &[i32]) -> Self {
let data: Vec<f64> = iter.into_iter().collect();
Self::from_slice_f64(&data, shape)
}
pub fn item_size(&self) -> usize {
unsafe { mlx_sys::mlx_array_itemsize(self.as_ptr()) }
}
pub fn size(&self) -> usize {
unsafe { mlx_sys::mlx_array_size(self.as_ptr()) }
}
pub fn strides(&self) -> &[usize] {
let ndim = self.ndim();
if ndim == 0 {
return &[];
}
unsafe {
let data = mlx_sys::mlx_array_strides(self.as_ptr());
std::slice::from_raw_parts(data, ndim)
}
}
pub fn nbytes(&self) -> usize {
unsafe { mlx_sys::mlx_array_nbytes(self.as_ptr()) }
}
pub fn ndim(&self) -> usize {
unsafe { mlx_sys::mlx_array_ndim(self.as_ptr()) }
}
pub fn shape(&self) -> &[i32] {
let ndim = self.ndim();
if ndim == 0 {
return &[];
}
unsafe {
let data = mlx_sys::mlx_array_shape(self.as_ptr());
std::slice::from_raw_parts(data, ndim)
}
}
pub fn dim(&self, dim: i32) -> i32 {
let dim = if dim.is_negative() {
(self.ndim() as i32).checked_add(dim).unwrap()
} else {
dim
};
unsafe { mlx_sys::mlx_array_dim(self.as_ptr(), dim) }
}
pub fn dtype(&self) -> Dtype {
let dtype = unsafe { mlx_sys::mlx_array_dtype(self.as_ptr()) };
Dtype::try_from(dtype).unwrap()
}
pub fn eval(&self) -> crate::error::Result<()> {
<() as Guarded>::try_from_op(|_| unsafe { mlx_sys::mlx_array_eval(self.as_ptr()) })
}
pub fn item_exact<T: ArrayElement>(&self) -> T {
self.try_item_exact().unwrap()
}
pub fn try_item_exact<T: ArrayElement>(&self) -> Result<T, ConversionError> {
if self.size() != 1 {
return Err(ConversionError::NotScalar {
actual: self.size(),
});
}
if self.dtype() != T::DTYPE {
return Err(ConversionError::DtypeMismatch {
expected: T::DTYPE,
actual: self.dtype(),
});
}
self.eval()?;
Ok(T::array_item(self)?)
}
pub fn item_cast<T: ArrayElement>(&self) -> T {
self.try_item_cast().unwrap()
}
pub fn try_item_cast<T: ArrayElement>(&self) -> Result<T, ConversionError> {
if self.size() != 1 {
return Err(ConversionError::NotScalar {
actual: self.size(),
});
}
self.eval()?;
if self.dtype() != T::DTYPE {
return self.as_type::<T>()?.try_item_exact();
}
Ok(T::array_item(self)?)
}
#[deprecated(since = "0.26.0", note = "use `item_cast` or `item_exact`")]
pub fn item<T: ArrayElement>(&self) -> T {
self.item_cast()
}
#[deprecated(since = "0.26.0", note = "use `try_item_cast` or `try_item_exact`")]
pub fn try_item<T: ArrayElement>(&self) -> crate::error::Result<T> {
self.try_item_cast().map_err(|error| match error {
ConversionError::Exception(error) => error,
error => crate::error::Exception::custom(error.to_string()),
})
}
pub fn to_vec_exact<T: ArrayElement + Clone>(&self) -> Result<Vec<T>, ConversionError> {
if self.dtype() != T::DTYPE {
return Err(ConversionError::DtypeMismatch {
expected: T::DTYPE,
actual: self.dtype(),
});
}
Ok(self.try_as_slice::<T>()?.to_vec())
}
pub fn to_vec_cast<T: ArrayElement + Clone>(&self) -> Result<Vec<T>, ConversionError> {
if self.dtype() == T::DTYPE {
return self.to_vec_exact();
}
let converted = self.as_type::<T>()?;
Ok(converted.try_as_slice::<T>()?.to_vec())
}
pub unsafe fn as_slice_unchecked<T: ArrayElement>(&self) -> &[T] {
self.eval().unwrap();
unsafe {
let data = T::array_data(self);
let size = self.size();
std::slice::from_raw_parts(data, size)
}
}
pub fn try_as_slice<T: ArrayElement>(&self) -> Result<&[T], AsSliceError> {
if self.dtype() != T::DTYPE {
return Err(AsSliceError::DtypeMismatch {
expecting: T::DTYPE,
found: self.dtype(),
});
}
self.eval()?;
let row_contiguous = bool::try_from_op(|res| unsafe {
mlx_sys::_mlx_array_is_row_contiguous(res, self.as_ptr())
})?;
if !row_contiguous {
return Err(AsSliceError::NotContiguous);
}
unsafe {
let size = self.size();
let data = T::array_data(self);
if data.is_null() || size == 0 {
return Err(AsSliceError::Null);
}
Ok(std::slice::from_raw_parts(data, size))
}
}
pub fn as_slice<T: ArrayElement>(&self) -> &[T] {
self.try_as_slice().unwrap()
}
pub fn deep_clone(&self) -> Self {
unsafe {
let dtype = self.dtype();
let shape = self.shape();
let data = match dtype {
Dtype::Bool => mlx_sys::mlx_array_data_bool(self.as_ptr()) as *const c_void,
Dtype::Uint8 => mlx_sys::mlx_array_data_uint8(self.as_ptr()) as *const c_void,
Dtype::Uint16 => mlx_sys::mlx_array_data_uint16(self.as_ptr()) as *const c_void,
Dtype::Uint32 => mlx_sys::mlx_array_data_uint32(self.as_ptr()) as *const c_void,
Dtype::Uint64 => mlx_sys::mlx_array_data_uint64(self.as_ptr()) as *const c_void,
Dtype::Int8 => mlx_sys::mlx_array_data_int8(self.as_ptr()) as *const c_void,
Dtype::Int16 => mlx_sys::mlx_array_data_int16(self.as_ptr()) as *const c_void,
Dtype::Int32 => mlx_sys::mlx_array_data_int32(self.as_ptr()) as *const c_void,
Dtype::Int64 => mlx_sys::mlx_array_data_int64(self.as_ptr()) as *const c_void,
Dtype::Float16 => mlx_sys::mlx_array_data_float16(self.as_ptr()) as *const c_void,
Dtype::Float32 => mlx_sys::mlx_array_data_float32(self.as_ptr()) as *const c_void,
Dtype::Float64 => mlx_sys::mlx_array_data_float64(self.as_ptr()) as *const c_void,
Dtype::Bfloat16 => mlx_sys::mlx_array_data_bfloat16(self.as_ptr()) as *const c_void,
Dtype::Complex64 => {
mlx_sys::mlx_array_data_complex64(self.as_ptr()) as *const c_void
}
};
let new_c_array =
mlx_sys::mlx_array_new_data(data, shape.as_ptr(), shape.len() as i32, dtype.into());
Array::from_ptr(new_c_array)
}
}
}
impl Clone for Array {
fn clone(&self) -> Self {
Array::try_from_op(|res| unsafe { mlx_sys::mlx_array_set(res, self.as_ptr()) })
.expect("Failed to clone array")
}
}
impl Sum for Array {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
iter.fold(Array::from_int(0), |acc, x| acc.add(&x).unwrap())
}
}
#[default_device]
pub fn stop_gradient_device(
a: impl AsRef<Array>,
stream: impl AsRef<Stream>,
) -> crate::error::Result<Array> {
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_stop_gradient(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
})
}
impl From<bool> for Array {
fn from(value: bool) -> Self {
Array::from_bool(value)
}
}
impl From<i32> for Array {
fn from(value: i32) -> Self {
Array::from_int(value)
}
}
impl From<f32> for Array {
fn from(value: f32) -> Self {
Array::from_f32(value)
}
}
impl From<complex64> for Array {
fn from(value: complex64) -> Self {
Array::from_complex(value)
}
}
impl<T> From<T> for Array
where
Array: FromNested<T>,
{
fn from(value: T) -> Self {
Array::from_nested(value)
}
}
impl AsRef<Array> for Array {
fn as_ref(&self) -> &Array {
self
}
}
pub trait FromScalar<T>
where
T: ArrayElement,
{
fn from_scalar(val: T) -> Array;
}
impl FromScalar<bool> for Array {
fn from_scalar(val: bool) -> Array {
Array::from_bool(val)
}
}
impl FromScalar<i32> for Array {
fn from_scalar(val: i32) -> Array {
Array::from_int(val)
}
}
impl FromScalar<f32> for Array {
fn from_scalar(val: f32) -> Array {
Array::from_f32(val)
}
}
impl FromScalar<complex64> for Array {
fn from_scalar(val: complex64) -> Array {
Array::from_complex(val)
}
}
pub trait FromNested<T> {
fn from_nested(data: T) -> Array;
}
impl<T: FromSliceElement> FromNested<&[T]> for Array {
fn from_nested(data: &[T]) -> Self {
Array::from_slice(data, &[data.len() as i32])
}
}
impl<T: FromSliceElement, const N: usize> FromNested<[T; N]> for Array {
fn from_nested(data: [T; N]) -> Self {
Array::from_slice(&data, &[N as i32])
}
}
impl<T: FromSliceElement, const N: usize> FromNested<&[T; N]> for Array {
fn from_nested(data: &[T; N]) -> Self {
Array::from_slice(data, &[N as i32])
}
}
impl<T: FromSliceElement + Copy> FromNested<&[&[T]]> for Array {
fn from_nested(data: &[&[T]]) -> Self {
let row_len = data[0].len();
assert!(
data.iter().all(|row| row.len() == row_len),
"Rows must have the same length"
);
let shape = [data.len() as i32, row_len as i32];
let data = data
.iter()
.flat_map(|x| x.iter())
.copied()
.collect::<Vec<T>>();
Array::from_slice(&data, &shape)
}
}
impl<T: FromSliceElement + Copy, const N: usize> FromNested<[&[T]; N]> for Array {
fn from_nested(data: [&[T]; N]) -> Self {
let row_len = data[0].len();
assert!(
data.iter().all(|row| row.len() == row_len),
"Rows must have the same length"
);
let shape = [N as i32, row_len as i32];
let data = data
.iter()
.flat_map(|x| x.iter())
.copied()
.collect::<Vec<T>>();
Array::from_slice(&data, &shape)
}
}
impl<T: FromSliceElement + Copy, const N: usize> FromNested<&[[T; N]]> for Array {
fn from_nested(data: &[[T; N]]) -> Self {
let shape = [data.len() as i32, N as i32];
let data = data
.iter()
.flat_map(|x| x.iter().copied())
.collect::<Vec<T>>();
Array::from_slice(&data, &shape)
}
}
impl<T: FromSliceElement + Copy, const N: usize> FromNested<&[&[T; N]]> for Array {
fn from_nested(data: &[&[T; N]]) -> Self {
let shape = [data.len() as i32, N as i32];
let data = data
.iter()
.flat_map(|x| x.iter().copied())
.collect::<Vec<T>>();
Array::from_slice(&data, &shape)
}
}
impl<T: FromSliceElement + Copy, const N: usize, const M: usize> FromNested<[[T; N]; M]> for Array {
fn from_nested(data: [[T; N]; M]) -> Self {
let shape = [M as i32, N as i32];
let data = data
.iter()
.flat_map(|x| x.iter().copied())
.collect::<Vec<T>>();
Array::from_slice(&data, &shape)
}
}
impl<T: FromSliceElement + Copy, const N: usize, const M: usize> FromNested<&[[T; N]; M]>
for Array
{
fn from_nested(data: &[[T; N]; M]) -> Self {
let shape = [M as i32, N as i32];
let data = data
.iter()
.flat_map(|x| x.iter().copied())
.collect::<Vec<T>>();
Array::from_slice(&data, &shape)
}
}
impl<T: FromSliceElement + Copy, const N: usize, const M: usize> FromNested<&[&[T; N]; M]>
for Array
{
fn from_nested(data: &[&[T; N]; M]) -> Self {
let shape = [M as i32, N as i32];
let data = data
.iter()
.flat_map(|x| x.iter().copied())
.collect::<Vec<T>>();
Array::from_slice(&data, &shape)
}
}
impl<T: FromSliceElement + Copy> FromNested<&[&[&[T]]]> for Array {
fn from_nested(data: &[&[&[T]]]) -> Self {
let len_2d = data[0].len();
assert!(
data.iter().all(|x| x.len() == len_2d),
"2nd dimension must have the same length"
);
let len_3d = data[0][0].len();
assert!(
data.iter().all(|x| x.iter().all(|y| y.len() == len_3d)),
"3rd dimension must have the same length"
);
let shape = [data.len() as i32, len_2d as i32, len_3d as i32];
let data = data
.iter()
.flat_map(|x| x.iter().flat_map(|y| y.iter().copied()))
.collect::<Vec<T>>();
Array::from_slice(&data, &shape)
}
}
impl<T: FromSliceElement + Copy, const N: usize> FromNested<[&[&[T]]; N]> for Array {
fn from_nested(data: [&[&[T]]; N]) -> Self {
let len_2d = data[0].len();
assert!(
data.iter().all(|x| x.len() == len_2d),
"2nd dimension must have the same length"
);
let len_3d = data[0][0].len();
assert!(
data.iter().all(|x| x.iter().all(|y| y.len() == len_3d)),
"3rd dimension must have the same length"
);
let shape = [N as i32, len_2d as i32, len_3d as i32];
let data = data
.iter()
.flat_map(|x| x.iter().flat_map(|y| y.iter().copied()))
.collect::<Vec<T>>();
Array::from_slice(&data, &shape)
}
}
impl<T: FromSliceElement + Copy, const N: usize> FromNested<&[[&[T]; N]]> for Array {
fn from_nested(data: &[[&[T]; N]]) -> Self {
let len_3d = data[0][0].len();
assert!(
data.iter().all(|x| x.iter().all(|y| y.len() == len_3d)),
"3rd dimension must have the same length"
);
let shape = [data.len() as i32, N as i32, len_3d as i32];
let data = data
.iter()
.flat_map(|x| x.iter().flat_map(|y| y.iter().copied()))
.collect::<Vec<T>>();
Array::from_slice(&data, &shape)
}
}
impl<T: FromSliceElement + Copy, const N: usize> FromNested<&[&[[T; N]]]> for Array {
fn from_nested(data: &[&[[T; N]]]) -> Self {
let len_2d = data[0].len();
assert!(
data.iter().all(|x| x.len() == len_2d),
"2nd dimension must have the same length"
);
let shape = [data.len() as i32, len_2d as i32, N as i32];
let data = data
.iter()
.flat_map(|x| x.iter().flat_map(|y| y.iter().copied()))
.collect::<Vec<T>>();
Array::from_slice(&data, &shape)
}
}
impl<T: FromSliceElement + Copy, const N: usize, const M: usize> FromNested<[[&[T]; N]; M]>
for Array
{
fn from_nested(data: [[&[T]; N]; M]) -> Self {
let len_3d = data[0][0].len();
assert!(
data.iter().all(|x| x.iter().all(|y| y.len() == len_3d)),
"3rd dimension must have the same length"
);
let shape = [M as i32, N as i32, len_3d as i32];
let data = data
.iter()
.flat_map(|x| x.iter().flat_map(|y| y.iter().copied()))
.collect::<Vec<T>>();
Array::from_slice(&data, &shape)
}
}
impl<T: FromSliceElement + Copy, const N: usize, const M: usize> FromNested<&[[&[T]; N]; M]>
for Array
{
fn from_nested(data: &[[&[T]; N]; M]) -> Self {
let len_3d = data[0][0].len();
assert!(
data.iter().all(|x| x.iter().all(|y| y.len() == len_3d)),
"3rd dimension must have the same length"
);
let shape = [M as i32, N as i32, len_3d as i32];
let data = data
.iter()
.flat_map(|x| x.iter().flat_map(|y| y.iter().copied()))
.collect::<Vec<T>>();
Array::from_slice(&data, &shape)
}
}
impl<T: FromSliceElement + Copy, const N: usize, const M: usize> FromNested<&[&[[T; N]]; M]>
for Array
{
fn from_nested(data: &[&[[T; N]]; M]) -> Self {
let len_2d = data[0].len();
assert!(
data.iter().all(|x| x.len() == len_2d),
"2nd dimension must have the same length"
);
let shape = [M as i32, len_2d as i32, N as i32];
let data = data
.iter()
.flat_map(|x| x.iter().flat_map(|y| y.iter().copied()))
.collect::<Vec<T>>();
Array::from_slice(&data, &shape)
}
}
impl<T: FromSliceElement + Copy, const N: usize, const M: usize, const O: usize>
FromNested<[[[T; N]; M]; O]> for Array
{
fn from_nested(data: [[[T; N]; M]; O]) -> Self {
let shape = [O as i32, M as i32, N as i32];
let data = data
.iter()
.flat_map(|x| x.iter().flat_map(|y| y.iter().copied()))
.collect::<Vec<T>>();
Array::from_slice(&data, &shape)
}
}
impl<T: FromSliceElement + Copy, const N: usize, const M: usize, const O: usize>
FromNested<&[[[T; N]; M]; O]> for Array
{
fn from_nested(data: &[[[T; N]; M]; O]) -> Self {
let shape = [O as i32, M as i32, N as i32];
let data = data
.iter()
.flat_map(|x| x.iter().flat_map(|y| y.iter().copied()))
.collect::<Vec<T>>();
Array::from_slice(&data, &shape)
}
}
impl<T: FromSliceElement + Copy, const N: usize, const M: usize, const O: usize>
FromNested<&[&[[T; N]; M]; O]> for Array
{
fn from_nested(data: &[&[[T; N]; M]; O]) -> Self {
let shape = [O as i32, M as i32, N as i32];
let data = data
.iter()
.flat_map(|x| x.iter().flat_map(|y| y.iter().copied()))
.collect::<Vec<T>>();
Array::from_slice(&data, &shape)
}
}
impl<T: FromSliceElement + Copy, const N: usize, const M: usize, const O: usize>
FromNested<&[[&[T; N]; M]; O]> for Array
{
fn from_nested(data: &[[&[T; N]; M]; O]) -> Self {
let shape = [O as i32, M as i32, N as i32];
let data = data
.iter()
.flat_map(|x| x.iter().flat_map(|y| y.iter().copied()))
.collect::<Vec<T>>();
Array::from_slice(&data, &shape)
}
}
impl<T: FromSliceElement + Copy, const N: usize, const M: usize, const O: usize>
FromNested<&[&[&[T; N]; M]; O]> for Array
{
fn from_nested(data: &[&[&[T; N]; M]; O]) -> Self {
let shape = [O as i32, M as i32, N as i32];
let data = data
.iter()
.flat_map(|x| x.iter().flat_map(|y| y.iter().copied()))
.collect::<Vec<T>>();
Array::from_slice(&data, &shape)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{array, ops::broadcast_to};
#[test]
fn broadcast_view_cannot_be_borrowed_as_slice() {
let broadcast = broadcast_to(&array!([1, 2]), &[3, 2]).unwrap();
assert_eq!(
broadcast.try_as_slice::<i32>(),
Err(AsSliceError::NotContiguous)
);
}
#[test]
fn new_scalar_array_from_bool() {
let array = Array::from_bool(true);
assert!(array.item_exact::<bool>());
assert_eq!(array.item_size(), 1);
assert_eq!(array.size(), 1);
assert!(array.strides().is_empty());
assert_eq!(array.nbytes(), 1);
assert_eq!(array.ndim(), 0);
assert!(array.shape().is_empty());
assert_eq!(array.dtype(), Dtype::Bool);
}
#[test]
fn new_scalar_array_from_int() {
let array = Array::from_int(42);
assert_eq!(array.item_exact::<i32>(), 42);
assert_eq!(array.item_size(), 4);
assert_eq!(array.size(), 1);
assert!(array.strides().is_empty());
assert_eq!(array.nbytes(), 4);
assert_eq!(array.ndim(), 0);
assert!(array.shape().is_empty());
assert_eq!(array.dtype(), Dtype::Int32);
}
#[test]
fn new_scalar_array_from_f32() {
let array = Array::from_f32(3.14);
assert_eq!(array.item_exact::<f32>(), 3.14);
assert_eq!(array.item_size(), 4);
assert_eq!(array.size(), 1);
assert!(array.strides().is_empty());
assert_eq!(array.nbytes(), 4);
assert_eq!(array.ndim(), 0);
assert!(array.shape().is_empty());
assert_eq!(array.dtype(), Dtype::Float32);
}
#[test]
fn new_scalar_array_from_f64() {
let array = Array::from_f64(3.14).as_dtype(Dtype::Float64).unwrap();
float_eq::assert_float_eq!(array.item_exact::<f64>(), 3.14, abs <= 1e-5);
assert_eq!(array.item_size(), 8);
assert_eq!(array.size(), 1);
assert!(array.strides().is_empty());
assert_eq!(array.nbytes(), 8);
assert_eq!(array.ndim(), 0);
assert!(array.shape().is_empty());
assert_eq!(array.dtype(), Dtype::Float64);
}
#[test]
fn new_array_from_slice_f64() {
let array = Array::from_slice_f64(&[1.0, 2.0, 3.0], &[3]);
assert_eq!(array.item_size(), 8);
assert_eq!(array.size(), 3);
assert_eq!(array.strides(), &[1]);
assert_eq!(array.nbytes(), 24);
assert_eq!(array.ndim(), 1);
assert_eq!(array.dim(0), 3);
assert_eq!(array.shape(), &[3]);
assert_eq!(array.dtype(), Dtype::Float64);
}
#[test]
fn new_scalar_array_from_complex() {
let val = complex64::new(1.0, 2.0);
let array = Array::from_complex(val);
assert_eq!(array.item_exact::<complex64>(), val);
assert_eq!(array.item_size(), 8);
assert_eq!(array.size(), 1);
assert!(array.strides().is_empty());
assert_eq!(array.nbytes(), 8);
assert_eq!(array.ndim(), 0);
assert!(array.shape().is_empty());
assert_eq!(array.dtype(), Dtype::Complex64);
}
#[test]
fn new_array_from_single_element_slice() {
let data = [1i32];
let array = Array::from_slice(&data, &[1]);
assert_eq!(array.as_slice::<i32>(), &data[..]);
assert_eq!(array.item_exact::<i32>(), 1);
assert_eq!(array.item_size(), 4);
assert_eq!(array.size(), 1);
assert_eq!(array.strides(), &[1]);
assert_eq!(array.nbytes(), 4);
assert_eq!(array.ndim(), 1);
assert_eq!(array.dim(0), 1);
assert_eq!(array.shape(), &[1]);
assert_eq!(array.dtype(), Dtype::Int32);
}
#[test]
fn new_array_from_multi_element_slice() {
let data = [1i32, 2, 3, 4, 5];
let array = Array::from_slice(&data, &[5]);
assert_eq!(array.as_slice::<i32>(), &data[..]);
assert_eq!(array.item_size(), 4);
assert_eq!(array.size(), 5);
assert_eq!(array.strides(), &[1]);
assert_eq!(array.nbytes(), 20);
assert_eq!(array.ndim(), 1);
assert_eq!(array.dim(0), 5);
assert_eq!(array.shape(), &[5]);
assert_eq!(array.dtype(), Dtype::Int32);
}
#[test]
fn new_2d_array_from_slice() {
let data = [1i32, 2, 3, 4, 5, 6];
let array = Array::from_slice(&data, &[2, 3]);
assert_eq!(array.as_slice::<i32>(), &data[..]);
assert_eq!(array.item_size(), 4);
assert_eq!(array.size(), 6);
assert_eq!(array.strides(), &[3, 1]);
assert_eq!(array.nbytes(), 24);
assert_eq!(array.ndim(), 2);
assert_eq!(array.dim(0), 2);
assert_eq!(array.dim(1), 3);
assert_eq!(array.dim(-1), 3); assert_eq!(array.dim(-2), 2); assert_eq!(array.shape(), &[2, 3]);
assert_eq!(array.dtype(), Dtype::Int32);
}
#[test]
fn deep_cloned_array_has_different_ptr() {
let data = [1i32, 2, 3, 4, 5];
let orig = Array::from_slice(&data, &[5]);
let clone = orig.deep_clone();
assert_eq!(orig.as_slice::<i32>(), clone.as_slice::<i32>());
assert_ne!(orig.as_ptr().ctx, clone.as_ptr().ctx);
assert_ne!(
orig.as_slice::<i32>().as_ptr(),
clone.as_slice::<i32>().as_ptr()
);
}
#[test]
fn test_array_eq() {
let data = [1i32, 2, 3, 4, 5];
let array1 = Array::from_slice(&data, &[5]);
let array2 = Array::from_slice(&data, &[5]);
let array3 = Array::from_slice(&[1i32, 2, 3, 4, 6], &[5]);
assert!(array1.eq_exact(&array2).unwrap());
assert!(!array1.eq_exact(&array3).unwrap());
}
#[test]
fn test_array_item_non_scalar() {
let data = [1i32, 2, 3, 4, 5];
let array = Array::from_slice(&data, &[5]);
assert!(array.try_item_exact::<i32>().is_err());
}
#[test]
fn test_item_type_conversion() {
let array = Array::from_f32(1.0);
assert_eq!(array.item_cast::<i32>(), 1);
assert_eq!(array.item_cast::<complex64>(), complex64::new(1.0, 0.0));
assert_eq!(array.item_cast::<u8>(), 1);
assert_eq!(array.as_slice::<f32>(), &[1.0]);
}
#[test]
fn exact_observers_reject_dtype_conversion() {
crate::with_device(crate::Device::cpu(), || {
let array = Array::from_slice(&[1_i32, 2, 3], &[3]);
let scalar = Array::from_int(1);
let vector_error = array.to_vec_exact::<f32>().unwrap_err();
assert!(
matches!(
vector_error,
crate::error::ConversionError::DtypeMismatch {
expected: Dtype::Float32,
actual: Dtype::Int32,
}
),
"{vector_error:?}; dtype={:?}",
array.dtype()
);
assert!(matches!(
scalar.try_item_exact::<f32>(),
Err(crate::error::ConversionError::DtypeMismatch {
expected: Dtype::Float32,
actual: Dtype::Int32,
})
));
});
}
#[test]
fn casting_observers_convert_values() {
crate::with_device(crate::Device::cpu(), || {
let vector = Array::from_slice(&[1_i32, 2, 3], &[3]);
let scalar = Array::from_int(7);
assert_eq!(vector.to_vec_cast::<f32>().unwrap(), vec![1.0, 2.0, 3.0]);
assert_eq!(scalar.try_item_cast::<f32>().unwrap(), 7.0);
});
}
#[test]
fn explicit_array_comparisons_distinguish_dtype_and_values() {
crate::with_device(crate::Device::cpu(), || {
let integers = Array::from_slice(&[1_i32, 2, 3], &[3]);
let same_integers = Array::from_slice(&[1_i32, 2, 3], &[3]);
let floats = Array::from_slice(&[1.0_f32, 2.0, 3.0], &[3]);
let nearby = Array::from_slice(&[1.0_f32, 2.0, 3.000_001], &[3]);
assert!(integers.eq_exact(&same_integers).unwrap());
assert!(!integers.eq_exact(&floats).unwrap());
assert!(integers.eq_values(&floats).unwrap());
assert!(floats
.all_close(&nearby, Some(1e-5), Some(1e-8), Some(false))
.unwrap());
});
}
}