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::ffi::CString;
13
14#[derive(Debug, Clone, Copy)]
18pub enum Ord<'a> {
19 Str(&'a str),
21
22 P(f64),
24}
25
26impl Default for Ord<'_> {
27 fn default() -> Self {
28 Ord::Str("fro")
29 }
30}
31
32impl std::fmt::Display for Ord<'_> {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 match self {
35 Ord::Str(s) => write!(f, "{s}"),
36 Ord::P(p) => write!(f, "{p}"),
37 }
38 }
39}
40
41impl<'a> From<&'a str> for Ord<'a> {
42 fn from(value: &'a str) -> Self {
43 Ord::Str(value)
44 }
45}
46
47impl From<f64> for Ord<'_> {
48 fn from(value: f64) -> Self {
49 Ord::P(value)
50 }
51}
52
53impl<'a> IntoOption<Ord<'a>> for &'a str {
54 fn into_option(self) -> Option<Ord<'a>> {
55 Some(Ord::Str(self))
56 }
57}
58
59impl<'a> IntoOption<Ord<'a>> for f64 {
60 fn into_option(self) -> Option<Ord<'a>> {
61 Some(Ord::P(self))
62 }
63}
64
65#[derive(Debug, Clone)]
67pub struct SlogDet {
68 pub sign: Array,
70
71 pub log_abs_det: Array,
73}
74
75pub fn det(array: impl AsRef<Array>) -> Result<Array> {
88 let stream = Stream::thread_local_or_default();
89 Array::try_from_op(|res| unsafe {
90 mlx_sys::mlx_linalg_det(res, array.as_ref().as_ptr(), stream.as_ref().as_ptr())
91 })
92}
93
94pub fn slogdet(array: impl AsRef<Array>) -> Result<SlogDet> {
108 let stream = Stream::thread_local_or_default();
109 let (sign, log_abs_det) =
110 <(Array, Array) as Guarded>::try_from_op(|(sign, log_abs_det)| unsafe {
111 mlx_sys::mlx_linalg_slogdet(
112 sign,
113 log_abs_det,
114 array.as_ref().as_ptr(),
115 stream.as_ref().as_ptr(),
116 )
117 })?;
118 Ok(SlogDet { sign, log_abs_det })
119}
120
121#[derive(Debug, Clone, Default, PartialEq, Eq)]
123pub struct NormOptions {
124 pub axes: Axes,
126
127 pub keep_dims: bool,
129}
130fn with_norm_axes<T>(axes: &Axes, f: impl FnOnce(*const i32, usize) -> T) -> T {
131 match axes {
132 Axes::All => f(std::ptr::null(), 0),
133 Axes::Axis(axis) => f(axis, 1),
134 Axes::Axes(axes) => f(axes.as_ptr(), axes.len()),
135 }
136}
137fn legacy_norm_options(axes: Option<&[i32]>, keep_dims: Option<bool>) -> NormOptions {
138 NormOptions {
139 axes: axes.map_or(Axes::All, Axes::from),
140 keep_dims: keep_dims.unwrap_or(false),
141 }
142}
143
144pub fn norm(array: impl AsRef<Array>, ord: f64, options: NormOptions) -> Result<Array> {
146 let stream = Stream::thread_local_or_default();
147 with_norm_axes(&options.axes, |axes, num_axes| {
148 Array::try_from_op(|res| unsafe {
149 mlx_sys::mlx_linalg_norm(
150 res,
151 array.as_ref().as_ptr(),
152 ord,
153 axes,
154 num_axes,
155 options.keep_dims,
156 stream.as_ref().as_ptr(),
157 )
158 })
159 })
160}
161
162#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
164#[deprecated(
165 since = "0.26.0",
166 note = "use `with_stream` or `with_device` around `norm` with `NormOptions`"
167)]
168pub fn norm_device<'a>(
169 array: impl AsRef<Array>,
170 ord: f64,
171 #[optional] axes: impl IntoOption<&'a [i32]>,
172 #[optional] keep_dims: impl Into<Option<bool>>,
173 #[optional] stream: impl AsRef<Stream>,
174) -> Result<Array> {
175 let options = legacy_norm_options(axes.into_option(), keep_dims.into());
176 with_stream(stream.as_ref(), || norm(array, ord, options))
177}
178
179pub fn norm_matrix(array: impl AsRef<Array>, ord: &str, options: NormOptions) -> Result<Array> {
181 let ord = CString::new(ord).map_err(|e| Exception::custom(format!("{e}")))?;
182 let stream = Stream::thread_local_or_default();
183 with_norm_axes(&options.axes, |axes, num_axes| {
184 Array::try_from_op(|res| unsafe {
185 mlx_sys::mlx_linalg_norm_matrix(
186 res,
187 array.as_ref().as_ptr(),
188 ord.as_ptr(),
189 axes,
190 num_axes,
191 options.keep_dims,
192 stream.as_ref().as_ptr(),
193 )
194 })
195 })
196}
197
198#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
200#[deprecated(
201 since = "0.26.0",
202 note = "use `with_stream` or `with_device` around `norm_matrix` with `NormOptions`"
203)]
204pub fn norm_matrix_device<'a>(
205 array: impl AsRef<Array>,
206 ord: &'a str,
207 #[optional] axes: impl IntoOption<&'a [i32]>,
208 #[optional] keep_dims: impl Into<Option<bool>>,
209 #[optional] stream: impl AsRef<Stream>,
210) -> Result<Array> {
211 let options = legacy_norm_options(axes.into_option(), keep_dims.into());
212 with_stream(stream.as_ref(), || norm_matrix(array, ord, options))
213}
214
215pub fn norm_l2(array: impl AsRef<Array>, options: NormOptions) -> Result<Array> {
217 let stream = Stream::thread_local_or_default();
218 with_norm_axes(&options.axes, |axes, num_axes| {
219 Array::try_from_op(|res| unsafe {
220 mlx_sys::mlx_linalg_norm_l2(
221 res,
222 array.as_ref().as_ptr(),
223 axes,
224 num_axes,
225 options.keep_dims,
226 stream.as_ref().as_ptr(),
227 )
228 })
229 })
230}
231
232#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
234#[deprecated(
235 since = "0.26.0",
236 note = "use `with_stream` or `with_device` around `norm_l2` with `NormOptions`"
237)]
238pub fn norm_l2_device<'a>(
239 array: impl AsRef<Array>,
240 #[optional] axes: impl IntoOption<&'a [i32]>,
241 #[optional] keep_dims: impl Into<Option<bool>>,
242 #[optional] stream: impl AsRef<Stream>,
243) -> Result<Array> {
244 let options = legacy_norm_options(axes.into_option(), keep_dims.into());
245 with_stream(stream.as_ref(), || norm_l2(array, options))
246}
247pub fn qr(a: impl AsRef<Array>) -> Result<(Array, Array)> {
367 let stream = Stream::thread_local_or_default();
368 <(Array, Array)>::try_from_op(|(res_0, res_1)| unsafe {
369 mlx_sys::mlx_linalg_qr(res_0, res_1, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
370 })
371}
372
373#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
375#[deprecated(
376 since = "0.26.0",
377 note = "use `with_stream` or `with_device` around `qr`"
378)]
379pub fn qr_device(
380 a: impl AsRef<Array>,
381 #[optional] stream: impl AsRef<Stream>,
382) -> Result<(Array, Array)> {
383 crate::with_stream(stream.as_ref(), || qr(a))
384}
385
386pub fn svd(array: impl AsRef<Array>) -> Result<(Array, Array, Array)> {
416 let stream = Stream::thread_local_or_default();
417 let v = VectorArray::try_from_op(|res| unsafe {
418 mlx_sys::mlx_linalg_svd(res, array.as_ref().as_ptr(), true, stream.as_ref().as_ptr())
419 })?;
420
421 let vals: SmallVec<[Array; 3]> = v.try_into_values()?;
422 let mut iter = vals.into_iter();
423 let u = iter.next().unwrap();
424 let s = iter.next().unwrap();
425 let vt = iter.next().unwrap();
426
427 Ok((u, s, vt))
428}
429
430#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
432#[deprecated(
433 since = "0.26.0",
434 note = "use `with_stream` or `with_device` around `svd`"
435)]
436pub fn svd_device(
437 array: impl AsRef<Array>,
438 #[optional] stream: impl AsRef<Stream>,
439) -> Result<(Array, Array, Array)> {
440 crate::with_stream(stream.as_ref(), || svd(array))
441}
442
443pub fn inv(a: impl AsRef<Array>) -> Result<Array> {
467 let stream = Stream::thread_local_or_default();
468 Array::try_from_op(|res| unsafe {
469 mlx_sys::mlx_linalg_inv(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
470 })
471}
472
473#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
475#[deprecated(
476 since = "0.26.0",
477 note = "use `with_stream` or `with_device` around `inv`"
478)]
479pub fn inv_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
480 crate::with_stream(stream.as_ref(), || inv(a))
481}
482
483pub fn cholesky(a: impl AsRef<Array>, upper: Option<bool>) -> Result<Array> {
497 let stream = Stream::thread_local_or_default();
498 let upper = upper.unwrap_or(false);
499 Array::try_from_op(|res| unsafe {
500 mlx_sys::mlx_linalg_cholesky(res, a.as_ref().as_ptr(), upper, stream.as_ref().as_ptr())
501 })
502}
503
504#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
506#[deprecated(
507 since = "0.26.0",
508 note = "use `with_stream` or `with_device` around `cholesky`"
509)]
510pub fn cholesky_device(
511 a: impl AsRef<Array>,
512 #[optional] upper: Option<bool>,
513 #[optional] stream: impl AsRef<Stream>,
514) -> Result<Array> {
515 crate::with_stream(stream.as_ref(), || cholesky(a, upper))
516}
517
518pub fn cholesky_inv(a: impl AsRef<Array>, upper: Option<bool>) -> Result<Array> {
522 let stream = Stream::thread_local_or_default();
523 let upper = upper.unwrap_or(false);
524 Array::try_from_op(|res| unsafe {
525 mlx_sys::mlx_linalg_cholesky_inv(res, a.as_ref().as_ptr(), upper, stream.as_ref().as_ptr())
526 })
527}
528
529#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
531#[deprecated(
532 since = "0.26.0",
533 note = "use `with_stream` or `with_device` around `cholesky_inv`"
534)]
535pub fn cholesky_inv_device(
536 a: impl AsRef<Array>,
537 #[optional] upper: Option<bool>,
538 #[optional] stream: impl AsRef<Stream>,
539) -> Result<Array> {
540 crate::with_stream(stream.as_ref(), || cholesky_inv(a, upper))
541}
542
543pub fn cross(a: impl AsRef<Array>, b: impl AsRef<Array>, axis: Option<i32>) -> Result<Array> {
548 let stream = Stream::thread_local_or_default();
549 let axis = axis.unwrap_or(-1);
550 Array::try_from_op(|res| unsafe {
551 mlx_sys::mlx_linalg_cross(
552 res,
553 a.as_ref().as_ptr(),
554 b.as_ref().as_ptr(),
555 axis,
556 stream.as_ref().as_ptr(),
557 )
558 })
559}
560
561#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
563#[deprecated(
564 since = "0.26.0",
565 note = "use `with_stream` or `with_device` around `cross`"
566)]
567pub fn cross_device(
568 a: impl AsRef<Array>,
569 b: impl AsRef<Array>,
570 #[optional] axis: Option<i32>,
571 #[optional] stream: impl AsRef<Stream>,
572) -> Result<Array> {
573 crate::with_stream(stream.as_ref(), || cross(a, b, axis))
574}
575
576pub fn eigh(a: impl AsRef<Array>, uplo: Option<&str>) -> Result<(Array, Array)> {
582 let stream = Stream::thread_local_or_default();
583 let a = a.as_ref();
584 let uplo = CString::new(uplo.unwrap_or("L")).map_err(|e| Exception::custom(format!("{e}")))?;
585
586 <(Array, Array) as Guarded>::try_from_op(|(res_0, res_1)| unsafe {
587 mlx_sys::mlx_linalg_eigh(
588 res_0,
589 res_1,
590 a.as_ptr(),
591 uplo.as_ptr(),
592 stream.as_ref().as_ptr(),
593 )
594 })
595}
596
597#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
599#[deprecated(
600 since = "0.26.0",
601 note = "use `with_stream` or `with_device` around `eigh`"
602)]
603pub fn eigh_device(
604 a: impl AsRef<Array>,
605 #[optional] uplo: Option<&str>,
606 #[optional] stream: impl AsRef<Stream>,
607) -> Result<(Array, Array)> {
608 crate::with_stream(stream.as_ref(), || eigh(a, uplo))
609}
610
611pub fn eigvalsh(a: impl AsRef<Array>, uplo: Option<&str>) -> Result<Array> {
616 let stream = Stream::thread_local_or_default();
617 let a = a.as_ref();
618 let uplo = CString::new(uplo.unwrap_or("L")).map_err(|e| Exception::custom(format!("{e}")))?;
619 Array::try_from_op(|res| unsafe {
620 mlx_sys::mlx_linalg_eigvalsh(res, a.as_ptr(), uplo.as_ptr(), stream.as_ref().as_ptr())
621 })
622}
623
624#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
626#[deprecated(
627 since = "0.26.0",
628 note = "use `with_stream` or `with_device` around `eigvalsh`"
629)]
630pub fn eigvalsh_device(
631 a: impl AsRef<Array>,
632 #[optional] uplo: Option<&str>,
633 #[optional] stream: impl AsRef<Stream>,
634) -> Result<Array> {
635 crate::with_stream(stream.as_ref(), || eigvalsh(a, uplo))
636}
637
638pub fn eig(a: impl AsRef<Array>) -> Result<(Array, Array)> {
668 let stream = Stream::thread_local_or_default();
669 <(Array, Array) as Guarded>::try_from_op(|(res_0, res_1)| unsafe {
670 mlx_sys::mlx_linalg_eig(res_0, res_1, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
671 })
672}
673
674#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
676#[deprecated(
677 since = "0.26.0",
678 note = "use `with_stream` or `with_device` around `eig`"
679)]
680pub fn eig_device(
681 a: impl AsRef<Array>,
682 #[optional] stream: impl AsRef<Stream>,
683) -> Result<(Array, Array)> {
684 crate::with_stream(stream.as_ref(), || eig(a))
685}
686
687pub fn eigvals(a: impl AsRef<Array>) -> Result<Array> {
714 let stream = Stream::thread_local_or_default();
715 Array::try_from_op(|res| unsafe {
716 mlx_sys::mlx_linalg_eigvals(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
717 })
718}
719
720#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
722#[deprecated(
723 since = "0.26.0",
724 note = "use `with_stream` or `with_device` around `eigvals`"
725)]
726pub fn eigvals_device(
727 a: impl AsRef<Array>,
728 #[optional] stream: impl AsRef<Stream>,
729) -> Result<Array> {
730 crate::with_stream(stream.as_ref(), || eigvals(a))
731}
732
733pub fn pinv(a: impl AsRef<Array>) -> Result<Array> {
735 let stream = Stream::thread_local_or_default();
736 Array::try_from_op(|res| unsafe {
737 mlx_sys::mlx_linalg_pinv(res, a.as_ref().as_ptr(), stream.as_ref().as_ptr())
738 })
739}
740
741#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
743#[deprecated(
744 since = "0.26.0",
745 note = "use `with_stream` or `with_device` around `pinv`"
746)]
747pub fn pinv_device(a: impl AsRef<Array>, #[optional] stream: impl AsRef<Stream>) -> Result<Array> {
748 crate::with_stream(stream.as_ref(), || pinv(a))
749}
750
751pub fn tri_inv(a: impl AsRef<Array>, upper: Option<bool>) -> Result<Array> {
756 let stream = Stream::thread_local_or_default();
757 let upper = upper.unwrap_or(false);
758 Array::try_from_op(|res| unsafe {
759 mlx_sys::mlx_linalg_tri_inv(res, a.as_ref().as_ptr(), upper, stream.as_ref().as_ptr())
760 })
761}
762
763#[generate_macro(customize(forwarding_shim = true, root = "$crate::linalg"))]
765#[deprecated(
766 since = "0.26.0",
767 note = "use `with_stream` or `with_device` around `tri_inv`"
768)]
769pub fn tri_inv_device(
770 a: impl AsRef<Array>,
771 #[optional] upper: Option<bool>,
772 #[optional] stream: impl AsRef<Stream>,
773) -> Result<Array> {
774 crate::with_stream(stream.as_ref(), || tri_inv(a, upper))
775}
776
777pub 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}