Skip to main content

tenferro_linalg/
tensor_ext.rs

1//! Receiver-first concrete linear algebra surfaces.
2//!
3//! Owned tensors use [`TensorLinalgExt`], borrowed tensors and views use the
4//! `_read` methods on [`TensorReadLinalgExt`], and typed tensors use
5//! [`TypedTensorLinalgExt`]. All methods reuse a caller-owned backend.
6
7use 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
18/// Scalar types supported by statically typed linear algebra methods.
19///
20/// # Examples
21///
22/// ```rust
23/// use tenferro_linalg::LinalgScalar;
24///
25/// fn accepts_linalg_scalar<T: LinalgScalar>() {}
26/// accepts_linalg_scalar::<f64>();
27/// ```
28pub trait LinalgScalar: TensorScalar + private::Sealed {
29    /// Complex counterpart used by general eigendecomposition.
30    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
54/// Fixed typed output tuple for singular value decomposition.
55///
56/// # Examples
57///
58/// ```rust
59/// let _: Option<tenferro_linalg::TypedSvd<f64>> = None;
60/// ```
61pub type TypedSvd<T> = (
62    TypedTensor<T>,
63    TypedTensor<<T as TensorScalar>::Real>,
64    TypedTensor<T>,
65);
66/// Fixed typed output tuple for LU decomposition.
67///
68/// # Examples
69///
70/// ```rust
71/// let _: Option<tenferro_linalg::TypedLu<f64>> = None;
72/// ```
73pub type TypedLu<T> = (
74    TypedTensor<T>,
75    TypedTensor<T>,
76    TypedTensor<T>,
77    TypedTensor<<T as TensorScalar>::Real>,
78);
79/// Fixed typed output tuple for complete-pivot LU decomposition.
80///
81/// # Examples
82///
83/// ```rust
84/// let _: Option<tenferro_linalg::TypedFullPivLu<f64>> = None;
85/// ```
86pub type TypedFullPivLu<T> = (
87    TypedTensor<T>,
88    TypedTensor<T>,
89    TypedTensor<T>,
90    TypedTensor<T>,
91    TypedTensor<<T as TensorScalar>::Real>,
92);
93/// Fixed typed output tuple for general eigendecomposition.
94///
95/// # Examples
96///
97/// ```rust
98/// let _: Option<tenferro_linalg::TypedEig<f64>> = None;
99/// ```
100pub type TypedEig<T> = (
101    TypedTensor<<T as LinalgScalar>::Complex>,
102    TypedTensor<<T as LinalgScalar>::Complex>,
103);
104
105/// Linear algebra methods for dtype-erased owned tensors.
106///
107/// # Examples
108///
109/// ```rust
110/// use tenferro_cpu::{with_cpu_exec_session, CpuBackend};
111/// use tenferro_linalg::TensorLinalgExt;
112/// use tenferro_tensor::{BackendSessionHost, Tensor};
113///
114/// let a = Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 0.0, 4.0])?;
115/// let mut host = CpuBackend::new();
116/// let (_u, singular_values, _vt) = host.with_backend_session(|session| {
117///     with_cpu_exec_session(session, |backend| a.svd(backend))
118///         .expect("CpuBackend must expose a CpuExecSession")
119/// })?;
120/// assert_eq!(singular_values.as_slice::<f64>()?, &[4.0, 2.0]);
121/// # Ok::<(), tenferro_tensor::Error>(())
122/// ```
123pub trait TensorLinalgExt {
124    /// # Errors
125    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
126    /// Returns validation, unsupported-backend, numerical, or output-contract errors.
127    fn svd<B: LinalgBackend>(
128        &self,
129        backend: &mut B,
130    ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor)>;
131    /// # Errors
132    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
133    /// Returns validation errors for matrix metadata or options, plus SVD backend errors.
134    fn svd_with_options<B: LinalgBackend>(
135        &self,
136        options: SvdOptions,
137        backend: &mut B,
138    ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor)>;
139    /// # Errors
140    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
141    /// Returns validation, unsupported-backend, numerical, or output-contract errors.
142    fn qr<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<(Tensor, Tensor)>;
143    /// # Errors
144    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
145    /// Returns validation errors for matrix metadata or options, plus QR backend errors.
146    fn qr_with_options<B: LinalgBackend>(
147        &self,
148        options: QrOptions,
149        backend: &mut B,
150    ) -> tenferro_tensor::Result<(Tensor, Tensor)>;
151    /// # Errors
152    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
153    /// Returns validation, unsupported-backend, numerical, or output-contract errors.
154    fn lu<B: LinalgBackend>(
155        &self,
156        backend: &mut B,
157    ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor, Tensor)>;
158    /// # Errors
159    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
160    /// Returns validation, unsupported-backend, numerical, or output-contract errors.
161    fn full_piv_lu<B: LinalgBackend>(
162        &self,
163        backend: &mut B,
164    ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor, Tensor, Tensor)>;
165    /// # Errors
166    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
167    /// Returns validation errors for incompatible inputs or a backend/singular-system error.
168    fn full_piv_lu_solve<B: LinalgBackend>(
169        &self,
170        b: &Tensor,
171        backend: &mut B,
172    ) -> tenferro_tensor::Result<Tensor>;
173    /// # Errors
174    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
175    /// Returns validation errors for incompatible inputs or a backend/singular-system error.
176    fn solve<B: LinalgBackend>(
177        &self,
178        b: &Tensor,
179        backend: &mut B,
180    ) -> tenferro_tensor::Result<Tensor>;
181    /// # Errors
182    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
183    /// Returns validation errors for a non-square input or backend/numerical errors.
184    fn cholesky<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<Tensor>;
185    /// # Errors
186    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
187    /// Returns validation, unsupported-backend, convergence, or output-contract errors.
188    fn eigh<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<(Tensor, Tensor)>;
189    /// # Errors
190    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
191    /// Returns validation errors for matrix metadata or options, plus Eigh backend errors.
192    fn eigh_with_options<B: LinalgBackend>(
193        &self,
194        options: EighOptions,
195        backend: &mut B,
196    ) -> tenferro_tensor::Result<(Tensor, Tensor)>;
197    /// # Errors
198    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
199    /// Returns validation, unsupported-backend, convergence, or output-contract errors.
200    fn eig<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<(Tensor, Tensor)>;
201    /// # Errors
202    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
203    /// Returns validation errors for incompatible inputs/flags or backend/numerical errors.
204    #[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    /// # Errors
215    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
216    /// Returns validation, unsupported-backend, numerical, or LU output-contract errors.
217    fn slogdet<B: LinalgBackend>(
218        &self,
219        backend: &mut B,
220    ) -> tenferro_tensor::Result<(Tensor, Tensor)>;
221    /// # Errors
222    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
223    /// Returns the validation, backend, numerical, or contract errors from [`Self::slogdet`].
224    fn det<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<Tensor>;
225    /// # Errors
226    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
227    /// Returns validation, unsupported-backend, or singular-solve numerical errors.
228    fn inv<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<Tensor>;
229    /// # Errors
230    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
231    /// Returns the validation, backend, convergence, or contract errors from [`Self::eigh`].
232    fn eigvalsh<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<Tensor>;
233    /// # Errors
234    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
235    /// Returns the validation, backend, convergence, or contract errors from [`Self::eig`].
236    fn eigvals<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<Tensor>;
237    /// # Errors
238    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
239    /// Returns validation, unsupported-backend, numerical, or SVD contract errors.
240    fn pinv<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<Tensor>;
241    /// # Errors
242    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
243    /// Returns a validation error for invalid `rtol`, plus errors from [`Self::pinv`].
244    fn pinv_with_rtol<B: LinalgBackend>(
245        &self,
246        rtol: f64,
247        backend: &mut B,
248    ) -> tenferro_tensor::Result<Tensor>;
249    /// # Errors
250    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
251    /// Returns validation errors for axes/order combinations or required backend operations.
252    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
261/// Linear algebra methods for borrowed tensor reads.
262///
263/// # Examples
264///
265/// ```rust
266/// use tenferro_cpu::{with_cpu_exec_session, CpuBackend};
267/// use tenferro_linalg::TensorReadLinalgExt;
268/// use tenferro_tensor::{BackendSessionHost, Tensor, TensorRead};
269///
270/// let input = Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 0.0, 4.0])?;
271/// let mut host = CpuBackend::new();
272/// let (_q, r) = host.with_backend_session(|session| {
273///     with_cpu_exec_session(session, |backend| {
274///         TensorRead::from_tensor(&input).qr_read(backend)
275///     })
276///     .expect("CpuBackend must expose a CpuExecSession")
277/// })?;
278/// assert_eq!(r.shape(), &[2, 2]);
279/// # Ok::<(), tenferro_tensor::Error>(())
280/// ```
281pub trait TensorReadLinalgExt {
282    /// # Errors
283    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
284    /// Returns validation, same-placement materialization, backend, numerical, or contract errors.
285    fn svd_read<B: LinalgBackend>(
286        self,
287        backend: &mut B,
288    ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor)>;
289    /// # Errors
290    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
291    /// Returns validation errors for metadata/options, plus read/materialization or SVD errors.
292    fn svd_with_options_read<B: LinalgBackend>(
293        self,
294        options: SvdOptions,
295        backend: &mut B,
296    ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor)>;
297    /// # Errors
298    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
299    /// Returns validation, same-placement materialization, backend, numerical, or contract errors.
300    fn qr_read<B: LinalgBackend>(
301        self,
302        backend: &mut B,
303    ) -> tenferro_tensor::Result<(Tensor, Tensor)>;
304    /// # Errors
305    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
306    /// Returns validation errors for metadata/options, plus read/materialization or QR errors.
307    fn qr_with_options_read<B: LinalgBackend>(
308        self,
309        options: QrOptions,
310        backend: &mut B,
311    ) -> tenferro_tensor::Result<(Tensor, Tensor)>;
312    /// # Errors
313    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
314    /// Returns validation, same-placement materialization, backend, numerical, or contract errors.
315    fn lu_read<B: LinalgBackend>(
316        self,
317        backend: &mut B,
318    ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor, Tensor)>;
319    /// # Errors
320    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
321    /// Returns validation, same-placement materialization, backend, numerical, or contract errors.
322    fn full_piv_lu_read<B: LinalgBackend>(
323        self,
324        backend: &mut B,
325    ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor, Tensor, Tensor)>;
326    /// # Errors
327    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
328    /// Returns incompatible-input validation, read/materialization, backend, or singular errors.
329    fn full_piv_lu_solve_read<B: LinalgBackend>(
330        self,
331        b: TensorRead<'_>,
332        backend: &mut B,
333    ) -> tenferro_tensor::Result<Tensor>;
334    /// # Errors
335    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
336    /// Returns incompatible-input validation, read/materialization, backend, or singular errors.
337    fn solve_read<B: LinalgBackend>(
338        self,
339        b: TensorRead<'_>,
340        backend: &mut B,
341    ) -> tenferro_tensor::Result<Tensor>;
342    /// Solve into a caller-owned destination without allocating the result at
343    /// the public API boundary.
344    ///
345    /// Backends with a native path may write directly into a compatible target;
346    /// the trait default preserves the ordinary solve-read plus copy behavior.
347    ///
348    /// # Errors
349    /// Returns `tenferro_tensor_core::ShapeMismatch` or
350    /// `tenferro_tensor_core::ValidationError::DTypeMismatch` for incompatible
351    /// destination metadata, `tenferro_tensor_core::ValidationError::InvalidArgument`
352    /// for aliasing or placement violations, `Error::Unsupported` when the
353    /// extension implementation or provider is unavailable, and `Error::Singular`
354    /// for a singular system.
355    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    /// # Errors
371    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
372    /// Returns matrix validation, read/materialization, backend, or positive-definiteness errors.
373    fn cholesky_read<B: LinalgBackend>(self, backend: &mut B) -> tenferro_tensor::Result<Tensor>;
374    /// # Errors
375    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
376    /// Returns validation, read/materialization, backend, convergence, or contract errors.
377    fn eigh_read<B: LinalgBackend>(
378        self,
379        backend: &mut B,
380    ) -> tenferro_tensor::Result<(Tensor, Tensor)>;
381    /// # Errors
382    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
383    /// Returns validation errors for metadata/options, plus read or Eigh backend errors.
384    fn eigh_with_options_read<B: LinalgBackend>(
385        self,
386        options: EighOptions,
387        backend: &mut B,
388    ) -> tenferro_tensor::Result<(Tensor, Tensor)>;
389    /// # Errors
390    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
391    /// Returns validation, read/materialization, backend, convergence, or contract errors.
392    fn eig_read<B: LinalgBackend>(
393        self,
394        backend: &mut B,
395    ) -> tenferro_tensor::Result<(Tensor, Tensor)>;
396    /// # Errors
397    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
398    /// Returns incompatible-input/flag validation, read, backend, or singular errors.
399    #[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    /// # Errors
410    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
411    /// Returns validation, read/materialization, backend, numerical, or LU contract errors.
412    fn slogdet_read<B: LinalgBackend>(
413        self,
414        backend: &mut B,
415    ) -> tenferro_tensor::Result<(Tensor, Tensor)>;
416    /// # Errors
417    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
418    /// Returns validation, read/materialization, backend, numerical, or contract errors.
419    fn det_read<B: LinalgBackend>(self, backend: &mut B) -> tenferro_tensor::Result<Tensor>;
420    /// # Errors
421    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
422    /// Returns validation, read/materialization, backend, or singular-solve errors.
423    fn inv_read<B: LinalgBackend>(self, backend: &mut B) -> tenferro_tensor::Result<Tensor>;
424    /// # Errors
425    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
426    /// Returns validation, read/materialization, backend, convergence, or contract errors.
427    fn eigvalsh_read<B: LinalgBackend>(self, backend: &mut B) -> tenferro_tensor::Result<Tensor>;
428    /// # Errors
429    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
430    /// Returns validation, read/materialization, backend, convergence, or contract errors.
431    fn eigvals_read<B: LinalgBackend>(self, backend: &mut B) -> tenferro_tensor::Result<Tensor>;
432    /// # Errors
433    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
434    /// Returns validation, read/materialization, backend, numerical, or SVD contract errors.
435    fn pinv_read<B: LinalgBackend>(self, backend: &mut B) -> tenferro_tensor::Result<Tensor>;
436    /// # Errors
437    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
438    /// Returns a validation error for invalid `rtol`, plus errors from [`Self::pinv_read`].
439    fn pinv_with_rtol_read<B: LinalgBackend>(
440        self,
441        rtol: f64,
442        backend: &mut B,
443    ) -> tenferro_tensor::Result<Tensor>;
444    /// # Errors
445    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
446    /// Returns validation errors for axes/order combinations or required read/backend operations.
447    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
456/// Linear algebra methods with statically typed inputs and outputs.
457///
458/// Singular values, Hermitian eigenvalues, determinant log-magnitudes, and
459/// norms use `T::Real`; general eigenvalues and eigenvectors use `T::Complex`.
460///
461/// # Examples
462///
463/// ```rust
464/// use tenferro_cpu::{with_cpu_exec_session, CpuBackend};
465/// use tenferro_linalg::TypedTensorLinalgExt;
466/// use tenferro_tensor::{BackendSessionHost, TypedTensor};
467///
468/// let input = TypedTensor::<f64>::from_vec_col_major(
469///     vec![2, 2],
470///     vec![2.0, 0.0, 0.0, 4.0],
471/// )?;
472/// let mut host = CpuBackend::new();
473/// let (_u, singular_values, _vt) = host.with_backend_session(|session| {
474///     with_cpu_exec_session(session, |backend| input.svd(backend))
475///         .expect("CpuBackend must expose a CpuExecSession")
476/// })?;
477/// assert_eq!(singular_values.as_slice()?, &[4.0, 2.0]);
478/// # Ok::<(), tenferro_tensor::Error>(())
479/// ```
480pub trait TypedTensorLinalgExt<T: LinalgScalar> {
481    /// # Errors
482    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
483    /// Returns validation, backend, numerical, output-contract, or typed-downcast errors.
484    fn svd<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<TypedSvd<T>>;
485    /// # Errors
486    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
487    /// Returns validation errors for metadata/options, plus backend or typed-output errors.
488    fn svd_with_options<B: LinalgBackend>(
489        &self,
490        options: SvdOptions,
491        backend: &mut B,
492    ) -> tenferro_tensor::Result<TypedSvd<T>>;
493    /// # Errors
494    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
495    /// Returns validation, backend, numerical, output-contract, or typed-downcast errors.
496    fn qr<B: LinalgBackend>(
497        &self,
498        backend: &mut B,
499    ) -> tenferro_tensor::Result<(TypedTensor<T>, TypedTensor<T>)>;
500    /// # Errors
501    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
502    /// Returns validation errors for metadata/options, plus backend or typed-output errors.
503    fn qr_with_options<B: LinalgBackend>(
504        &self,
505        options: QrOptions,
506        backend: &mut B,
507    ) -> tenferro_tensor::Result<(TypedTensor<T>, TypedTensor<T>)>;
508    /// # Errors
509    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
510    /// Returns validation, backend, numerical, output-contract, or typed-downcast errors.
511    fn lu<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<TypedLu<T>>;
512    /// # Errors
513    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
514    /// Returns validation, backend, numerical, output-contract, or typed-downcast errors.
515    fn full_piv_lu<B: LinalgBackend>(
516        &self,
517        backend: &mut B,
518    ) -> tenferro_tensor::Result<TypedFullPivLu<T>>;
519    /// # Errors
520    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
521    /// Returns incompatible-input validation, backend, singular, or typed-downcast errors.
522    fn full_piv_lu_solve<B: LinalgBackend>(
523        &self,
524        b: &TypedTensor<T>,
525        backend: &mut B,
526    ) -> tenferro_tensor::Result<TypedTensor<T>>;
527    /// # Errors
528    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
529    /// Returns incompatible-input validation, backend, singular, or typed-downcast errors.
530    fn solve<B: LinalgBackend>(
531        &self,
532        b: &TypedTensor<T>,
533        backend: &mut B,
534    ) -> tenferro_tensor::Result<TypedTensor<T>>;
535    /// # Errors
536    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
537    /// Returns matrix validation, backend, positive-definiteness, or typed-downcast errors.
538    fn cholesky<B: LinalgBackend>(
539        &self,
540        backend: &mut B,
541    ) -> tenferro_tensor::Result<TypedTensor<T>>;
542    /// # Errors
543    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
544    /// Returns validation, backend, convergence, output-contract, or typed-downcast errors.
545    fn eigh<B: LinalgBackend>(
546        &self,
547        backend: &mut B,
548    ) -> tenferro_tensor::Result<(TypedTensor<<T as TensorScalar>::Real>, TypedTensor<T>)>;
549    /// # Errors
550    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
551    /// Returns validation errors for metadata/options, plus backend or typed-output errors.
552    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    /// # Errors
558    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
559    /// Returns validation, backend, convergence, output-contract, or typed-downcast errors.
560    fn eig<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<TypedEig<T>>;
561    /// # Errors
562    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
563    /// Returns incompatible-input/flag validation, backend, singular, or typed-output errors.
564    #[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    /// # Errors
575    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
576    /// Returns validation, backend, numerical, output-contract, or typed-downcast errors.
577    fn slogdet<B: LinalgBackend>(
578        &self,
579        backend: &mut B,
580    ) -> tenferro_tensor::Result<(TypedTensor<T>, TypedTensor<<T as TensorScalar>::Real>)>;
581    /// # Errors
582    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
583    /// Returns validation, backend, numerical, output-contract, or typed-downcast errors.
584    fn det<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<TypedTensor<T>>;
585    /// # Errors
586    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
587    /// Returns validation, backend, singular-solve, or typed-downcast errors.
588    fn inv<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<TypedTensor<T>>;
589    /// # Errors
590    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
591    /// Returns validation, backend, convergence, output-contract, or typed-downcast errors.
592    fn eigvalsh<B: LinalgBackend>(
593        &self,
594        backend: &mut B,
595    ) -> tenferro_tensor::Result<TypedTensor<<T as TensorScalar>::Real>>;
596    /// # Errors
597    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
598    /// Returns validation, backend, convergence, output-contract, or typed-downcast errors.
599    fn eigvals<B: LinalgBackend>(
600        &self,
601        backend: &mut B,
602    ) -> tenferro_tensor::Result<TypedTensor<T::Complex>>;
603    /// # Errors
604    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
605    /// Returns validation, backend, numerical, output-contract, or typed-downcast errors.
606    fn pinv<B: LinalgBackend>(&self, backend: &mut B) -> tenferro_tensor::Result<TypedTensor<T>>;
607    /// # Errors
608    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
609    /// Returns a validation error for invalid `rtol`, plus backend or typed-output errors.
610    fn pinv_with_rtol<B: LinalgBackend>(
611        &self,
612        rtol: f64,
613        backend: &mut B,
614    ) -> tenferro_tensor::Result<TypedTensor<T>>;
615    /// # Errors
616    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
617    /// Returns validation errors for axes/order combinations, backend, or typed-output errors.
618    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
1187// Composite implementations are kept below the surface adapters so every
1188// backend-visible operation remains explicit and testable.
1189fn 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}