use crate::error::{Exception, Result};
use crate::utils::guard::Guarded;
use crate::utils::{IntoOption, VectorArray};
use crate::{with_stream, Array, Axes, Stream};
use mlx_internal_macros::generate_macro;
use smallvec::SmallVec;
use std::ffi::CString;
#[derive(Debug, Clone, Copy)]
pub enum Ord<'a> {
Str(&'a str),
P(f64),
}
impl Default for Ord<'_> {
fn default() -> Self {
Ord::Str("fro")
}
}
impl std::fmt::Display for Ord<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Ord::Str(s) => write!(f, "{s}"),
Ord::P(p) => write!(f, "{p}"),
}
}
}
impl<'a> From<&'a str> for Ord<'a> {
fn from(value: &'a str) -> Self {
Ord::Str(value)
}
}
impl From<f64> for Ord<'_> {
fn from(value: f64) -> Self {
Ord::P(value)
}
}
impl<'a> IntoOption<Ord<'a>> for &'a str {
fn into_option(self) -> Option<Ord<'a>> {
Some(Ord::Str(self))
}
}
impl<'a> IntoOption<Ord<'a>> for f64 {
fn into_option(self) -> Option<Ord<'a>> {
Some(Ord::P(self))
}
}
#[derive(Debug, Clone)]
pub struct SlogDet {
pub sign: Array,
pub log_abs_det: Array,
}
pub fn det(array: impl AsRef<Array>) -> Result<Array> {
let stream = Stream::thread_local_or_default();
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_linalg_det(res, array.as_ref().as_ptr(), stream.as_ref().as_ptr())
})
}
pub fn slogdet(array: impl AsRef<Array>) -> Result<SlogDet> {
let stream = Stream::thread_local_or_default();
let (sign, log_abs_det) =
<(Array, Array) as Guarded>::try_from_op(|(sign, log_abs_det)| unsafe {
mlx_sys::mlx_linalg_slogdet(
sign,
log_abs_det,
array.as_ref().as_ptr(),
stream.as_ref().as_ptr(),
)
})?;
Ok(SlogDet { sign, log_abs_det })
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct NormOptions {
pub axes: Axes,
pub keep_dims: bool,
}
fn with_norm_axes<T>(axes: &Axes, f: impl FnOnce(*const i32, usize) -> T) -> T {
match axes {
Axes::All => f(std::ptr::null(), 0),
Axes::Axis(axis) => f(axis, 1),
Axes::Axes(axes) => f(axes.as_ptr(), axes.len()),
}
}
fn legacy_norm_options(axes: Option<&[i32]>, keep_dims: Option<bool>) -> NormOptions {
NormOptions {
axes: axes.map_or(Axes::All, Axes::from),
keep_dims: keep_dims.unwrap_or(false),
}
}
pub fn norm(array: impl AsRef<Array>, ord: f64, options: NormOptions) -> Result<Array> {
let stream = Stream::thread_local_or_default();
with_norm_axes(&options.axes, |axes, num_axes| {
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_linalg_norm(
res,
array.as_ref().as_ptr(),
ord,
axes,
num_axes,
options.keep_dims,
stream.as_ref().as_ptr(),
)
})
})
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `norm` with `NormOptions`"
)]
pub fn norm_device<'a>(
array: impl AsRef<Array>,
ord: f64,
#[optional] axes: impl IntoOption<&'a [i32]>,
#[optional] keep_dims: impl Into<Option<bool>>,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
let options = legacy_norm_options(axes.into_option(), keep_dims.into());
with_stream(stream.as_ref(), || norm(array, ord, options))
}
pub fn norm_matrix(array: impl AsRef<Array>, ord: &str, options: NormOptions) -> Result<Array> {
let ord = CString::new(ord).map_err(|e| Exception::custom(format!("{e}")))?;
let stream = Stream::thread_local_or_default();
with_norm_axes(&options.axes, |axes, num_axes| {
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_linalg_norm_matrix(
res,
array.as_ref().as_ptr(),
ord.as_ptr(),
axes,
num_axes,
options.keep_dims,
stream.as_ref().as_ptr(),
)
})
})
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `norm_matrix` with `NormOptions`"
)]
pub fn norm_matrix_device<'a>(
array: impl AsRef<Array>,
ord: &'a str,
#[optional] axes: impl IntoOption<&'a [i32]>,
#[optional] keep_dims: impl Into<Option<bool>>,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
let options = legacy_norm_options(axes.into_option(), keep_dims.into());
with_stream(stream.as_ref(), || norm_matrix(array, ord, options))
}
pub fn norm_l2(array: impl AsRef<Array>, options: NormOptions) -> Result<Array> {
let stream = Stream::thread_local_or_default();
with_norm_axes(&options.axes, |axes, num_axes| {
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_linalg_norm_l2(
res,
array.as_ref().as_ptr(),
axes,
num_axes,
options.keep_dims,
stream.as_ref().as_ptr(),
)
})
})
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `norm_l2` with `NormOptions`"
)]
pub fn norm_l2_device<'a>(
array: impl AsRef<Array>,
#[optional] axes: impl IntoOption<&'a [i32]>,
#[optional] keep_dims: impl Into<Option<bool>>,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
let options = legacy_norm_options(axes.into_option(), keep_dims.into());
with_stream(stream.as_ref(), || norm_l2(array, options))
}
pub fn qr(a: impl AsRef<Array>) -> Result<(Array, Array)> {
let stream = Stream::thread_local_or_default();
<(Array, Array)>::try_from_op(|(res_0, res_1)| unsafe {
mlx_sys::mlx_linalg_qr(res_0, res_1, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
})
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `qr`"
)]
pub fn qr_device(
a: impl AsRef<Array>,
#[optional] stream: impl AsRef<Stream>,
) -> Result<(Array, Array)> {
crate::with_stream(stream.as_ref(), || qr(a))
}
pub fn svd(array: impl AsRef<Array>) -> Result<(Array, Array, Array)> {
let stream = Stream::thread_local_or_default();
let v = VectorArray::try_from_op(|res| unsafe {
mlx_sys::mlx_linalg_svd(res, array.as_ref().as_ptr(), true, stream.as_ref().as_ptr())
})?;
let vals: SmallVec<[Array; 3]> = v.try_into_values()?;
let mut iter = vals.into_iter();
let u = iter.next().unwrap();
let s = iter.next().unwrap();
let vt = iter.next().unwrap();
Ok((u, s, vt))
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `svd`"
)]
pub fn svd_device(
array: impl AsRef<Array>,
#[optional] stream: impl AsRef<Stream>,
) -> Result<(Array, Array, Array)> {
crate::with_stream(stream.as_ref(), || svd(array))
}
pub fn inv(a: impl AsRef<Array>) -> Result<Array> {
let stream = Stream::thread_local_or_default();
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_linalg_inv(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
})
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `inv`"
)]
pub fn inv_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
crate::with_stream(stream.as_ref(), || inv(a))
}
pub fn cholesky(a: impl AsRef<Array>, upper: Option<bool>) -> Result<Array> {
let stream = Stream::thread_local_or_default();
let upper = upper.unwrap_or(false);
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_linalg_cholesky(res, a.as_ref().as_ptr(), upper, stream.as_ref().as_ptr())
})
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `cholesky`"
)]
pub fn cholesky_device(
a: impl AsRef<Array>,
#[optional] upper: Option<bool>,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || cholesky(a, upper))
}
pub fn cholesky_inv(a: impl AsRef<Array>, upper: Option<bool>) -> Result<Array> {
let stream = Stream::thread_local_or_default();
let upper = upper.unwrap_or(false);
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_linalg_cholesky_inv(res, a.as_ref().as_ptr(), upper, stream.as_ref().as_ptr())
})
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `cholesky_inv`"
)]
pub fn cholesky_inv_device(
a: impl AsRef<Array>,
#[optional] upper: Option<bool>,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || cholesky_inv(a, upper))
}
pub fn cross(a: impl AsRef<Array>, b: impl AsRef<Array>, axis: Option<i32>) -> Result<Array> {
let stream = Stream::thread_local_or_default();
let axis = axis.unwrap_or(-1);
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_linalg_cross(
res,
a.as_ref().as_ptr(),
b.as_ref().as_ptr(),
axis,
stream.as_ref().as_ptr(),
)
})
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `cross`"
)]
pub fn cross_device(
a: impl AsRef<Array>,
b: impl AsRef<Array>,
#[optional] axis: Option<i32>,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || cross(a, b, axis))
}
pub fn eigh(a: impl AsRef<Array>, uplo: Option<&str>) -> Result<(Array, Array)> {
let stream = Stream::thread_local_or_default();
let a = a.as_ref();
let uplo = CString::new(uplo.unwrap_or("L")).map_err(|e| Exception::custom(format!("{e}")))?;
<(Array, Array) as Guarded>::try_from_op(|(res_0, res_1)| unsafe {
mlx_sys::mlx_linalg_eigh(
res_0,
res_1,
a.as_ptr(),
uplo.as_ptr(),
stream.as_ref().as_ptr(),
)
})
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `eigh`"
)]
pub fn eigh_device(
a: impl AsRef<Array>,
#[optional] uplo: Option<&str>,
#[optional] stream: impl AsRef<Stream>,
) -> Result<(Array, Array)> {
crate::with_stream(stream.as_ref(), || eigh(a, uplo))
}
pub fn eigvalsh(a: impl AsRef<Array>, uplo: Option<&str>) -> Result<Array> {
let stream = Stream::thread_local_or_default();
let a = a.as_ref();
let uplo = CString::new(uplo.unwrap_or("L")).map_err(|e| Exception::custom(format!("{e}")))?;
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_linalg_eigvalsh(res, a.as_ptr(), uplo.as_ptr(), stream.as_ref().as_ptr())
})
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `eigvalsh`"
)]
pub fn eigvalsh_device(
a: impl AsRef<Array>,
#[optional] uplo: Option<&str>,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || eigvalsh(a, uplo))
}
pub fn eig(a: impl AsRef<Array>) -> Result<(Array, Array)> {
let stream = Stream::thread_local_or_default();
<(Array, Array) as Guarded>::try_from_op(|(res_0, res_1)| unsafe {
mlx_sys::mlx_linalg_eig(res_0, res_1, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
})
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `eig`"
)]
pub fn eig_device(
a: impl AsRef<Array>,
#[optional] stream: impl AsRef<Stream>,
) -> Result<(Array, Array)> {
crate::with_stream(stream.as_ref(), || eig(a))
}
pub fn eigvals(a: impl AsRef<Array>) -> Result<Array> {
let stream = Stream::thread_local_or_default();
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_linalg_eigvals(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
})
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `eigvals`"
)]
pub fn eigvals_device(
a: impl AsRef<Array>,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || eigvals(a))
}
pub fn pinv(a: impl AsRef<Array>) -> Result<Array> {
let stream = Stream::thread_local_or_default();
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_linalg_pinv(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
})
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `pinv`"
)]
pub fn pinv_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
crate::with_stream(stream.as_ref(), || pinv(a))
}
pub fn tri_inv(a: impl AsRef<Array>, upper: Option<bool>) -> Result<Array> {
let stream = Stream::thread_local_or_default();
let upper = upper.unwrap_or(false);
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_linalg_tri_inv(res, a.as_ref().as_ptr(), upper, stream.as_ref().as_ptr())
})
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `tri_inv`"
)]
pub fn tri_inv_device(
a: impl AsRef<Array>,
#[optional] upper: Option<bool>,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || tri_inv(a, upper))
}
pub fn lu(a: impl AsRef<Array>) -> Result<(Array, Array, Array)> {
let stream = Stream::thread_local_or_default();
let v = Vec::<Array>::try_from_op(|res| unsafe {
mlx_sys::mlx_linalg_lu(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
})?;
let mut iter = v.into_iter();
let p = iter.next().ok_or_else(|| Exception::custom("missing P"))?;
let l = iter.next().ok_or_else(|| Exception::custom("missing L"))?;
let u = iter.next().ok_or_else(|| Exception::custom("missing U"))?;
Ok((p, l, u))
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `lu`"
)]
pub fn lu_device(
a: impl AsRef<Array>,
#[optional] stream: impl AsRef<Stream>,
) -> Result<(Array, Array, Array)> {
crate::with_stream(stream.as_ref(), || lu(a))
}
pub fn lu_factor(a: impl AsRef<Array>) -> Result<(Array, Array)> {
let stream = Stream::thread_local_or_default();
<(Array, Array)>::try_from_op(|(res_0, res_1)| unsafe {
mlx_sys::mlx_linalg_lu_factor(res_0, res_1, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
})
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `lu_factor`"
)]
pub fn lu_factor_device(
a: impl AsRef<Array>,
#[optional] stream: impl AsRef<Stream>,
) -> Result<(Array, Array)> {
crate::with_stream(stream.as_ref(), || lu_factor(a))
}
pub fn solve(a: impl AsRef<Array>, b: impl AsRef<Array>) -> Result<Array> {
let stream = Stream::thread_local_or_default();
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_linalg_solve(
res,
a.as_ref().as_ptr(),
b.as_ref().as_ptr(),
stream.as_ref().as_ptr(),
)
})
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `solve`"
)]
pub fn solve_device(
a: impl AsRef<Array>,
b: impl AsRef<Array>,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || solve(a, b))
}
pub fn solve_triangular(
a: impl AsRef<Array>,
b: impl AsRef<Array>,
upper: impl Into<Option<bool>>,
) -> Result<Array> {
let stream = Stream::thread_local_or_default();
let upper = upper.into().unwrap_or(false);
Array::try_from_op(|res| unsafe {
mlx_sys::mlx_linalg_solve_triangular(
res,
a.as_ref().as_ptr(),
b.as_ref().as_ptr(),
upper,
stream.as_ref().as_ptr(),
)
})
}
#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
#[deprecated(
since = "0.26.0",
note = "use `with_stream` or `with_device` around `solve_triangular`"
)]
pub fn solve_triangular_device(
a: impl AsRef<Array>,
b: impl AsRef<Array>,
#[optional] upper: impl Into<Option<bool>>,
#[optional] stream: impl AsRef<Stream>,
) -> Result<Array> {
crate::with_stream(stream.as_ref(), || solve_triangular(a, b, upper))
}
#[cfg(test)]
mod tests {
use float_eq::assert_float_eq;
use crate::{
array,
ops::{eye, indexing::IndexOp, tril, triu},
with_device, with_stream, Device, StreamOrDevice,
};
use super::*;
#[test]
fn test_norm_no_axes() {
let a = Array::from_iter(0..9, &[9]) - 4;
let b = a.reshape(&[3, 3]).unwrap();
assert_float_eq!(
norm_l2(&a, NormOptions::default())
.unwrap()
.item_exact::<f32>(),
7.74597,
abs <= 0.001
);
assert_float_eq!(
norm_l2(&b, NormOptions::default())
.unwrap()
.item_exact::<f32>(),
7.74597,
abs <= 0.001
);
assert_float_eq!(
norm_matrix(&b, "fro", NormOptions::default())
.unwrap()
.item_exact::<f32>(),
7.74597,
abs <= 0.001
);
assert_float_eq!(
norm(&a, f64::INFINITY, NormOptions::default())
.unwrap()
.item_exact::<f32>(),
4.0,
abs <= 0.001
);
assert_float_eq!(
norm(&b, f64::INFINITY, NormOptions::default())
.unwrap()
.item_exact::<f32>(),
9.0,
abs <= 0.001
);
assert_float_eq!(
norm(&a, f64::NEG_INFINITY, NormOptions::default())
.unwrap()
.item_exact::<f32>(),
0.0,
abs <= 0.001
);
assert_float_eq!(
norm(&b, f64::NEG_INFINITY, NormOptions::default())
.unwrap()
.item_exact::<f32>(),
2.0,
abs <= 0.001
);
assert_float_eq!(
norm(&a, 1.0, NormOptions::default())
.unwrap()
.item_exact::<f32>(),
20.0,
abs <= 0.001
);
assert_float_eq!(
norm(&b, 1.0, NormOptions::default())
.unwrap()
.item_exact::<f32>(),
7.0,
abs <= 0.001
);
assert_float_eq!(
norm(&a, -1.0, NormOptions::default())
.unwrap()
.item_exact::<f32>(),
0.0,
abs <= 0.001
);
assert_float_eq!(
norm(&b, -1.0, NormOptions::default())
.unwrap()
.item_exact::<f32>(),
6.0,
abs <= 0.001
);
}
#[test]
fn test_norm_axis() {
let c = Array::from_slice(&[1, 2, 3, -1, 1, 4], &[2, 3]);
let result = norm_l2(
&c,
NormOptions {
axes: Axes::from([0]),
..Default::default()
},
)
.unwrap();
let expected = Array::from_slice(&[1.41421, 2.23607, 5.0], &[3]);
assert!(result.all_close(&expected, None, None, None).unwrap());
}
#[test]
fn test_norm_axes() {
let m = Array::from_iter(0..8, &[2, 2, 2]);
let result = norm_l2(
&m,
NormOptions {
axes: Axes::from([1, 2]),
..Default::default()
},
)
.unwrap();
let expected = Array::from_slice(&[3.74166, 11.225], &[2]);
assert!(result.all_close(&expected, None, None, None).unwrap());
}
#[test]
fn test_qr() {
let a = Array::from_slice(&[2.0f32, 3.0, 1.0, 2.0], &[2, 2]);
let (q, r) = with_device(Device::cpu(), || qr(&a)).unwrap();
let q_expected = Array::from_slice(&[-0.894427, -0.447214, -0.447214, 0.894427], &[2, 2]);
let r_expected = Array::from_slice(&[-2.23607, -3.57771, 0.0, 0.447214], &[2, 2]);
assert!(q.all_close(&q_expected, None, None, None).unwrap());
assert!(r.all_close(&r_expected, None, None, None).unwrap());
}
#[test]
fn test_svd() {
let stream = StreamOrDevice::cpu();
let a = Array::from_f32(0.0);
assert!(with_stream(stream.as_ref(), || svd(&a)).is_err());
let a = Array::from_slice(&[0.0, 1.0], &[2]);
assert!(with_stream(stream.as_ref(), || svd(&a)).is_err());
let a = Array::from_slice(&[0, 1], &[1, 2]);
assert!(with_stream(stream.as_ref(), || svd(&a)).is_err());
}
#[test]
fn test_inv() {
let stream = StreamOrDevice::cpu();
let a = Array::from_f32(0.0);
assert!(with_stream(stream.as_ref(), || inv(&a)).is_err());
let a = Array::from_slice(&[0.0, 1.0], &[2]);
assert!(with_stream(stream.as_ref(), || inv(&a)).is_err());
let a = Array::from_slice(&[1, 2, 3, 4, 5, 6], &[2, 3]);
assert!(with_stream(stream.as_ref(), || inv(&a)).is_err());
}
#[test]
fn test_cholesky() {
let stream = StreamOrDevice::cpu();
let a = Array::from_f32(0.0);
assert!(with_stream(stream.as_ref(), || cholesky(&a, None)).is_err());
let a = Array::from_slice(&[0.0, 1.0], &[2]);
assert!(with_stream(stream.as_ref(), || cholesky(&a, None)).is_err());
let a = Array::from_slice(&[0, 1, 1, 2], &[2, 2]);
assert!(with_stream(stream.as_ref(), || cholesky(&a, None)).is_err());
let a = Array::from_slice(&[1, 2, 3, 4, 5, 6], &[2, 3]);
assert!(with_stream(stream.as_ref(), || cholesky(&a, None)).is_err());
}
#[test]
fn test_lu() {
let scalar = array!(1.0);
let result = with_device(Device::cpu(), || lu(&scalar));
assert!(result.is_err());
let a = array!([[3.0f32, 1.0, 2.0], [1.0, 8.0, 6.0], [9.0, 2.0, 5.0]]);
let (p, l, u) = with_device(Device::cpu(), || lu(&a)).unwrap();
let a_rec = l.index((p, ..)).matmul(u).unwrap();
assert_array_all_close!(a, a_rec);
}
#[test]
fn test_lu_factor() {
crate::random::seed(7).unwrap();
let a = crate::random::uniform::<_, f32>(0.0, 1.0, &[5, 5], None).unwrap();
let (lu, pivots) = with_device(Device::cpu(), || lu_factor(&a)).unwrap();
let shape = a.shape();
let n = shape[shape.len() - 1];
let pivots = pivots.to_vec_exact::<u32>().unwrap();
let mut perm: Vec<u32> = (0..n as u32).collect();
for (i, p) in pivots.iter().enumerate() {
perm.swap(i, *p as usize);
}
let l = tril(&lu, -1)
.and_then(|l| l.add(eye::<f32>(n, None, None)?))
.unwrap();
let u = triu(&lu, None).unwrap();
let lhs = l.matmul(&u).unwrap();
let perm = Array::from_slice(&perm, &[n]);
let rhs = a.index((perm, ..));
assert_array_all_close!(lhs, rhs);
}
#[test]
fn test_solve() {
crate::random::seed(7).unwrap();
let a = array!([[3.0f32, 1.0, 2.0], [1.0, 8.0, 6.0], [9.0, 2.0, 5.0]]);
let b = array!([11.0f32, 35.0, 28.0]);
let result = with_device(Device::cpu(), || solve(&a, &b)).unwrap();
let expected = array!([1.0f32, 2.0, 3.0]);
assert_array_all_close!(result, expected);
}
#[test]
fn test_solve_triangular() {
let a = array!([[4.0f32, 0.0, 0.0], [2.0, 3.0, 0.0], [1.0, -2.0, 5.0]]);
let b = array!([8.0f32, 14.0, 3.0]);
let result = with_device(Device::cpu(), || solve_triangular(&a, &b, false)).unwrap();
let expected = array!([2.0f32, 3.333_333_3, 1.533_333_3]);
assert_array_all_close!(result, expected);
}
#[test]
fn test_eig() {
use crate::ops::expand_dims;
fn check_eigs_and_vecs(a: &Array) {
let (eig_vals, eig_vecs) = with_device(Device::cpu(), || eig(a)).unwrap();
let lhs = a.matmul(&eig_vecs).unwrap();
let eig_vals_broadcast = expand_dims(&eig_vals, -2).unwrap();
let rhs = eig_vals_broadcast.multiply(&eig_vecs).unwrap();
assert!(
lhs.all_close(&rhs, 1e-4, 1e-4, None).unwrap(),
"A @ eig_vecs should equal eig_vals * eig_vecs"
);
let eig_vals_only = with_device(Device::cpu(), || eigvals(a)).unwrap();
assert!(
eig_vals
.all_close(&eig_vals_only, 1e-4, 1e-4, None)
.unwrap(),
"eigvals should return same eigenvalues as eig"
);
}
let a = array!([[1.0f32, 1.0], [3.0, 4.0]]);
check_eigs_and_vecs(&a);
let a = array!([[1.0f32, -1.0], [1.0, 1.0]]);
check_eigs_and_vecs(&a);
crate::random::seed(1).unwrap();
let a = crate::random::normal::<f32>(&[5, 5], None, None, None).unwrap();
check_eigs_and_vecs(&a);
let a = crate::random::normal::<f32>(&[3, 5, 5], None, None, None).unwrap();
check_eigs_and_vecs(&a);
}
#[test]
fn test_eig_errors() {
let a = array!([1.0f32, 2.0]);
assert!(with_device(Device::cpu(), || eig(&a)).is_err());
assert!(with_device(Device::cpu(), || eigvals(&a)).is_err());
let a = array!([[1.0f32, 2.0], [3.0, 4.0], [5.0, 6.0]]);
assert!(with_device(Device::cpu(), || eig(&a)).is_err());
assert!(with_device(Device::cpu(), || eigvals(&a)).is_err());
}
}