1use num_complex::{Complex32, Complex64};
8use tenferro_tensor::{
9 CompareDir, DType, DotGeneralConfig, Tensor, TensorRead, TensorScalar, TensorWrite, TypedTensor,
10};
11
12use crate::extension::{
13 apply_eigh_gauge, apply_qr_gauge, apply_svd_gauge, validate_derivative_eps, EighOptions,
14 QrOptions, SvdOptions,
15};
16use crate::LinalgBackend;
17
18pub trait LinalgScalar: TensorScalar + private::Sealed {
29 type Complex: TensorScalar;
31}
32
33mod private {
34 pub trait Sealed {}
35 impl Sealed for f32 {}
36 impl Sealed for f64 {}
37 impl Sealed for num_complex::Complex32 {}
38 impl Sealed for num_complex::Complex64 {}
39}
40
41impl LinalgScalar for f32 {
42 type Complex = Complex32;
43}
44impl LinalgScalar for f64 {
45 type Complex = Complex64;
46}
47impl LinalgScalar for Complex32 {
48 type Complex = Complex32;
49}
50impl LinalgScalar for Complex64 {
51 type Complex = Complex64;
52}
53
54pub type TypedSvd<T> = (
62 TypedTensor<T>,
63 TypedTensor<<T as TensorScalar>::Real>,
64 TypedTensor<T>,
65);
66pub type TypedLu<T> = (
74 TypedTensor<T>,
75 TypedTensor<T>,
76 TypedTensor<T>,
77 TypedTensor<<T as TensorScalar>::Real>,
78);
79pub type TypedFullPivLu<T> = (
87 TypedTensor<T>,
88 TypedTensor<T>,
89 TypedTensor<T>,
90 TypedTensor<T>,
91 TypedTensor<<T as TensorScalar>::Real>,
92);
93pub type TypedEig<T> = (
101 TypedTensor<<T as LinalgScalar>::Complex>,
102 TypedTensor<<T as LinalgScalar>::Complex>,
103);
104
105pub trait TensorLinalgExt {
124 fn svd<B: LinalgBackend>(
128 &self,
129 backend: &mut B,
130 ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor)>;
131 fn svd_with_options<B: LinalgBackend>(
135 &self,
136 options: SvdOptions,
137 backend: &mut B,
138 ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor)>;
139 fn qr<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<(Tensor, Tensor)>;
143 fn qr_with_options<B: LinalgBackend>(
147 &self,
148 options: QrOptions,
149 backend: &mut B,
150 ) -> tenferro_tensor::Result<(Tensor, Tensor)>;
151 fn lu<B: LinalgBackend>(
155 &self,
156 backend: &mut B,
157 ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor, Tensor)>;
158 fn full_piv_lu<B: LinalgBackend>(
162 &self,
163 backend: &mut B,
164 ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor, Tensor, Tensor)>;
165 fn full_piv_lu_solve<B: LinalgBackend>(
169 &self,
170 b: &Tensor,
171 backend: &mut B,
172 ) -> tenferro_tensor::Result<Tensor>;
173 fn solve<B: LinalgBackend>(
177 &self,
178 b: &Tensor,
179 backend: &mut B,
180 ) -> tenferro_tensor::Result<Tensor>;
181 fn cholesky<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<Tensor>;
185 fn eigh<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<(Tensor, Tensor)>;
189 fn eigh_with_options<B: LinalgBackend>(
193 &self,
194 options: EighOptions,
195 backend: &mut B,
196 ) -> tenferro_tensor::Result<(Tensor, Tensor)>;
197 fn eig<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<(Tensor, Tensor)>;
201 #[allow(clippy::too_many_arguments)]
205 fn triangular_solve<B: LinalgBackend>(
206 &self,
207 b: &Tensor,
208 left_side: bool,
209 lower: bool,
210 transpose_a: bool,
211 unit_diagonal: bool,
212 backend: &mut B,
213 ) -> tenferro_tensor::Result<Tensor>;
214 fn slogdet<B: LinalgBackend>(
218 &self,
219 backend: &mut B,
220 ) -> tenferro_tensor::Result<(Tensor, Tensor)>;
221 fn det<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<Tensor>;
225 fn inv<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<Tensor>;
229 fn eigvalsh<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<Tensor>;
233 fn eigvals<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<Tensor>;
237 fn pinv<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<Tensor>;
241 fn pinv_with_rtol<B: LinalgBackend>(
245 &self,
246 rtol: f64,
247 backend: &mut B,
248 ) -> tenferro_tensor::Result<Tensor>;
249 fn norm<B: LinalgBackend>(
253 &self,
254 ord: Option<f64>,
255 dim: Option<&[usize]>,
256 keepdim: bool,
257 backend: &mut B,
258 ) -> tenferro_tensor::Result<Tensor>;
259}
260
261pub trait TensorReadLinalgExt {
282 fn svd_read<B: LinalgBackend>(
286 self,
287 backend: &mut B,
288 ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor)>;
289 fn svd_with_options_read<B: LinalgBackend>(
293 self,
294 options: SvdOptions,
295 backend: &mut B,
296 ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor)>;
297 fn qr_read<B: LinalgBackend>(
301 self,
302 backend: &mut B,
303 ) -> tenferro_tensor::Result<(Tensor, Tensor)>;
304 fn qr_with_options_read<B: LinalgBackend>(
308 self,
309 options: QrOptions,
310 backend: &mut B,
311 ) -> tenferro_tensor::Result<(Tensor, Tensor)>;
312 fn lu_read<B: LinalgBackend>(
316 self,
317 backend: &mut B,
318 ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor, Tensor)>;
319 fn full_piv_lu_read<B: LinalgBackend>(
323 self,
324 backend: &mut B,
325 ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor, Tensor, Tensor)>;
326 fn full_piv_lu_solve_read<B: LinalgBackend>(
330 self,
331 b: TensorRead<'_>,
332 backend: &mut B,
333 ) -> tenferro_tensor::Result<Tensor>;
334 fn solve_read<B: LinalgBackend>(
338 self,
339 b: TensorRead<'_>,
340 backend: &mut B,
341 ) -> tenferro_tensor::Result<Tensor>;
342 fn solve_read_into<B: LinalgBackend>(
356 self,
357 b: TensorRead<'_>,
358 out: TensorWrite<'_>,
359 backend: &mut B,
360 ) -> tenferro_tensor::Result<()>
361 where
362 Self: Sized,
363 {
364 let _ = (self, b, out, backend);
365 Err(tenferro_tensor::Error::unsupported(
366 "solve_read_into",
367 "this tensor-read extension implementation does not accept borrowed solve targets",
368 ))
369 }
370 fn cholesky_read<B: LinalgBackend>(self, backend: &mut B) -> tenferro_tensor::Result<Tensor>;
374 fn eigh_read<B: LinalgBackend>(
378 self,
379 backend: &mut B,
380 ) -> tenferro_tensor::Result<(Tensor, Tensor)>;
381 fn eigh_with_options_read<B: LinalgBackend>(
385 self,
386 options: EighOptions,
387 backend: &mut B,
388 ) -> tenferro_tensor::Result<(Tensor, Tensor)>;
389 fn eig_read<B: LinalgBackend>(
393 self,
394 backend: &mut B,
395 ) -> tenferro_tensor::Result<(Tensor, Tensor)>;
396 #[allow(clippy::too_many_arguments)]
400 fn triangular_solve_read<B: LinalgBackend>(
401 self,
402 b: TensorRead<'_>,
403 left_side: bool,
404 lower: bool,
405 transpose_a: bool,
406 unit_diagonal: bool,
407 backend: &mut B,
408 ) -> tenferro_tensor::Result<Tensor>;
409 fn slogdet_read<B: LinalgBackend>(
413 self,
414 backend: &mut B,
415 ) -> tenferro_tensor::Result<(Tensor, Tensor)>;
416 fn det_read<B: LinalgBackend>(self, backend: &mut B) -> tenferro_tensor::Result<Tensor>;
420 fn inv_read<B: LinalgBackend>(self, backend: &mut B) -> tenferro_tensor::Result<Tensor>;
424 fn eigvalsh_read<B: LinalgBackend>(self, backend: &mut B) -> tenferro_tensor::Result<Tensor>;
428 fn eigvals_read<B: LinalgBackend>(self, backend: &mut B) -> tenferro_tensor::Result<Tensor>;
432 fn pinv_read<B: LinalgBackend>(self, backend: &mut B) -> tenferro_tensor::Result<Tensor>;
436 fn pinv_with_rtol_read<B: LinalgBackend>(
440 self,
441 rtol: f64,
442 backend: &mut B,
443 ) -> tenferro_tensor::Result<Tensor>;
444 fn norm_read<B: LinalgBackend>(
448 self,
449 ord: Option<f64>,
450 dim: Option<&[usize]>,
451 keepdim: bool,
452 backend: &mut B,
453 ) -> tenferro_tensor::Result<Tensor>;
454}
455
456pub trait TypedTensorLinalgExt<T: LinalgScalar> {
481 fn svd<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<TypedSvd<T>>;
485 fn svd_with_options<B: LinalgBackend>(
489 &self,
490 options: SvdOptions,
491 backend: &mut B,
492 ) -> tenferro_tensor::Result<TypedSvd<T>>;
493 fn qr<B: LinalgBackend>(
497 &self,
498 backend: &mut B,
499 ) -> tenferro_tensor::Result<(TypedTensor<T>, TypedTensor<T>)>;
500 fn qr_with_options<B: LinalgBackend>(
504 &self,
505 options: QrOptions,
506 backend: &mut B,
507 ) -> tenferro_tensor::Result<(TypedTensor<T>, TypedTensor<T>)>;
508 fn lu<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<TypedLu<T>>;
512 fn full_piv_lu<B: LinalgBackend>(
516 &self,
517 backend: &mut B,
518 ) -> tenferro_tensor::Result<TypedFullPivLu<T>>;
519 fn full_piv_lu_solve<B: LinalgBackend>(
523 &self,
524 b: &TypedTensor<T>,
525 backend: &mut B,
526 ) -> tenferro_tensor::Result<TypedTensor<T>>;
527 fn solve<B: LinalgBackend>(
531 &self,
532 b: &TypedTensor<T>,
533 backend: &mut B,
534 ) -> tenferro_tensor::Result<TypedTensor<T>>;
535 fn cholesky<B: LinalgBackend>(
539 &self,
540 backend: &mut B,
541 ) -> tenferro_tensor::Result<TypedTensor<T>>;
542 fn eigh<B: LinalgBackend>(
546 &self,
547 backend: &mut B,
548 ) -> tenferro_tensor::Result<(TypedTensor<<T as TensorScalar>::Real>, TypedTensor<T>)>;
549 fn eigh_with_options<B: LinalgBackend>(
553 &self,
554 options: EighOptions,
555 backend: &mut B,
556 ) -> tenferro_tensor::Result<(TypedTensor<<T as TensorScalar>::Real>, TypedTensor<T>)>;
557 fn eig<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<TypedEig<T>>;
561 #[allow(clippy::too_many_arguments)]
565 fn triangular_solve<B: LinalgBackend>(
566 &self,
567 b: &TypedTensor<T>,
568 left_side: bool,
569 lower: bool,
570 transpose_a: bool,
571 unit_diagonal: bool,
572 backend: &mut B,
573 ) -> tenferro_tensor::Result<TypedTensor<T>>;
574 fn slogdet<B: LinalgBackend>(
578 &self,
579 backend: &mut B,
580 ) -> tenferro_tensor::Result<(TypedTensor<T>, TypedTensor<<T as TensorScalar>::Real>)>;
581 fn det<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<TypedTensor<T>>;
585 fn inv<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<TypedTensor<T>>;
589 fn eigvalsh<B: LinalgBackend>(
593 &self,
594 backend: &mut B,
595 ) -> tenferro_tensor::Result<TypedTensor<<T as TensorScalar>::Real>>;
596 fn eigvals<B: LinalgBackend>(
600 &self,
601 backend: &mut B,
602 ) -> tenferro_tensor::Result<TypedTensor<T::Complex>>;
603 fn pinv<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<TypedTensor<T>>;
607 fn pinv_with_rtol<B: LinalgBackend>(
611 &self,
612 rtol: f64,
613 backend: &mut B,
614 ) -> tenferro_tensor::Result<TypedTensor<T>>;
615 fn norm<B: LinalgBackend>(
619 &self,
620 ord: Option<f64>,
621 dim: Option<&[usize]>,
622 keepdim: bool,
623 backend: &mut B,
624 ) -> tenferro_tensor::Result<TypedTensor<<T as TensorScalar>::Real>>;
625}
626
627impl TensorLinalgExt for Tensor {
628 fn svd<B: LinalgBackend>(
629 &self,
630 backend: &mut B,
631 ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor)> {
632 three(backend.svd(self)?, "svd")
633 }
634 fn svd_with_options<B: LinalgBackend>(
635 &self,
636 options: SvdOptions,
637 backend: &mut B,
638 ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor)> {
639 three(backend.svd_with_options(self, options)?, "svd_with_options")
640 }
641 fn qr<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<(Tensor, Tensor)> {
642 two(backend.qr(self)?, "qr")
643 }
644 fn qr_with_options<B: LinalgBackend>(
645 &self,
646 options: QrOptions,
647 backend: &mut B,
648 ) -> tenferro_tensor::Result<(Tensor, Tensor)> {
649 two(backend.qr_with_options(self, options)?, "qr_with_options")
650 }
651 fn lu<B: LinalgBackend>(
652 &self,
653 backend: &mut B,
654 ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor, Tensor)> {
655 four(backend.lu(self)?, "lu")
656 }
657 fn full_piv_lu<B: LinalgBackend>(
658 &self,
659 backend: &mut B,
660 ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor, Tensor, Tensor)> {
661 five(backend.full_piv_lu(self)?, "full_piv_lu")
662 }
663 fn full_piv_lu_solve<B: LinalgBackend>(
664 &self,
665 b: &Tensor,
666 backend: &mut B,
667 ) -> tenferro_tensor::Result<Tensor> {
668 backend.full_piv_lu_solve(self, b, false)
669 }
670 fn solve<B: LinalgBackend>(
671 &self,
672 b: &Tensor,
673 backend: &mut B,
674 ) -> tenferro_tensor::Result<Tensor> {
675 backend.solve(self, b)
676 }
677 fn cholesky<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<Tensor> {
678 backend.cholesky(self)
679 }
680 fn eigh<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<(Tensor, Tensor)> {
681 two(backend.eigh(self)?, "eigh")
682 }
683 fn eigh_with_options<B: LinalgBackend>(
684 &self,
685 options: EighOptions,
686 backend: &mut B,
687 ) -> tenferro_tensor::Result<(Tensor, Tensor)> {
688 two(
689 backend.eigh_with_options(self, options)?,
690 "eigh_with_options",
691 )
692 }
693 fn eig<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<(Tensor, Tensor)> {
694 two(backend.eig(self)?, "eig")
695 }
696 fn triangular_solve<B: LinalgBackend>(
697 &self,
698 b: &Tensor,
699 left_side: bool,
700 lower: bool,
701 transpose_a: bool,
702 unit_diagonal: bool,
703 backend: &mut B,
704 ) -> tenferro_tensor::Result<Tensor> {
705 backend.triangular_solve(self, b, left_side, lower, transpose_a, unit_diagonal)
706 }
707 fn slogdet<B: LinalgBackend>(
708 &self,
709 backend: &mut B,
710 ) -> tenferro_tensor::Result<(Tensor, Tensor)> {
711 slogdet_from_lu(four(backend.lu(self)?, "slogdet")?, backend)
712 }
713 fn det<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<Tensor> {
714 det_impl(self.slogdet(backend)?, backend)
715 }
716 fn inv<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<Tensor> {
717 inv_owned(self, backend)
718 }
719 fn eigvalsh<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<Tensor> {
720 Ok(self.eigh(backend)?.0)
721 }
722 fn eigvals<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<Tensor> {
723 backend.eig_values(self)
724 }
725 fn pinv<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<Tensor> {
726 pinv_owned(self, None, backend)
727 }
728 fn pinv_with_rtol<B: LinalgBackend>(
729 &self,
730 rtol: f64,
731 backend: &mut B,
732 ) -> tenferro_tensor::Result<Tensor> {
733 pinv_owned(self, Some(rtol), backend)
734 }
735 fn norm<B: LinalgBackend>(
736 &self,
737 ord: Option<f64>,
738 dim: Option<&[usize]>,
739 keepdim: bool,
740 backend: &mut B,
741 ) -> tenferro_tensor::Result<Tensor> {
742 norm_from_read(TensorRead::from_tensor(self), ord, dim, keepdim, backend)
743 }
744}
745
746impl TensorReadLinalgExt for TensorRead<'_> {
747 fn svd_read<B: LinalgBackend>(
748 self,
749 backend: &mut B,
750 ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor)> {
751 three(backend.svd_read(self)?, "svd_read")
752 }
753 fn svd_with_options_read<B: LinalgBackend>(
754 self,
755 options: SvdOptions,
756 backend: &mut B,
757 ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor)> {
758 validate_derivative_eps("svd_with_options_read", options.derivative_eps)?;
759 let mut out = backend.svd_read(self)?;
760 apply_svd_gauge(options.gauge, &mut out)?;
761 three(out, "svd_with_options_read")
762 }
763 fn qr_read<B: LinalgBackend>(
764 self,
765 backend: &mut B,
766 ) -> tenferro_tensor::Result<(Tensor, Tensor)> {
767 two(backend.qr_read(self)?, "qr_read")
768 }
769 fn qr_with_options_read<B: LinalgBackend>(
770 self,
771 options: QrOptions,
772 backend: &mut B,
773 ) -> tenferro_tensor::Result<(Tensor, Tensor)> {
774 let mut out = backend.qr_read(self)?;
775 apply_qr_gauge(options.gauge, &mut out)?;
776 two(out, "qr_with_options_read")
777 }
778 fn lu_read<B: LinalgBackend>(
779 self,
780 backend: &mut B,
781 ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor, Tensor)> {
782 four(backend.lu_read(self)?, "lu_read")
783 }
784 fn full_piv_lu_read<B: LinalgBackend>(
785 self,
786 backend: &mut B,
787 ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor, Tensor, Tensor)> {
788 five(backend.full_piv_lu_read(self)?, "full_piv_lu_read")
789 }
790 fn full_piv_lu_solve_read<B: LinalgBackend>(
791 self,
792 b: TensorRead<'_>,
793 backend: &mut B,
794 ) -> tenferro_tensor::Result<Tensor> {
795 let b_is_vector = b.shape().len() + 1 == self.shape().len();
796 let original_b_shape = b.shape().to_vec();
797 let vector_as_matrix = if b_is_vector {
798 let mut shape = vec![b.shape()[0], 1];
799 shape.extend_from_slice(&b.shape()[1..]);
800 Some(backend.reshape_read(b.clone(), &shape)?)
801 } else {
802 None
803 };
804 let b = vector_as_matrix.as_ref().map_or(b, TensorRead::from_tensor);
805 let (p, l, u, q, _parity) = self.full_piv_lu_read(backend)?;
806 let pb = linalg_matmul_read(&p, b, false, backend)?;
807 let z = backend.triangular_solve_read(
808 TensorRead::from_tensor(&l),
809 TensorRead::from_tensor(&pb),
810 true,
811 true,
812 false,
813 true,
814 )?;
815 let w = backend.triangular_solve_read(
816 TensorRead::from_tensor(&u),
817 TensorRead::from_tensor(&z),
818 true,
819 false,
820 false,
821 false,
822 )?;
823 let mut perm: Vec<usize> = (0..q.shape().len()).collect();
824 perm.swap(0, 1);
825 let qt = transpose(&q, &perm, backend)?;
826 let solution = linalg_matmul_read(&qt, TensorRead::from_tensor(&w), false, backend)?;
827 if b_is_vector {
828 reshape(&solution, &original_b_shape, backend)
829 } else {
830 Ok(solution)
831 }
832 }
833 fn solve_read<B: LinalgBackend>(
834 self,
835 b: TensorRead<'_>,
836 backend: &mut B,
837 ) -> tenferro_tensor::Result<Tensor> {
838 backend.solve_read(self, b)
839 }
840 fn solve_read_into<B: LinalgBackend>(
841 self,
842 b: TensorRead<'_>,
843 out: TensorWrite<'_>,
844 backend: &mut B,
845 ) -> tenferro_tensor::Result<()> {
846 backend.solve_read_into(self, b, out)
847 }
848 fn cholesky_read<B: LinalgBackend>(self, backend: &mut B) -> tenferro_tensor::Result<Tensor> {
849 backend.cholesky_read(self)
850 }
851 fn eigh_read<B: LinalgBackend>(
852 self,
853 backend: &mut B,
854 ) -> tenferro_tensor::Result<(Tensor, Tensor)> {
855 two(backend.eigh_read(self)?, "eigh_read")
856 }
857 fn eigh_with_options_read<B: LinalgBackend>(
858 self,
859 options: EighOptions,
860 backend: &mut B,
861 ) -> tenferro_tensor::Result<(Tensor, Tensor)> {
862 validate_derivative_eps("eigh_with_options_read", options.derivative_eps)?;
863 let mut out = backend.eigh_read(self)?;
864 apply_eigh_gauge(options.gauge, &mut out)?;
865 two(out, "eigh_with_options_read")
866 }
867 fn eig_read<B: LinalgBackend>(
868 self,
869 backend: &mut B,
870 ) -> tenferro_tensor::Result<(Tensor, Tensor)> {
871 two(backend.eig_read(self)?, "eig_read")
872 }
873 fn triangular_solve_read<B: LinalgBackend>(
874 self,
875 b: TensorRead<'_>,
876 left_side: bool,
877 lower: bool,
878 transpose_a: bool,
879 unit_diagonal: bool,
880 backend: &mut B,
881 ) -> tenferro_tensor::Result<Tensor> {
882 backend.triangular_solve_read(self, b, left_side, lower, transpose_a, unit_diagonal)
883 }
884 fn slogdet_read<B: LinalgBackend>(
885 self,
886 backend: &mut B,
887 ) -> tenferro_tensor::Result<(Tensor, Tensor)> {
888 slogdet_from_lu(four(backend.lu_read(self)?, "slogdet_read")?, backend)
889 }
890 fn det_read<B: LinalgBackend>(self, backend: &mut B) -> tenferro_tensor::Result<Tensor> {
891 det_impl(self.slogdet_read(backend)?, backend)
892 }
893 fn inv_read<B: LinalgBackend>(self, backend: &mut B) -> tenferro_tensor::Result<Tensor> {
894 inv_read(self, backend)
895 }
896 fn eigvalsh_read<B: LinalgBackend>(self, backend: &mut B) -> tenferro_tensor::Result<Tensor> {
897 Ok(self.eigh_read(backend)?.0)
898 }
899 fn eigvals_read<B: LinalgBackend>(self, backend: &mut B) -> tenferro_tensor::Result<Tensor> {
900 Ok(self.eig_read(backend)?.0)
901 }
902 fn pinv_read<B: LinalgBackend>(self, backend: &mut B) -> tenferro_tensor::Result<Tensor> {
903 pinv_read(self, None, backend)
904 }
905 fn pinv_with_rtol_read<B: LinalgBackend>(
906 self,
907 rtol: f64,
908 backend: &mut B,
909 ) -> tenferro_tensor::Result<Tensor> {
910 pinv_read(self, Some(rtol), backend)
911 }
912 fn norm_read<B: LinalgBackend>(
913 self,
914 ord: Option<f64>,
915 dim: Option<&[usize]>,
916 keepdim: bool,
917 backend: &mut B,
918 ) -> tenferro_tensor::Result<Tensor> {
919 norm_from_read(self, ord, dim, keepdim, backend)
920 }
921}
922
923impl<T: LinalgScalar> TypedTensorLinalgExt<T> for TypedTensor<T> {
924 fn svd<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<TypedSvd<T>> {
925 typed_svd(T::tensor_read(self).svd_read(backend)?)
926 }
927
928 fn svd_with_options<B: LinalgBackend>(
929 &self,
930 options: SvdOptions,
931 backend: &mut B,
932 ) -> tenferro_tensor::Result<TypedSvd<T>> {
933 typed_svd(T::tensor_read(self).svd_with_options_read(options, backend)?)
934 }
935
936 fn qr<B: LinalgBackend>(
937 &self,
938 backend: &mut B,
939 ) -> tenferro_tensor::Result<(TypedTensor<T>, TypedTensor<T>)> {
940 typed_pair_same(T::tensor_read(self).qr_read(backend)?)
941 }
942
943 fn qr_with_options<B: LinalgBackend>(
944 &self,
945 options: QrOptions,
946 backend: &mut B,
947 ) -> tenferro_tensor::Result<(TypedTensor<T>, TypedTensor<T>)> {
948 typed_pair_same(T::tensor_read(self).qr_with_options_read(options, backend)?)
949 }
950
951 fn lu<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<TypedLu<T>> {
952 let (p, l, u, parity) = T::tensor_read(self).lu_read(backend)?;
953 Ok((
954 typed_output::<T>(p)?,
955 typed_output::<T>(l)?,
956 typed_output::<T>(u)?,
957 typed_output::<<T as TensorScalar>::Real>(parity)?,
958 ))
959 }
960
961 fn full_piv_lu_solve<B: LinalgBackend>(
962 &self,
963 b: &TypedTensor<T>,
964 backend: &mut B,
965 ) -> tenferro_tensor::Result<TypedTensor<T>> {
966 typed_output::<T>(T::tensor_read(self).full_piv_lu_solve_read(T::tensor_read(b), backend)?)
967 }
968
969 fn full_piv_lu<B: LinalgBackend>(
970 &self,
971 backend: &mut B,
972 ) -> tenferro_tensor::Result<TypedFullPivLu<T>> {
973 let (p, l, u, q, parity) = T::tensor_read(self).full_piv_lu_read(backend)?;
974 Ok((
975 typed_output::<T>(p)?,
976 typed_output::<T>(l)?,
977 typed_output::<T>(u)?,
978 typed_output::<T>(q)?,
979 typed_output::<<T as TensorScalar>::Real>(parity)?,
980 ))
981 }
982
983 fn solve<B: LinalgBackend>(
984 &self,
985 b: &TypedTensor<T>,
986 backend: &mut B,
987 ) -> tenferro_tensor::Result<TypedTensor<T>> {
988 typed_output::<T>(T::tensor_read(self).solve_read(T::tensor_read(b), backend)?)
989 }
990
991 fn cholesky<B: LinalgBackend>(
992 &self,
993 backend: &mut B,
994 ) -> tenferro_tensor::Result<TypedTensor<T>> {
995 typed_output::<T>(T::tensor_read(self).cholesky_read(backend)?)
996 }
997
998 fn eigh<B: LinalgBackend>(
999 &self,
1000 backend: &mut B,
1001 ) -> tenferro_tensor::Result<(TypedTensor<<T as TensorScalar>::Real>, TypedTensor<T>)> {
1002 typed_eigh(T::tensor_read(self).eigh_read(backend)?)
1003 }
1004
1005 fn eigh_with_options<B: LinalgBackend>(
1006 &self,
1007 options: EighOptions,
1008 backend: &mut B,
1009 ) -> tenferro_tensor::Result<(TypedTensor<<T as TensorScalar>::Real>, TypedTensor<T>)> {
1010 typed_eigh(T::tensor_read(self).eigh_with_options_read(options, backend)?)
1011 }
1012
1013 fn eig<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<TypedEig<T>> {
1014 let (values, vectors) = T::tensor_read(self).eig_read(backend)?;
1015 Ok((
1016 typed_output::<T::Complex>(values)?,
1017 typed_output::<T::Complex>(vectors)?,
1018 ))
1019 }
1020
1021 fn triangular_solve<B: LinalgBackend>(
1022 &self,
1023 b: &TypedTensor<T>,
1024 left_side: bool,
1025 lower: bool,
1026 transpose_a: bool,
1027 unit_diagonal: bool,
1028 backend: &mut B,
1029 ) -> tenferro_tensor::Result<TypedTensor<T>> {
1030 typed_output::<T>(T::tensor_read(self).triangular_solve_read(
1031 T::tensor_read(b),
1032 left_side,
1033 lower,
1034 transpose_a,
1035 unit_diagonal,
1036 backend,
1037 )?)
1038 }
1039
1040 fn slogdet<B: LinalgBackend>(
1041 &self,
1042 backend: &mut B,
1043 ) -> tenferro_tensor::Result<(TypedTensor<T>, TypedTensor<<T as TensorScalar>::Real>)> {
1044 let (sign, logabsdet) = T::tensor_read(self).slogdet_read(backend)?;
1045 Ok((
1046 typed_output::<T>(sign)?,
1047 typed_output::<<T as TensorScalar>::Real>(logabsdet)?,
1048 ))
1049 }
1050
1051 fn det<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<TypedTensor<T>> {
1052 typed_output::<T>(T::tensor_read(self).det_read(backend)?)
1053 }
1054
1055 fn inv<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<TypedTensor<T>> {
1056 typed_output::<T>(T::tensor_read(self).inv_read(backend)?)
1057 }
1058
1059 fn eigvalsh<B: LinalgBackend>(
1060 &self,
1061 backend: &mut B,
1062 ) -> tenferro_tensor::Result<TypedTensor<<T as TensorScalar>::Real>> {
1063 typed_output::<<T as TensorScalar>::Real>(T::tensor_read(self).eigvalsh_read(backend)?)
1064 }
1065
1066 fn eigvals<B: LinalgBackend>(
1067 &self,
1068 backend: &mut B,
1069 ) -> tenferro_tensor::Result<TypedTensor<T::Complex>> {
1070 typed_output::<T::Complex>(T::tensor_read(self).eigvals_read(backend)?)
1071 }
1072
1073 fn pinv<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<TypedTensor<T>> {
1074 typed_output::<T>(T::tensor_read(self).pinv_read(backend)?)
1075 }
1076
1077 fn pinv_with_rtol<B: LinalgBackend>(
1078 &self,
1079 rtol: f64,
1080 backend: &mut B,
1081 ) -> tenferro_tensor::Result<TypedTensor<T>> {
1082 typed_output::<T>(T::tensor_read(self).pinv_with_rtol_read(rtol, backend)?)
1083 }
1084
1085 fn norm<B: LinalgBackend>(
1086 &self,
1087 ord: Option<f64>,
1088 dim: Option<&[usize]>,
1089 keepdim: bool,
1090 backend: &mut B,
1091 ) -> tenferro_tensor::Result<TypedTensor<<T as TensorScalar>::Real>> {
1092 typed_output::<<T as TensorScalar>::Real>(
1093 T::tensor_read(self).norm_read(ord, dim, keepdim, backend)?,
1094 )
1095 }
1096}
1097
1098fn typed_svd<T: LinalgScalar>(
1099 (u, s, vt): (Tensor, Tensor, Tensor),
1100) -> tenferro_tensor::Result<TypedSvd<T>> {
1101 Ok((
1102 typed_output::<T>(u)?,
1103 typed_output::<<T as TensorScalar>::Real>(s)?,
1104 typed_output::<T>(vt)?,
1105 ))
1106}
1107
1108fn typed_pair_same<T: LinalgScalar>(
1109 (a, b): (Tensor, Tensor),
1110) -> tenferro_tensor::Result<(TypedTensor<T>, TypedTensor<T>)> {
1111 Ok((typed_output::<T>(a)?, typed_output::<T>(b)?))
1112}
1113
1114fn typed_eigh<T: LinalgScalar>(
1115 (values, vectors): (Tensor, Tensor),
1116) -> tenferro_tensor::Result<(TypedTensor<<T as TensorScalar>::Real>, TypedTensor<T>)> {
1117 Ok((
1118 typed_output::<<T as TensorScalar>::Real>(values)?,
1119 typed_output::<T>(vectors)?,
1120 ))
1121}
1122
1123fn typed_output<T: TensorScalar>(tensor: Tensor) -> tenferro_tensor::Result<TypedTensor<T>> {
1124 if tensor.dtype() != T::dtype() {
1125 return Err(tenferro_tensor::Error::Internal(format!(
1126 "typed linalg backend contract expected {:?}, got {:?}",
1127 T::dtype(),
1128 tensor.dtype()
1129 )));
1130 }
1131 T::into_typed(tensor)
1132}
1133
1134fn arity(name: &'static str, expected: usize, actual: usize) -> tenferro_tensor::Error {
1135 tenferro_tensor::Error::Internal(format!(
1136 "{name} backend contract expected {expected} outputs, got {actual}"
1137 ))
1138}
1139fn two(mut out: Vec<Tensor>, name: &'static str) -> tenferro_tensor::Result<(Tensor, Tensor)> {
1140 if out.len() != 2 {
1141 return Err(arity(name, 2, out.len()));
1142 }
1143 let b = out.pop().unwrap();
1144 let a = out.pop().unwrap();
1145 Ok((a, b))
1146}
1147fn three(
1148 mut out: Vec<Tensor>,
1149 name: &'static str,
1150) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor)> {
1151 if out.len() != 3 {
1152 return Err(arity(name, 3, out.len()));
1153 }
1154 let c = out.pop().unwrap();
1155 let b = out.pop().unwrap();
1156 let a = out.pop().unwrap();
1157 Ok((a, b, c))
1158}
1159fn four(
1160 mut out: Vec<Tensor>,
1161 name: &'static str,
1162) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor, Tensor)> {
1163 if out.len() != 4 {
1164 return Err(arity(name, 4, out.len()));
1165 }
1166 let d = out.pop().unwrap();
1167 let c = out.pop().unwrap();
1168 let b = out.pop().unwrap();
1169 let a = out.pop().unwrap();
1170 Ok((a, b, c, d))
1171}
1172fn five(
1173 mut out: Vec<Tensor>,
1174 name: &'static str,
1175) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor, Tensor, Tensor)> {
1176 if out.len() != 5 {
1177 return Err(arity(name, 5, out.len()));
1178 }
1179 let e = out.pop().unwrap();
1180 let d = out.pop().unwrap();
1181 let c = out.pop().unwrap();
1182 let b = out.pop().unwrap();
1183 let a = out.pop().unwrap();
1184 Ok((a, b, c, d, e))
1185}
1186
1187fn slogdet_from_lu<B: LinalgBackend>(
1190 (_p, _l, u, parity): (Tensor, Tensor, Tensor, Tensor),
1191 backend: &mut B,
1192) -> tenferro_tensor::Result<(Tensor, Tensor)> {
1193 let diag = backend.extract_diagonal(&u, 0, 1)?;
1194 let sign = backend.sign_read(TensorRead::from_tensor(&diag))?;
1195 let sign_u = backend.reduce_prod_read(TensorRead::from_tensor(&sign), &[0])?;
1196 let sign = backend.mul_read(
1197 TensorRead::from_tensor(&parity),
1198 TensorRead::from_tensor(&sign_u),
1199 )?;
1200 let abs = backend.abs_read(TensorRead::from_tensor(&diag))?;
1201 let log = backend.log_read(TensorRead::from_tensor(&abs))?;
1202 let logabsdet = backend.reduce_sum_read(TensorRead::from_tensor(&log), &[0])?;
1203 Ok((sign, logabsdet))
1204}
1205fn det_impl<B: LinalgBackend>(
1206 (sign, logabsdet): (Tensor, Tensor),
1207 backend: &mut B,
1208) -> tenferro_tensor::Result<Tensor> {
1209 let magnitude = backend.exp_read(TensorRead::from_tensor(&logabsdet))?;
1210 backend.mul_read(
1211 TensorRead::from_tensor(&sign),
1212 TensorRead::from_tensor(&magnitude),
1213 )
1214}
1215fn inv_owned<B: LinalgBackend>(a: &Tensor, backend: &mut B) -> tenferro_tensor::Result<Tensor> {
1216 let eye = eye_like(a.dtype(), a.shape(), backend)?;
1217 backend.solve(a, &eye)
1218}
1219fn inv_read<B: LinalgBackend>(
1220 a: TensorRead<'_>,
1221 backend: &mut B,
1222) -> tenferro_tensor::Result<Tensor> {
1223 let eye = eye_like(a.dtype(), a.shape(), backend)?;
1224 backend.solve_read(a, TensorRead::from_tensor(&eye))
1225}
1226fn pinv_owned<B: LinalgBackend>(
1227 a: &Tensor,
1228 rtol: Option<f64>,
1229 backend: &mut B,
1230) -> tenferro_tensor::Result<Tensor> {
1231 ensure_float_or_complex("pinv", a.dtype())?;
1232 let outputs = three(backend.svd(a)?, "pinv")?;
1233 pinv_from_svd(
1234 outputs,
1235 rtol.unwrap_or_else(|| default_pinv_rtol(a.dtype(), a.shape())),
1236 backend,
1237 )
1238}
1239fn pinv_read<B: LinalgBackend>(
1240 a: TensorRead<'_>,
1241 rtol: Option<f64>,
1242 backend: &mut B,
1243) -> tenferro_tensor::Result<Tensor> {
1244 ensure_float_or_complex("pinv", a.dtype())?;
1245 let default_rtol = default_pinv_rtol(a.dtype(), a.shape());
1246 let outputs = three(backend.svd_read(a)?, "pinv_read")?;
1247 pinv_from_svd(outputs, rtol.unwrap_or(default_rtol), backend)
1248}
1249fn norm_from_read<B: LinalgBackend>(
1250 input: TensorRead<'_>,
1251 ord: Option<f64>,
1252 dim: Option<&[usize]>,
1253 keepdim: bool,
1254 backend: &mut B,
1255) -> tenferro_tensor::Result<Tensor> {
1256 ensure_float_or_complex("norm", input.dtype())?;
1257 let original_shape = input.shape().to_vec();
1258 let axes = dim.map_or_else(
1259 || (0..original_shape.len()).collect::<Vec<_>>(),
1260 <[usize]>::to_vec,
1261 );
1262 if axes.is_empty() {
1263 return backend.to_contiguous_read(input);
1264 }
1265 validate_axes("norm", original_shape.len(), &axes)?;
1266 let reduced = if can_square_without_abs(input.dtype(), axes.len(), ord) {
1267 frobenius_norm_read(input.clone(), &axes, backend)?
1268 } else {
1269 let abs = backend.abs_read(input)?;
1270 match axes.len() {
1271 1 => norm_over_axes(&abs, &axes, ord, backend)?,
1272 2 => matrix_norm(&abs, &axes, ord, backend)?,
1273 _ => norm_over_axes(&abs, &axes, ord, backend)?,
1274 }
1275 };
1276 if !keepdim {
1277 return Ok(reduced);
1278 }
1279 let mut shape = original_shape;
1280 for &axis in &axes {
1281 shape[axis] = 1;
1282 }
1283 reshape(&reduced, &shape, backend)
1284}
1285
1286fn scalar_real(dtype: DType, value: f64) -> tenferro_tensor::Result<Tensor> {
1287 match dtype {
1288 DType::F32 => Tensor::from_vec_col_major(vec![], vec![value as f32]),
1289 DType::F64 => Tensor::from_vec_col_major(vec![], vec![value]),
1290 DType::C32 => Tensor::from_vec_col_major(vec![], vec![Complex32::new(value as f32, 0.0)]),
1291 DType::C64 => Tensor::from_vec_col_major(vec![], vec![Complex64::new(value, 0.0)]),
1292 _ => Err(crate::error::unsupported_dtype("linalg_scalar", dtype)),
1293 }
1294}
1295
1296fn broadcast<B: LinalgBackend>(
1297 input: &Tensor,
1298 shape: &[usize],
1299 dims: &[usize],
1300 backend: &mut B,
1301) -> tenferro_tensor::Result<Tensor> {
1302 if input.shape() == shape {
1303 return Ok(input.clone());
1304 }
1305 backend.broadcast_in_dim_read(TensorRead::from_tensor(input), shape, dims)
1306}
1307
1308fn eye_like<B: LinalgBackend>(
1309 dtype: DType,
1310 shape: &[usize],
1311 backend: &mut B,
1312) -> tenferro_tensor::Result<Tensor> {
1313 if shape.len() < 2 {
1314 return Err(tenferro_tensor::Error::rank_mismatch("inv", 2, shape.len()));
1315 }
1316 let mut diagonal_shape = vec![shape[0]];
1317 diagonal_shape.extend_from_slice(&shape[2..]);
1318 let scalar = scalar_real(dtype, 1.0)?;
1319 let diagonal = broadcast(&scalar, &diagonal_shape, &[], backend)?;
1320 backend.embed_diagonal(&diagonal, 0, 1)
1321}
1322
1323fn ensure_float_or_complex(op: &'static str, dtype: DType) -> tenferro_tensor::Result<()> {
1324 match dtype {
1325 DType::F32 | DType::F64 | DType::C32 | DType::C64 => Ok(()),
1326 _ => Err(crate::error::unsupported_dtype(op, dtype)),
1327 }
1328}
1329
1330fn can_square_without_abs(dtype: DType, axes_len: usize, ord: Option<f64>) -> bool {
1331 matches!(dtype, DType::F32 | DType::F64)
1332 && (ord.is_none() || (ord == Some(2.0) && axes_len != 2))
1333}
1334
1335fn validate_axes(op: &'static str, rank: usize, axes: &[usize]) -> tenferro_tensor::Result<()> {
1336 let mut seen = vec![false; rank];
1337 for &axis in axes {
1338 if axis >= rank {
1339 return Err(tenferro_tensor::Error::axis_out_of_bounds(op, axis, rank));
1340 }
1341 if seen[axis] {
1342 return Err(tenferro_tensor::Error::duplicate_axis(op, axis, "dim"));
1343 }
1344 seen[axis] = true;
1345 }
1346 Ok(())
1347}
1348
1349fn default_pinv_rtol(dtype: DType, shape: &[usize]) -> f64 {
1350 let max_dim = shape
1351 .first()
1352 .copied()
1353 .unwrap_or(0)
1354 .max(shape.get(1).copied().unwrap_or(0));
1355 let eps = match dtype {
1356 DType::F32 | DType::C32 => f32::EPSILON as f64,
1357 DType::F64 | DType::C64 => f64::EPSILON,
1358 _ => 0.0,
1359 };
1360 eps * max_dim as f64
1361}
1362
1363fn pinv_from_svd<B: LinalgBackend>(
1364 (u, s, vt): (Tensor, Tensor, Tensor),
1365 rtol: f64,
1366 backend: &mut B,
1367) -> tenferro_tensor::Result<Tensor> {
1368 let abs_s = backend.abs_read(TensorRead::from_tensor(&s))?;
1369 let s_max = reduce_max(&abs_s, &[0], backend)?;
1370 let threshold_scalar = scalar_real(abs_s.dtype(), rtol.max(0.0))?;
1371 let threshold = binary_mul(&s_max, &threshold_scalar, backend)?;
1372 let threshold = broadcast_batch_scalar_to_leading_axis(&threshold, s.shape(), backend)?;
1373 let mask = {
1374 let mask = backend.compare_read(
1375 TensorRead::from_tensor(&abs_s),
1376 TensorRead::from_tensor(&threshold),
1377 &CompareDir::Gt,
1378 )?;
1379 backend.convert(&mask, s.dtype())?
1380 };
1381 let ones = ones_like(&s, backend)?;
1382 let neg_mask = backend.neg_read(TensorRead::from_tensor(&mask))?;
1383 let offset = binary_add(&ones, &neg_mask, backend)?;
1384 let denom = binary_add(&s, &offset, backend)?;
1385 let s_inv = backend.div_read(
1386 TensorRead::from_tensor(&mask),
1387 TensorRead::from_tensor(&denom),
1388 )?;
1389 let v = conjugate_transpose(&vt, backend)?;
1390 let uh = conjugate_transpose(&u, backend)?;
1391 let vs = scale_matrix_columns(&v, &s_inv, backend)?;
1392 matmul_preserve_trailing_batch(&vs, &uh, backend)
1393}
1394
1395fn ones_like<B: LinalgBackend>(input: &Tensor, backend: &mut B) -> tenferro_tensor::Result<Tensor> {
1396 let one = scalar_real(input.dtype(), 1.0)?;
1397 broadcast(&one, input.shape(), &[], backend)
1398}
1399
1400fn binary_add<B: LinalgBackend>(
1401 lhs: &Tensor,
1402 rhs: &Tensor,
1403 backend: &mut B,
1404) -> tenferro_tensor::Result<Tensor> {
1405 backend.add_read(TensorRead::from_tensor(lhs), TensorRead::from_tensor(rhs))
1406}
1407
1408fn binary_mul<B: LinalgBackend>(
1409 lhs: &Tensor,
1410 rhs: &Tensor,
1411 backend: &mut B,
1412) -> tenferro_tensor::Result<Tensor> {
1413 backend.mul_read(TensorRead::from_tensor(lhs), TensorRead::from_tensor(rhs))
1414}
1415
1416fn reduce_max<B: LinalgBackend>(
1417 input: &Tensor,
1418 axes: &[usize],
1419 backend: &mut B,
1420) -> tenferro_tensor::Result<Tensor> {
1421 backend.reduce_max_read(TensorRead::from_tensor(input), axes)
1422}
1423
1424fn reduce_sum<B: LinalgBackend>(
1425 input: &Tensor,
1426 axes: &[usize],
1427 backend: &mut B,
1428) -> tenferro_tensor::Result<Tensor> {
1429 backend.reduce_sum_read(TensorRead::from_tensor(input), axes)
1430}
1431
1432fn reduce_min<B: LinalgBackend>(
1433 input: &Tensor,
1434 axes: &[usize],
1435 backend: &mut B,
1436) -> tenferro_tensor::Result<Tensor> {
1437 backend.reduce_min_read(TensorRead::from_tensor(input), axes)
1438}
1439
1440fn reshape<B: LinalgBackend>(
1441 input: &Tensor,
1442 shape: &[usize],
1443 backend: &mut B,
1444) -> tenferro_tensor::Result<Tensor> {
1445 backend.reshape_read(TensorRead::from_tensor(input), shape)
1446}
1447
1448fn transpose<B: LinalgBackend>(
1449 input: &Tensor,
1450 perm: &[usize],
1451 backend: &mut B,
1452) -> tenferro_tensor::Result<Tensor> {
1453 backend.transpose_read(TensorRead::from_tensor(input), perm)
1454}
1455
1456fn broadcast_batch_scalar_to_leading_axis<B: LinalgBackend>(
1457 input: &Tensor,
1458 shape: &[usize],
1459 backend: &mut B,
1460) -> tenferro_tensor::Result<Tensor> {
1461 let dims: Vec<usize> = (1..shape.len()).collect();
1462 broadcast(input, shape, &dims, backend)
1463}
1464
1465fn conjugate_transpose<B: LinalgBackend>(
1466 input: &Tensor,
1467 backend: &mut B,
1468) -> tenferro_tensor::Result<Tensor> {
1469 let conj = backend.conj_read(TensorRead::from_tensor(input))?;
1470 let mut perm: Vec<usize> = (0..input.shape().len()).collect();
1471 perm.swap(0, 1);
1472 transpose(&conj, &perm, backend)
1473}
1474
1475fn scale_matrix_columns<B: LinalgBackend>(
1476 matrix: &Tensor,
1477 scale: &Tensor,
1478 backend: &mut B,
1479) -> tenferro_tensor::Result<Tensor> {
1480 let mut scale_shape = vec![1, scale.shape()[0]];
1481 scale_shape.extend_from_slice(&matrix.shape()[2..]);
1482 let reshaped = reshape(scale, &scale_shape, backend)?;
1483 let dims: Vec<usize> = (0..matrix.shape().len()).collect();
1484 let expanded = broadcast(&reshaped, matrix.shape(), &dims, backend)?;
1485 binary_mul(matrix, &expanded, backend)
1486}
1487
1488fn matmul_preserve_trailing_batch<B: LinalgBackend>(
1489 lhs: &Tensor,
1490 rhs: &Tensor,
1491 backend: &mut B,
1492) -> tenferro_tensor::Result<Tensor> {
1493 let batch: Vec<usize> = (2..lhs.shape().len()).collect();
1494 let config = DotGeneralConfig {
1495 lhs_contracting_dims: vec![1],
1496 rhs_contracting_dims: vec![0],
1497 lhs_batch_dims: batch.clone(),
1498 rhs_batch_dims: batch,
1499 };
1500 backend.dot_general_read(
1501 TensorRead::from_tensor(lhs),
1502 TensorRead::from_tensor(rhs),
1503 &config,
1504 )
1505}
1506
1507fn linalg_matmul_read<B: LinalgBackend>(
1508 lhs: &Tensor,
1509 rhs: TensorRead<'_>,
1510 rhs_is_vector: bool,
1511 backend: &mut B,
1512) -> tenferro_tensor::Result<Tensor> {
1513 let lhs_batch_dims: Vec<usize> = (2..lhs.shape().len()).collect();
1514 let rhs_batch_start = if rhs_is_vector { 1 } else { 2 };
1515 let rhs_batch_dims: Vec<usize> = (rhs_batch_start..rhs.shape().len()).collect();
1516 let config = DotGeneralConfig {
1517 lhs_contracting_dims: vec![1],
1518 rhs_contracting_dims: vec![0],
1519 lhs_batch_dims,
1520 rhs_batch_dims,
1521 };
1522 backend.dot_general_read(TensorRead::from_tensor(lhs), rhs, &config)
1523}
1524
1525fn frobenius_norm<B: LinalgBackend>(
1526 abs: &Tensor,
1527 axes: &[usize],
1528 backend: &mut B,
1529) -> tenferro_tensor::Result<Tensor> {
1530 let sum = backend.reduce_sum_squares_read(TensorRead::from_tensor(abs), axes)?;
1531 backend.sqrt_read(TensorRead::from_tensor(&sum))
1532}
1533
1534fn frobenius_norm_read<B: LinalgBackend>(
1535 input: TensorRead<'_>,
1536 axes: &[usize],
1537 backend: &mut B,
1538) -> tenferro_tensor::Result<Tensor> {
1539 let sum = backend.reduce_sum_squares_read(input, axes)?;
1540 backend.sqrt_read(TensorRead::from_tensor(&sum))
1541}
1542
1543fn p_norm<B: LinalgBackend>(
1544 abs: &Tensor,
1545 axes: &[usize],
1546 p: f64,
1547 backend: &mut B,
1548) -> tenferro_tensor::Result<Tensor> {
1549 if !p.is_finite() || p == 0.0 {
1550 return Err(tenferro_tensor::Error::invalid_argument(
1551 "norm",
1552 "p",
1553 format!("p-norm order must be finite and nonzero, got {p}"),
1554 ));
1555 }
1556 if p == 2.0 {
1557 return frobenius_norm(abs, axes, backend);
1558 }
1559 let power = scalar_real(abs.dtype(), p)?;
1560 let powered = backend.pow_read(
1561 TensorRead::from_tensor(abs),
1562 TensorRead::from_tensor(&power),
1563 )?;
1564 let sum = reduce_sum(&powered, axes, backend)?;
1565 let inverse = scalar_real(abs.dtype(), 1.0 / p)?;
1566 backend.pow_read(
1567 TensorRead::from_tensor(&sum),
1568 TensorRead::from_tensor(&inverse),
1569 )
1570}
1571
1572fn count_nonzero<B: LinalgBackend>(
1573 abs: &Tensor,
1574 axes: &[usize],
1575 backend: &mut B,
1576) -> tenferro_tensor::Result<Tensor> {
1577 let zero = scalar_real(abs.dtype(), 0.0)?;
1578 let zero = broadcast(&zero, abs.shape(), &[], backend)?;
1579 let mask = backend.compare_read(
1580 TensorRead::from_tensor(abs),
1581 TensorRead::from_tensor(&zero),
1582 &CompareDir::Gt,
1583 )?;
1584 let mask = backend.convert(&mask, abs.dtype())?;
1585 reduce_sum(&mask, axes, backend)
1586}
1587
1588fn norm_over_axes<B: LinalgBackend>(
1589 abs: &Tensor,
1590 axes: &[usize],
1591 ord: Option<f64>,
1592 backend: &mut B,
1593) -> tenferro_tensor::Result<Tensor> {
1594 match ord {
1595 None => frobenius_norm(abs, axes, backend),
1596 Some(p) if p == f64::INFINITY => reduce_max(abs, axes, backend),
1597 Some(p) if p == f64::NEG_INFINITY => reduce_min(abs, axes, backend),
1598 Some(0.0) => count_nonzero(abs, axes, backend),
1599 Some(p) => p_norm(abs, axes, p, backend),
1600 }
1601}
1602
1603fn matrix_norm<B: LinalgBackend>(
1604 abs: &Tensor,
1605 axes: &[usize],
1606 ord: Option<f64>,
1607 backend: &mut B,
1608) -> tenferro_tensor::Result<Tensor> {
1609 let matrix = move_axes_to_front(abs, axes, backend)?;
1610 match ord {
1611 None => frobenius_norm(&matrix, &[0, 1], backend),
1612 Some(p) if p == f64::INFINITY => row_sum_norm(&matrix, true, backend),
1613 Some(p) if p == f64::NEG_INFINITY => row_sum_norm(&matrix, false, backend),
1614 Some(1.0) => col_sum_norm(&matrix, true, backend),
1615 Some(-1.0) => col_sum_norm(&matrix, false, backend),
1616 Some(2.0) | Some(-2.0) => {
1617 let (_, singular_values, _) = three(backend.svd(&matrix)?, "norm")?;
1618 if ord == Some(2.0) {
1619 reduce_max(&singular_values, &[0], backend)
1620 } else {
1621 reduce_min(&singular_values, &[0], backend)
1622 }
1623 }
1624 Some(0.0) => count_nonzero(&matrix, &[0, 1], backend),
1625 Some(p) => p_norm(&matrix, &[0, 1], p, backend),
1626 }
1627}
1628
1629fn row_sum_norm<B: LinalgBackend>(
1630 input: &Tensor,
1631 take_max: bool,
1632 backend: &mut B,
1633) -> tenferro_tensor::Result<Tensor> {
1634 let sums = reduce_sum(input, &[1], backend)?;
1635 if take_max {
1636 reduce_max(&sums, &[0], backend)
1637 } else {
1638 reduce_min(&sums, &[0], backend)
1639 }
1640}
1641
1642fn col_sum_norm<B: LinalgBackend>(
1643 input: &Tensor,
1644 take_max: bool,
1645 backend: &mut B,
1646) -> tenferro_tensor::Result<Tensor> {
1647 let sums = reduce_sum(input, &[0], backend)?;
1648 if take_max {
1649 reduce_max(&sums, &[0], backend)
1650 } else {
1651 reduce_min(&sums, &[0], backend)
1652 }
1653}
1654
1655fn move_axes_to_front<B: LinalgBackend>(
1656 input: &Tensor,
1657 axes: &[usize],
1658 backend: &mut B,
1659) -> tenferro_tensor::Result<Tensor> {
1660 if axes.iter().enumerate().all(|(index, &axis)| index == axis) {
1661 return Ok(input.clone());
1662 }
1663 let mut selected = vec![false; input.shape().len()];
1664 for &axis in axes {
1665 selected[axis] = true;
1666 }
1667 let mut perm = axes.to_vec();
1668 perm.extend(
1669 selected
1670 .iter()
1671 .enumerate()
1672 .filter_map(|(axis, selected)| (!selected).then_some(axis)),
1673 );
1674 transpose(input, &perm, backend)
1675}