1use crate::error::{Exception, Result};
7use crate::utils::guard::Guarded;
8use crate::utils::{IntoOption, VectorArray};
9use crate::{with_stream, Array, Axes, Stream};
10use mlx_internal_macros::generate_macro;
11use smallvec::SmallVec;
12use std::f64;
13use std::ffi::CString;
14
15#[derive(Debug, Clone, Copy)]
19pub enum Ord<'a> {
20 Str(&'a str),
22
23 P(f64),
25}
26
27impl Default for Ord<'_> {
28 fn default() -> Self {
29 Ord::Str("fro")
30 }
31}
32
33impl std::fmt::Display for Ord<'_> {
34 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35 match self {
36 Ord::Str(s) => write!(f, "{s}"),
37 Ord::P(p) => write!(f, "{p}"),
38 }
39 }
40}
41
42impl<'a> From<&'a str> for Ord<'a> {
43 fn from(value: &'a str) -> Self {
44 Ord::Str(value)
45 }
46}
47
48impl From<f64> for Ord<'_> {
49 fn from(value: f64) -> Self {
50 Ord::P(value)
51 }
52}
53
54impl<'a> IntoOption<Ord<'a>> for &'a str {
55 fn into_option(self) -> Option<Ord<'a>> {
56 Some(Ord::Str(self))
57 }
58}
59
60impl<'a> IntoOption<Ord<'a>> for f64 {
61 fn into_option(self) -> Option<Ord<'a>> {
62 Some(Ord::P(self))
63 }
64}
65
66#[derive(Debug, Clone)]
68pub struct SlogDet {
69 pub sign: Array,
71
72 pub log_abs_det: Array,
74}
75
76pub fn det(array: impl AsRef<Array>) -> Result<Array> {
89 let stream = Stream::thread_local_or_default();
90 Array::try_from_op(|res| unsafe {
91 mlx_sys::mlx_linalg_det(res, array.as_ref().as_ptr(), stream.as_ref().as_ptr())
92 })
93}
94
95pub fn slogdet(array: impl AsRef<Array>) -> Result<SlogDet> {
109 let stream = Stream::thread_local_or_default();
110 let (sign, log_abs_det) =
111 <(Array, Array) as Guarded>::try_from_op(|(sign, log_abs_det)| unsafe {
112 mlx_sys::mlx_linalg_slogdet(
113 sign,
114 log_abs_det,
115 array.as_ref().as_ptr(),
116 stream.as_ref().as_ptr(),
117 )
118 })?;
119 Ok(SlogDet { sign, log_abs_det })
120}
121
122#[derive(Debug, Clone, Default, PartialEq, Eq)]
124pub struct NormOptions {
125 pub axes: Axes,
127
128 pub keep_dims: bool,
130}
131fn with_norm_axes<T>(axes: &Axes, f: impl FnOnce(*const i32, usize) -> T) -> T {
132 match axes {
133 Axes::All => f(std::ptr::null(), 0),
134 Axes::Axis(axis) => f(axis, 1),
135 Axes::Axes(axes) => f(axes.as_ptr(), axes.len()),
136 }
137}
138fn legacy_norm_options(axes: Option<&[i32]>, keep_dims: Option<bool>) -> NormOptions {
139 NormOptions {
140 axes: axes.map_or(Axes::All, Axes::from),
141 keep_dims: keep_dims.unwrap_or(false),
142 }
143}
144
145pub fn norm(array: impl AsRef<Array>, ord: f64, options: NormOptions) -> Result<Array> {
147 let stream = Stream::thread_local_or_default();
148 with_norm_axes(&options.axes, |axes, num_axes| {
149 Array::try_from_op(|res| unsafe {
150 mlx_sys::mlx_linalg_norm(
151 res,
152 array.as_ref().as_ptr(),
153 ord,
154 axes,
155 num_axes,
156 options.keep_dims,
157 stream.as_ref().as_ptr(),
158 )
159 })
160 })
161}
162
163#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
165#[deprecated(
166 since = "0.26.0",
167 note = "use `with_stream` or `with_device` around `norm` with `NormOptions`"
168)]
169pub fn norm_device<'a>(
170 array: impl AsRef<Array>,
171 ord: f64,
172 #[optional] axes: impl IntoOption<&'a [i32]>,
173 #[optional] keep_dims: impl Into<Option<bool>>,
174 #[optional] stream: impl AsRef<Stream>,
175) -> Result<Array> {
176 let options = legacy_norm_options(axes.into_option(), keep_dims.into());
177 with_stream(stream.as_ref(), || norm(array, ord, options))
178}
179
180pub fn norm_matrix(array: impl AsRef<Array>, ord: &str, options: NormOptions) -> Result<Array> {
182 let ord = CString::new(ord).map_err(|e| Exception::custom(format!("{e}")))?;
183 let stream = Stream::thread_local_or_default();
184 with_norm_axes(&options.axes, |axes, num_axes| {
185 Array::try_from_op(|res| unsafe {
186 mlx_sys::mlx_linalg_norm_matrix(
187 res,
188 array.as_ref().as_ptr(),
189 ord.as_ptr(),
190 axes,
191 num_axes,
192 options.keep_dims,
193 stream.as_ref().as_ptr(),
194 )
195 })
196 })
197}
198
199#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
201#[deprecated(
202 since = "0.26.0",
203 note = "use `with_stream` or `with_device` around `norm_matrix` with `NormOptions`"
204)]
205pub fn norm_matrix_device<'a>(
206 array: impl AsRef<Array>,
207 ord: &'a str,
208 #[optional] axes: impl IntoOption<&'a [i32]>,
209 #[optional] keep_dims: impl Into<Option<bool>>,
210 #[optional] stream: impl AsRef<Stream>,
211) -> Result<Array> {
212 let options = legacy_norm_options(axes.into_option(), keep_dims.into());
213 with_stream(stream.as_ref(), || norm_matrix(array, ord, options))
214}
215
216pub fn norm_l2(array: impl AsRef<Array>, options: NormOptions) -> Result<Array> {
218 let stream = Stream::thread_local_or_default();
219 with_norm_axes(&options.axes, |axes, num_axes| {
220 Array::try_from_op(|res| unsafe {
221 mlx_sys::mlx_linalg_norm_l2(
222 res,
223 array.as_ref().as_ptr(),
224 axes,
225 num_axes,
226 options.keep_dims,
227 stream.as_ref().as_ptr(),
228 )
229 })
230 })
231}
232
233#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
235#[deprecated(
236 since = "0.26.0",
237 note = "use `with_stream` or `with_device` around `norm_l2` with `NormOptions`"
238)]
239pub fn norm_l2_device<'a>(
240 array: impl AsRef<Array>,
241 #[optional] axes: impl IntoOption<&'a [i32]>,
242 #[optional] keep_dims: impl Into<Option<bool>>,
243 #[optional] stream: impl AsRef<Stream>,
244) -> Result<Array> {
245 let options = legacy_norm_options(axes.into_option(), keep_dims.into());
246 with_stream(stream.as_ref(), || norm_l2(array, options))
247}
248pub fn qr(a: impl AsRef<Array>) -> Result<(Array, Array)> {
368 let stream = Stream::thread_local_or_default();
369 <(Array, Array)>::try_from_op(|(res_0, res_1)| unsafe {
370 mlx_sys::mlx_linalg_qr(res_0, res_1, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
371 })
372}
373
374#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
376#[deprecated(
377 since = "0.26.0",
378 note = "use `with_stream` or `with_device` around `qr`"
379)]
380pub fn qr_device(
381 a: impl AsRef<Array>,
382 #[optional] stream: impl AsRef<Stream>,
383) -> Result<(Array, Array)> {
384 crate::with_stream(stream.as_ref(), || qr(a))
385}
386
387pub fn svd(array: impl AsRef<Array>) -> Result<(Array, Array, Array)> {
417 let stream = Stream::thread_local_or_default();
418 let v = VectorArray::try_from_op(|res| unsafe {
419 mlx_sys::mlx_linalg_svd(res, array.as_ref().as_ptr(), true, stream.as_ref().as_ptr())
420 })?;
421
422 let vals: SmallVec<[Array; 3]> = v.try_into_values()?;
423 let mut iter = vals.into_iter();
424 let u = iter.next().unwrap();
425 let s = iter.next().unwrap();
426 let vt = iter.next().unwrap();
427
428 Ok((u, s, vt))
429}
430
431#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
433#[deprecated(
434 since = "0.26.0",
435 note = "use `with_stream` or `with_device` around `svd`"
436)]
437pub fn svd_device(
438 array: impl AsRef<Array>,
439 #[optional] stream: impl AsRef<Stream>,
440) -> Result<(Array, Array, Array)> {
441 crate::with_stream(stream.as_ref(), || svd(array))
442}
443
444pub fn inv(a: impl AsRef<Array>) -> Result<Array> {
468 let stream = Stream::thread_local_or_default();
469 Array::try_from_op(|res| unsafe {
470 mlx_sys::mlx_linalg_inv(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
471 })
472}
473
474#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
476#[deprecated(
477 since = "0.26.0",
478 note = "use `with_stream` or `with_device` around `inv`"
479)]
480pub fn inv_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
481 crate::with_stream(stream.as_ref(), || inv(a))
482}
483
484pub fn cholesky(a: impl AsRef<Array>, upper: Option<bool>) -> Result<Array> {
498 let stream = Stream::thread_local_or_default();
499 let upper = upper.unwrap_or(false);
500 Array::try_from_op(|res| unsafe {
501 mlx_sys::mlx_linalg_cholesky(res, a.as_ref().as_ptr(), upper, stream.as_ref().as_ptr())
502 })
503}
504
505#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
507#[deprecated(
508 since = "0.26.0",
509 note = "use `with_stream` or `with_device` around `cholesky`"
510)]
511pub fn cholesky_device(
512 a: impl AsRef<Array>,
513 #[optional] upper: Option<bool>,
514 #[optional] stream: impl AsRef<Stream>,
515) -> Result<Array> {
516 crate::with_stream(stream.as_ref(), || cholesky(a, upper))
517}
518
519pub fn cholesky_inv(a: impl AsRef<Array>, upper: Option<bool>) -> Result<Array> {
523 let stream = Stream::thread_local_or_default();
524 let upper = upper.unwrap_or(false);
525 Array::try_from_op(|res| unsafe {
526 mlx_sys::mlx_linalg_cholesky_inv(res, a.as_ref().as_ptr(), upper, stream.as_ref().as_ptr())
527 })
528}
529
530#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
532#[deprecated(
533 since = "0.26.0",
534 note = "use `with_stream` or `with_device` around `cholesky_inv`"
535)]
536pub fn cholesky_inv_device(
537 a: impl AsRef<Array>,
538 #[optional] upper: Option<bool>,
539 #[optional] stream: impl AsRef<Stream>,
540) -> Result<Array> {
541 crate::with_stream(stream.as_ref(), || cholesky_inv(a, upper))
542}
543
544pub fn cross(a: impl AsRef<Array>, b: impl AsRef<Array>, axis: Option<i32>) -> Result<Array> {
549 let stream = Stream::thread_local_or_default();
550 let axis = axis.unwrap_or(-1);
551 Array::try_from_op(|res| unsafe {
552 mlx_sys::mlx_linalg_cross(
553 res,
554 a.as_ref().as_ptr(),
555 b.as_ref().as_ptr(),
556 axis,
557 stream.as_ref().as_ptr(),
558 )
559 })
560}
561
562#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
564#[deprecated(
565 since = "0.26.0",
566 note = "use `with_stream` or `with_device` around `cross`"
567)]
568pub fn cross_device(
569 a: impl AsRef<Array>,
570 b: impl AsRef<Array>,
571 #[optional] axis: Option<i32>,
572 #[optional] stream: impl AsRef<Stream>,
573) -> Result<Array> {
574 crate::with_stream(stream.as_ref(), || cross(a, b, axis))
575}
576
577pub fn eigh(a: impl AsRef<Array>, uplo: Option<&str>) -> Result<(Array, Array)> {
583 let stream = Stream::thread_local_or_default();
584 let a = a.as_ref();
585 let uplo = CString::new(uplo.unwrap_or("L")).map_err(|e| Exception::custom(format!("{e}")))?;
586
587 <(Array, Array) as Guarded>::try_from_op(|(res_0, res_1)| unsafe {
588 mlx_sys::mlx_linalg_eigh(
589 res_0,
590 res_1,
591 a.as_ptr(),
592 uplo.as_ptr(),
593 stream.as_ref().as_ptr(),
594 )
595 })
596}
597
598#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
600#[deprecated(
601 since = "0.26.0",
602 note = "use `with_stream` or `with_device` around `eigh`"
603)]
604pub fn eigh_device(
605 a: impl AsRef<Array>,
606 #[optional] uplo: Option<&str>,
607 #[optional] stream: impl AsRef<Stream>,
608) -> Result<(Array, Array)> {
609 crate::with_stream(stream.as_ref(), || eigh(a, uplo))
610}
611
612pub fn eigvalsh(a: impl AsRef<Array>, uplo: Option<&str>) -> Result<Array> {
617 let stream = Stream::thread_local_or_default();
618 let a = a.as_ref();
619 let uplo = CString::new(uplo.unwrap_or("L")).map_err(|e| Exception::custom(format!("{e}")))?;
620 Array::try_from_op(|res| unsafe {
621 mlx_sys::mlx_linalg_eigvalsh(res, a.as_ptr(), uplo.as_ptr(), stream.as_ref().as_ptr())
622 })
623}
624
625#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
627#[deprecated(
628 since = "0.26.0",
629 note = "use `with_stream` or `with_device` around `eigvalsh`"
630)]
631pub fn eigvalsh_device(
632 a: impl AsRef<Array>,
633 #[optional] uplo: Option<&str>,
634 #[optional] stream: impl AsRef<Stream>,
635) -> Result<Array> {
636 crate::with_stream(stream.as_ref(), || eigvalsh(a, uplo))
637}
638
639pub fn eig(a: impl AsRef<Array>) -> Result<(Array, Array)> {
669 let stream = Stream::thread_local_or_default();
670 <(Array, Array) as Guarded>::try_from_op(|(res_0, res_1)| unsafe {
671 mlx_sys::mlx_linalg_eig(res_0, res_1, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
672 })
673}
674
675#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
677#[deprecated(
678 since = "0.26.0",
679 note = "use `with_stream` or `with_device` around `eig`"
680)]
681pub fn eig_device(
682 a: impl AsRef<Array>,
683 #[optional] stream: impl AsRef<Stream>,
684) -> Result<(Array, Array)> {
685 crate::with_stream(stream.as_ref(), || eig(a))
686}
687
688pub fn eigvals(a: impl AsRef<Array>) -> Result<Array> {
715 let stream = Stream::thread_local_or_default();
716 Array::try_from_op(|res| unsafe {
717 mlx_sys::mlx_linalg_eigvals(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
718 })
719}
720
721#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
723#[deprecated(
724 since = "0.26.0",
725 note = "use `with_stream` or `with_device` around `eigvals`"
726)]
727pub fn eigvals_device(
728 a: impl AsRef<Array>,
729 #[optional] stream: impl AsRef<Stream>,
730) -> Result<Array> {
731 crate::with_stream(stream.as_ref(), || eigvals(a))
732}
733
734pub fn pinv(a: impl AsRef<Array>) -> Result<Array> {
736 let stream = Stream::thread_local_or_default();
737 Array::try_from_op(|res| unsafe {
738 mlx_sys::mlx_linalg_pinv(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
739 })
740}
741
742#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
744#[deprecated(
745 since = "0.26.0",
746 note = "use `with_stream` or `with_device` around `pinv`"
747)]
748pub fn pinv_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
749 crate::with_stream(stream.as_ref(), || pinv(a))
750}
751
752pub fn tri_inv(a: impl AsRef<Array>, upper: Option<bool>) -> Result<Array> {
757 let stream = Stream::thread_local_or_default();
758 let upper = upper.unwrap_or(false);
759 Array::try_from_op(|res| unsafe {
760 mlx_sys::mlx_linalg_tri_inv(res, a.as_ref().as_ptr(), upper, stream.as_ref().as_ptr())
761 })
762}
763
764#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
766#[deprecated(
767 since = "0.26.0",
768 note = "use `with_stream` or `with_device` around `tri_inv`"
769)]
770pub fn tri_inv_device(
771 a: impl AsRef<Array>,
772 #[optional] upper: Option<bool>,
773 #[optional] stream: impl AsRef<Stream>,
774) -> Result<Array> {
775 crate::with_stream(stream.as_ref(), || tri_inv(a, upper))
776}
777
778pub fn lu(a: impl AsRef<Array>) -> Result<(Array, Array, Array)> {
812 let stream = Stream::thread_local_or_default();
813 let v = Vec::<Array>::try_from_op(|res| unsafe {
814 mlx_sys::mlx_linalg_lu(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
815 })?;
816 let mut iter = v.into_iter();
817 let p = iter.next().ok_or_else(|| Exception::custom("missing P"))?;
818 let l = iter.next().ok_or_else(|| Exception::custom("missing L"))?;
819 let u = iter.next().ok_or_else(|| Exception::custom("missing U"))?;
820 Ok((p, l, u))
821}
822
823#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
825#[deprecated(
826 since = "0.26.0",
827 note = "use `with_stream` or `with_device` around `lu`"
828)]
829pub fn lu_device(
830 a: impl AsRef<Array>,
831 #[optional] stream: impl AsRef<Stream>,
832) -> Result<(Array, Array, Array)> {
833 crate::with_stream(stream.as_ref(), || lu(a))
834}
835
836pub fn lu_factor(a: impl AsRef<Array>) -> Result<(Array, Array)> {
847 let stream = Stream::thread_local_or_default();
848 <(Array, Array)>::try_from_op(|(res_0, res_1)| unsafe {
849 mlx_sys::mlx_linalg_lu_factor(res_0, res_1, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
850 })
851}
852
853#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
855#[deprecated(
856 since = "0.26.0",
857 note = "use `with_stream` or `with_device` around `lu_factor`"
858)]
859pub fn lu_factor_device(
860 a: impl AsRef<Array>,
861 #[optional] stream: impl AsRef<Stream>,
862) -> Result<(Array, Array)> {
863 crate::with_stream(stream.as_ref(), || lu_factor(a))
864}
865
866pub fn solve(a: impl AsRef<Array>, b: impl AsRef<Array>) -> Result<Array> {
878 let stream = Stream::thread_local_or_default();
879 Array::try_from_op(|res| unsafe {
880 mlx_sys::mlx_linalg_solve(
881 res,
882 a.as_ref().as_ptr(),
883 b.as_ref().as_ptr(),
884 stream.as_ref().as_ptr(),
885 )
886 })
887}
888
889#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
891#[deprecated(
892 since = "0.26.0",
893 note = "use `with_stream` or `with_device` around `solve`"
894)]
895pub fn solve_device(
896 a: impl AsRef<Array>,
897 b: impl AsRef<Array>,
898 #[optional] stream: impl AsRef<Stream>,
899) -> Result<Array> {
900 crate::with_stream(stream.as_ref(), || solve(a, b))
901}
902
903pub fn solve_triangular(
916 a: impl AsRef<Array>,
917 b: impl AsRef<Array>,
918 upper: impl Into<Option<bool>>,
919) -> Result<Array> {
920 let stream = Stream::thread_local_or_default();
921 let upper = upper.into().unwrap_or(false);
922
923 Array::try_from_op(|res| unsafe {
924 mlx_sys::mlx_linalg_solve_triangular(
925 res,
926 a.as_ref().as_ptr(),
927 b.as_ref().as_ptr(),
928 upper,
929 stream.as_ref().as_ptr(),
930 )
931 })
932}
933
934#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
936#[deprecated(
937 since = "0.26.0",
938 note = "use `with_stream` or `with_device` around `solve_triangular`"
939)]
940pub fn solve_triangular_device(
941 a: impl AsRef<Array>,
942 b: impl AsRef<Array>,
943 #[optional] upper: impl Into<Option<bool>>,
944 #[optional] stream: impl AsRef<Stream>,
945) -> Result<Array> {
946 crate::with_stream(stream.as_ref(), || solve_triangular(a, b, upper))
947}
948
949#[cfg(test)]
950mod tests {
951 use float_eq::assert_float_eq;
952
953 use crate::{
954 array,
955 ops::{eye, indexing::IndexOp, tril, triu},
956 with_device, with_stream, Device, StreamOrDevice,
957 };
958
959 use super::*;
960
961 #[test]
966 fn test_norm_no_axes() {
967 let a = Array::from_iter(0..9, &[9]) - 4;
968 let b = a.reshape(&[3, 3]).unwrap();
969
970 assert_float_eq!(
971 norm_l2(&a, NormOptions::default())
972 .unwrap()
973 .item_exact::<f32>(),
974 7.74597,
975 abs <= 0.001
976 );
977 assert_float_eq!(
978 norm_l2(&b, NormOptions::default())
979 .unwrap()
980 .item_exact::<f32>(),
981 7.74597,
982 abs <= 0.001
983 );
984
985 assert_float_eq!(
986 norm_matrix(&b, "fro", NormOptions::default())
987 .unwrap()
988 .item_exact::<f32>(),
989 7.74597,
990 abs <= 0.001
991 );
992
993 assert_float_eq!(
994 norm(&a, f64::INFINITY, NormOptions::default())
995 .unwrap()
996 .item_exact::<f32>(),
997 4.0,
998 abs <= 0.001
999 );
1000 assert_float_eq!(
1001 norm(&b, f64::INFINITY, NormOptions::default())
1002 .unwrap()
1003 .item_exact::<f32>(),
1004 9.0,
1005 abs <= 0.001
1006 );
1007
1008 assert_float_eq!(
1009 norm(&a, f64::NEG_INFINITY, NormOptions::default())
1010 .unwrap()
1011 .item_exact::<f32>(),
1012 0.0,
1013 abs <= 0.001
1014 );
1015 assert_float_eq!(
1016 norm(&b, f64::NEG_INFINITY, NormOptions::default())
1017 .unwrap()
1018 .item_exact::<f32>(),
1019 2.0,
1020 abs <= 0.001
1021 );
1022
1023 assert_float_eq!(
1024 norm(&a, 1.0, NormOptions::default())
1025 .unwrap()
1026 .item_exact::<f32>(),
1027 20.0,
1028 abs <= 0.001
1029 );
1030 assert_float_eq!(
1031 norm(&b, 1.0, NormOptions::default())
1032 .unwrap()
1033 .item_exact::<f32>(),
1034 7.0,
1035 abs <= 0.001
1036 );
1037
1038 assert_float_eq!(
1039 norm(&a, -1.0, NormOptions::default())
1040 .unwrap()
1041 .item_exact::<f32>(),
1042 0.0,
1043 abs <= 0.001
1044 );
1045 assert_float_eq!(
1046 norm(&b, -1.0, NormOptions::default())
1047 .unwrap()
1048 .item_exact::<f32>(),
1049 6.0,
1050 abs <= 0.001
1051 );
1052 }
1053
1054 #[test]
1055 fn test_norm_axis() {
1056 let c = Array::from_slice(&[1, 2, 3, -1, 1, 4], &[2, 3]);
1057
1058 let result = norm_l2(
1059 &c,
1060 NormOptions {
1061 axes: Axes::from([0]),
1062 ..Default::default()
1063 },
1064 )
1065 .unwrap();
1066 let expected = Array::from_slice(&[1.41421, 2.23607, 5.0], &[3]);
1067 assert!(result.all_close(&expected, None, None, None).unwrap());
1068 }
1069
1070 #[test]
1071 fn test_norm_axes() {
1072 let m = Array::from_iter(0..8, &[2, 2, 2]);
1073
1074 let result = norm_l2(
1075 &m,
1076 NormOptions {
1077 axes: Axes::from([1, 2]),
1078 ..Default::default()
1079 },
1080 )
1081 .unwrap();
1082 let expected = Array::from_slice(&[3.74166, 11.225], &[2]);
1083 assert!(result.all_close(&expected, None, None, None).unwrap());
1084 }
1085
1086 #[test]
1087 fn test_qr() {
1088 let a = Array::from_slice(&[2.0f32, 3.0, 1.0, 2.0], &[2, 2]);
1089
1090 let (q, r) = with_device(Device::cpu(), || qr(&a)).unwrap();
1091
1092 let q_expected = Array::from_slice(&[-0.894427, -0.447214, -0.447214, 0.894427], &[2, 2]);
1093 let r_expected = Array::from_slice(&[-2.23607, -3.57771, 0.0, 0.447214], &[2, 2]);
1094
1095 assert!(q.all_close(&q_expected, None, None, None).unwrap());
1096 assert!(r.all_close(&r_expected, None, None, None).unwrap());
1097 }
1098
1099 #[test]
1102 fn test_svd() {
1103 let stream = StreamOrDevice::cpu();
1105
1106 let a = Array::from_f32(0.0);
1108 assert!(with_stream(stream.as_ref(), || svd(&a)).is_err());
1109
1110 let a = Array::from_slice(&[0.0, 1.0], &[2]);
1111 assert!(with_stream(stream.as_ref(), || svd(&a)).is_err());
1112
1113 let a = Array::from_slice(&[0, 1], &[1, 2]);
1115 assert!(with_stream(stream.as_ref(), || svd(&a)).is_err());
1116 }
1118
1119 #[test]
1120 fn test_inv() {
1121 let stream = StreamOrDevice::cpu();
1123
1124 let a = Array::from_f32(0.0);
1126 assert!(with_stream(stream.as_ref(), || inv(&a)).is_err());
1127
1128 let a = Array::from_slice(&[0.0, 1.0], &[2]);
1129 assert!(with_stream(stream.as_ref(), || inv(&a)).is_err());
1130
1131 let a = Array::from_slice(&[1, 2, 3, 4, 5, 6], &[2, 3]);
1133 assert!(with_stream(stream.as_ref(), || inv(&a)).is_err());
1134 }
1136
1137 #[test]
1138 fn test_cholesky() {
1139 let stream = StreamOrDevice::cpu();
1141
1142 let a = Array::from_f32(0.0);
1144 assert!(with_stream(stream.as_ref(), || cholesky(&a, None)).is_err());
1145
1146 let a = Array::from_slice(&[0.0, 1.0], &[2]);
1147 assert!(with_stream(stream.as_ref(), || cholesky(&a, None)).is_err());
1148
1149 let a = Array::from_slice(&[0, 1, 1, 2], &[2, 2]);
1151 assert!(with_stream(stream.as_ref(), || cholesky(&a, None)).is_err());
1152
1153 let a = Array::from_slice(&[1, 2, 3, 4, 5, 6], &[2, 3]);
1155 assert!(with_stream(stream.as_ref(), || cholesky(&a, None)).is_err());
1156 }
1158
1159 #[test]
1161 fn test_lu() {
1162 let scalar = array!(1.0);
1163 let result = with_device(Device::cpu(), || lu(&scalar));
1164 assert!(result.is_err());
1165
1166 let a = array!([[3.0f32, 1.0, 2.0], [1.0, 8.0, 6.0], [9.0, 2.0, 5.0]]);
1168 let (p, l, u) = with_device(Device::cpu(), || lu(&a)).unwrap();
1169 let a_rec = l.index((p, ..)).matmul(u).unwrap();
1170 assert_array_all_close!(a, a_rec);
1171 }
1172
1173 #[test]
1175 fn test_lu_factor() {
1176 crate::random::seed(7).unwrap();
1177
1178 let a = crate::random::uniform::<_, f32>(0.0, 1.0, &[5, 5], None).unwrap();
1180 let (lu, pivots) = with_device(Device::cpu(), || lu_factor(&a)).unwrap();
1181 let shape = a.shape();
1182 let n = shape[shape.len() - 1];
1183
1184 let pivots = pivots.to_vec_exact::<u32>().unwrap();
1185 let mut perm: Vec<u32> = (0..n as u32).collect();
1186 for (i, p) in pivots.iter().enumerate() {
1187 perm.swap(i, *p as usize);
1188 }
1189
1190 let l = tril(&lu, -1)
1191 .and_then(|l| l.add(eye::<f32>(n, None, None)?))
1192 .unwrap();
1193 let u = triu(&lu, None).unwrap();
1194
1195 let lhs = l.matmul(&u).unwrap();
1196 let perm = Array::from_slice(&perm, &[n]);
1197 let rhs = a.index((perm, ..));
1198 assert_array_all_close!(lhs, rhs);
1199 }
1200
1201 #[test]
1203 fn test_solve() {
1204 crate::random::seed(7).unwrap();
1205
1206 let a = array!([[3.0f32, 1.0, 2.0], [1.0, 8.0, 6.0], [9.0, 2.0, 5.0]]);
1208 let b = array!([11.0f32, 35.0, 28.0]);
1209
1210 let result = with_device(Device::cpu(), || solve(&a, &b)).unwrap();
1211 let expected = array!([1.0f32, 2.0, 3.0]);
1212 assert_array_all_close!(result, expected);
1213 }
1214
1215 #[test]
1216 fn test_solve_triangular() {
1217 let a = array!([[4.0f32, 0.0, 0.0], [2.0, 3.0, 0.0], [1.0, -2.0, 5.0]]);
1218 let b = array!([8.0f32, 14.0, 3.0]);
1219
1220 let result = with_device(Device::cpu(), || solve_triangular(&a, &b, false)).unwrap();
1221 let expected = array!([2.0f32, 3.333_333_3, 1.533_333_3]);
1222 assert_array_all_close!(result, expected);
1223 }
1224
1225 #[test]
1227 fn test_eig() {
1228 use crate::ops::expand_dims;
1229
1230 fn check_eigs_and_vecs(a: &Array) {
1232 let (eig_vals, eig_vecs) = with_device(Device::cpu(), || eig(a)).unwrap();
1233
1234 let lhs = a.matmul(&eig_vecs).unwrap();
1236 let eig_vals_broadcast = expand_dims(&eig_vals, -2).unwrap();
1240 let rhs = eig_vals_broadcast.multiply(&eig_vecs).unwrap();
1241 assert!(
1242 lhs.all_close(&rhs, 1e-4, 1e-4, None).unwrap(),
1243 "A @ eig_vecs should equal eig_vals * eig_vecs"
1244 );
1245
1246 let eig_vals_only = with_device(Device::cpu(), || eigvals(a)).unwrap();
1248 assert!(
1249 eig_vals
1250 .all_close(&eig_vals_only, 1e-4, 1e-4, None)
1251 .unwrap(),
1252 "eigvals should return same eigenvalues as eig"
1253 );
1254 }
1255
1256 let a = array!([[1.0f32, 1.0], [3.0, 4.0]]);
1258 check_eigs_and_vecs(&a);
1259
1260 let a = array!([[1.0f32, -1.0], [1.0, 1.0]]);
1262 check_eigs_and_vecs(&a);
1263
1264 crate::random::seed(1).unwrap();
1266 let a = crate::random::normal::<f32>(&[5, 5], None, None, None).unwrap();
1267 check_eigs_and_vecs(&a);
1268
1269 let a = crate::random::normal::<f32>(&[3, 5, 5], None, None, None).unwrap();
1271 check_eigs_and_vecs(&a);
1272 }
1273
1274 #[test]
1275 fn test_eig_errors() {
1276 let a = array!([1.0f32, 2.0]);
1278 assert!(with_device(Device::cpu(), || eig(&a)).is_err());
1279 assert!(with_device(Device::cpu(), || eigvals(&a)).is_err());
1280
1281 let a = array!([[1.0f32, 2.0], [3.0, 4.0], [5.0, 6.0]]);
1283 assert!(with_device(Device::cpu(), || eig(&a)).is_err());
1284 assert!(with_device(Device::cpu(), || eigvals(&a)).is_err());
1285 }
1286}