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 dispatch internally to the built-in
6//! CPU/CUDA execution sessions through an erased `&mut dyn Engine`
7//! (issue #1680 Phase 3); third-party [`LinalgBackend`] implementations
8//! remain supported through the SPI trait, but the concrete op path is
9//! built-in-session only.
10
11use num_complex::{Complex32, Complex64};
12use tenferro_cpu::with_cpu_engine;
13#[cfg(feature = "cuda")]
14use tenferro_gpu::cuda::raw::with_cuda_engine;
15use tenferro_tensor::{
16    CompareDir, DType, DotGeneralConfig, Engine, Tensor, TensorRead, TensorScalar, TensorWrite,
17    TypedTensor,
18};
19
20use crate::extension::{EighOptions, QrOptions, SvdOptions};
21use crate::{LinalgBackend, RankRevealingQrOptions, RankRevealingQrResult};
22
23/// Scalar types supported by statically typed linear algebra methods.
24///
25/// # Examples
26///
27/// ```rust
28/// use tenferro_linalg::LinalgScalar;
29///
30/// fn accepts_linalg_scalar<T: LinalgScalar>() {}
31/// accepts_linalg_scalar::<f64>();
32/// ```
33pub trait LinalgScalar: TensorScalar + private::Sealed {
34    /// Complex counterpart used by general eigendecomposition.
35    type Complex: TensorScalar;
36}
37
38mod private {
39    pub trait Sealed {}
40    impl Sealed for f32 {}
41    impl Sealed for f64 {}
42    impl Sealed for num_complex::Complex32 {}
43    impl Sealed for num_complex::Complex64 {}
44}
45
46impl LinalgScalar for f32 {
47    type Complex = Complex32;
48}
49impl LinalgScalar for f64 {
50    type Complex = Complex64;
51}
52impl LinalgScalar for Complex32 {
53    type Complex = Complex32;
54}
55impl LinalgScalar for Complex64 {
56    type Complex = Complex64;
57}
58
59/// Fixed typed output tuple for singular value decomposition.
60///
61/// # Examples
62///
63/// ```rust
64/// let _: Option<tenferro_linalg::TypedSvd<f64>> = None;
65/// ```
66pub type TypedSvd<T> = (
67    TypedTensor<T>,
68    TypedTensor<<T as TensorScalar>::Real>,
69    TypedTensor<T>,
70);
71/// Fixed typed output for rank-revealing QR.
72///
73/// # Examples
74///
75/// ```rust
76/// use tenferro_linalg::{RankRevealingQrResult, TypedRankRevealingQrResult};
77/// use tenferro_tensor::TypedTensor;
78///
79/// let result: TypedRankRevealingQrResult<f64> = RankRevealingQrResult {
80///     q: TypedTensor::from_vec_col_major(vec![1, 1], vec![1.0])?,
81///     r: TypedTensor::from_vec_col_major(vec![1, 1], vec![2.0])?,
82///     column_permutation: TypedTensor::from_vec_col_major(vec![1], vec![0_i64])?,
83///     rank: TypedTensor::from_vec_col_major(vec![], vec![1_i64])?,
84/// };
85/// assert_eq!(result.rank.as_slice()?, &[1]);
86/// # Ok::<(), tenferro_tensor::Error>(())
87/// ```
88pub type TypedRankRevealingQrResult<T> = RankRevealingQrResult<TypedTensor<T>, TypedTensor<i64>>;
89/// Fixed typed output tuple for LU decomposition.
90///
91/// # Examples
92///
93/// ```rust
94/// let _: Option<tenferro_linalg::TypedLu<f64>> = None;
95/// ```
96pub type TypedLu<T> = (
97    TypedTensor<T>,
98    TypedTensor<T>,
99    TypedTensor<T>,
100    TypedTensor<<T as TensorScalar>::Real>,
101);
102/// Fixed typed output tuple for complete-pivot LU decomposition.
103///
104/// # Examples
105///
106/// ```rust
107/// let _: Option<tenferro_linalg::TypedFullPivLu<f64>> = None;
108/// ```
109pub type TypedFullPivLu<T> = (
110    TypedTensor<T>,
111    TypedTensor<T>,
112    TypedTensor<T>,
113    TypedTensor<T>,
114    TypedTensor<<T as TensorScalar>::Real>,
115);
116/// Fixed typed output tuple for general eigendecomposition.
117///
118/// # Examples
119///
120/// ```rust
121/// let _: Option<tenferro_linalg::TypedEig<f64>> = None;
122/// ```
123pub type TypedEig<T> = (
124    TypedTensor<<T as LinalgScalar>::Complex>,
125    TypedTensor<<T as LinalgScalar>::Complex>,
126);
127
128/// Linear algebra methods for dtype-erased owned tensors.
129///
130/// # Examples
131///
132/// ```rust
133/// use tenferro_cpu::CpuEngine;
134/// use tenferro_linalg::TensorLinalgExt;
135/// use tenferro_tensor::Tensor;
136///
137/// let a = Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 0.0, 4.0])?;
138/// let mut host = tenferro_cpu::CpuEngine::serial();
139/// let (_u, singular_values, _vt) = a.svd(&mut host)?;
140/// assert_eq!(singular_values.as_slice::<f64>()?, &[4.0, 2.0]);
141/// # Ok::<(), tenferro_tensor::Error>(())
142/// ```
143pub trait TensorLinalgExt {
144    /// # Errors
145    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
146    /// Returns validation, unsupported-backend, numerical, or output-contract errors.
147    /// # Examples
148    ///
149    /// ```rust
150    /// # use tenferro_cpu::CpuEngine;
151    /// # use tenferro_linalg::TensorLinalgExt;
152    /// # use tenferro_tensor::Tensor;
153    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 0.0, 4.0])?;
154    /// # let mut host = tenferro_cpu::CpuEngine::serial();
155    /// let (_u, s, _vt) = a.svd(&mut host)?;
156    /// assert_eq!(s.shape(), &[2]);
157    /// # Ok::<(), tenferro_tensor::Error>(())
158    /// ```
159    fn svd(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor)>;
160    /// Compute singular values without allocating singular-vector outputs.
161    ///
162    /// # Errors
163    /// Returns [`tenferro_tensor::Error::Unsupported`] when the selected
164    /// backend has no values-only capability, rather than silently computing a
165    /// full decomposition and discarding its vectors.
166    ///
167    /// # Examples
168    ///
169    /// ```rust
170    /// # use tenferro_cpu::CpuEngine;
171    /// # use tenferro_linalg::TensorLinalgExt;
172    /// # use tenferro_tensor::Tensor;
173    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 0.0, 4.0])?;
174    /// # let mut host = tenferro_cpu::CpuEngine::serial();
175    /// let values = a.svdvals(&mut host)?;
176    /// assert_eq!(values.as_slice::<f64>()?, &[4.0, 2.0]);
177    /// # Ok::<(), tenferro_tensor::Error>(())
178    /// ```
179    fn svdvals(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<Tensor>;
180    /// # Errors
181    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
182    /// Returns validation errors for matrix metadata or options, plus SVD backend errors.
183    /// # Examples
184    ///
185    /// ```rust
186    /// # use tenferro_cpu::CpuEngine;
187    /// # use tenferro_linalg::{SvdOptions, TensorLinalgExt};
188    /// # use tenferro_tensor::Tensor;
189    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 0.0, 4.0])?;
190    /// # let mut host = tenferro_cpu::CpuEngine::serial();
191    /// let (_u, s, _vt) = { a.svd_with_options(SvdOptions::default(), &mut host) }?;
192    /// assert_eq!(s.shape(), &[2]);
193    /// # Ok::<(), tenferro_tensor::Error>(())
194    /// ```
195    fn svd_with_options(
196        &self,
197        options: SvdOptions,
198        session: &mut dyn Engine,
199    ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor)>;
200    /// Compute the full-matrices SVD `(U, S, Vt)` with `U` shaped `m x m` and
201    /// `Vt` shaped `n x n`, so the trailing `Vt` rows span the input's right
202    /// nullspace and the trailing `U` columns span its left nullspace.
203    ///
204    /// # Errors
205    /// Returns [`tenferro_tensor::Error::Unsupported`] when the selected
206    /// backend or CPU provider has no full-matrices kernel; the thin
207    /// decomposition is never substituted for it. Returns validation errors for
208    /// an unsupported rank or dtype, plus backend, numerical, or
209    /// output-contract errors.
210    /// # Examples
211    ///
212    /// ```rust
213    /// # use tenferro_cpu::CpuEngine;
214    /// # use tenferro_linalg::TensorLinalgExt;
215    /// # use tenferro_tensor::Tensor;
216    /// # let a = Tensor::from_vec_col_major(vec![1, 2], vec![1.0_f64, 1.0])?;
217    /// # let mut host = tenferro_cpu::CpuEngine::serial();
218    /// let (u, s, vt) = a.svd_full(&mut host)?;
219    /// assert_eq!(u.shape(), &[1, 1]);
220    /// assert_eq!(s.shape(), &[1]);
221    /// assert_eq!(vt.shape(), &[2, 2]);
222    /// # Ok::<(), tenferro_tensor::Error>(())
223    /// ```
224    fn svd_full(
225        &self,
226        session: &mut dyn Engine,
227    ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor)>;
228    /// # Errors
229    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
230    /// Returns validation, unsupported-backend, numerical, or output-contract errors.
231    /// # Examples
232    ///
233    /// ```rust
234    /// # use tenferro_cpu::CpuEngine;
235    /// # use tenferro_linalg::TensorLinalgExt;
236    /// # use tenferro_tensor::Tensor;
237    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 1.0])?;
238    /// # let mut host = tenferro_cpu::CpuEngine::serial();
239    /// let (q, r) = a.qr(&mut host)?;
240    /// assert_eq!(q.shape(), &[2, 2]);
241    /// assert_eq!(r.shape(), &[2, 2]);
242    /// # Ok::<(), tenferro_tensor::Error>(())
243    /// ```
244    fn qr(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<(Tensor, Tensor)>;
245
246    /// Initialize opaque compact Householder QR state.
247    ///
248    /// # Errors
249    ///
250    /// Returns validation errors for non-matrix or unsupported-dtype input and
251    /// typed provider errors when compact QR is unavailable.
252    ///
253    /// # Examples
254    ///
255    /// ```rust
256    /// use tenferro_cpu::CpuEngine;
257    /// use tenferro_linalg::TensorLinalgExt;
258    /// use tenferro_tensor::Tensor;
259    /// let a = Tensor::from_vec_col_major(vec![2, 1], vec![1.0_f64, 2.0])?;
260    /// let mut host = tenferro_cpu::CpuEngine::serial();
261    /// let qr = a.householder_qr(&mut host)?;
262    /// assert!(format!("{qr:?}").starts_with("HouseholderQr"));
263    /// # Ok::<(), tenferro_tensor::Error>(())
264    /// ```
265    fn householder_qr(
266        &self,
267        session: &mut dyn Engine,
268    ) -> tenferro_tensor::Result<crate::HouseholderQr<Tensor>>;
269
270    /// The CUDA provider does not read the cuSOLVER `geqrf`/`orgqr` status
271    /// flags, so forward QR reports neither a vendor parameter error nor a
272    /// rank deficiency: shapes and dtypes are validated up front, and a
273    /// rank-deficient input still yields its valid factors.
274    ///
275    /// # Errors
276    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
277    /// Returns validation errors for matrix metadata or options, plus QR backend errors.
278    /// # Examples
279    ///
280    /// ```rust
281    /// # use tenferro_cpu::CpuEngine;
282    /// # use tenferro_linalg::{QrOptions, TensorLinalgExt};
283    /// # use tenferro_tensor::Tensor;
284    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 1.0])?;
285    /// # let mut host = tenferro_cpu::CpuEngine::serial();
286    /// let (q, r) = { a.qr_with_options(QrOptions::default(), &mut host) }?;
287    /// assert_eq!(q.shape(), &[2, 2]);
288    /// assert_eq!(r.shape(), &[2, 2]);
289    /// # Ok::<(), tenferro_tensor::Error>(())
290    /// ```
291    fn qr_with_options(
292        &self,
293        options: QrOptions,
294        session: &mut dyn Engine,
295    ) -> tenferro_tensor::Result<(Tensor, Tensor)>;
296    /// Compute column-pivoted rank-revealing QR with fixed-shape tensor metadata.
297    ///
298    /// # Errors
299    /// Returns validation errors for rank, dtype, or invalid tolerances;
300    /// numerical failure for non-finite input or diagonal values; and explicit
301    /// unsupported/backend errors when the selected provider cannot execute RRQR.
302    ///
303    /// # Examples
304    ///
305    /// ```rust
306    /// # use tenferro_cpu::CpuEngine;
307    /// # use tenferro_linalg::{RankRevealingQrOptions, TensorLinalgExt};
308    /// # use tenferro_tensor::Tensor;
309    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 2.0])?;
310    /// # let mut host = tenferro_cpu::CpuEngine::serial();
311    /// let result = {
312    ///     a.rank_revealing_qr(RankRevealingQrOptions::default().rtol(1e-12), &mut host)
313    /// }?;
314    /// assert_eq!(result.column_permutation.shape(), &[2]);
315    /// assert_eq!(result.rank.shape(), &[] as &[usize]);
316    /// # Ok::<(), tenferro_tensor::Error>(())
317    /// ```
318    fn rank_revealing_qr(
319        &self,
320        options: RankRevealingQrOptions,
321        session: &mut dyn Engine,
322    ) -> tenferro_tensor::Result<RankRevealingQrResult<Tensor>>;
323    /// # Errors
324    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
325    /// Returns validation, unsupported-backend, numerical, or output-contract errors.
326    /// # Examples
327    ///
328    /// ```rust
329    /// # use tenferro_cpu::CpuEngine;
330    /// # use tenferro_linalg::TensorLinalgExt;
331    /// # use tenferro_tensor::Tensor;
332    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 3.0, 2.0, 4.0])?;
333    /// # let mut host = tenferro_cpu::CpuEngine::serial();
334    /// let (_p, l, u, parity) = a.lu(&mut host)?;
335    /// assert_eq!(l.shape(), &[2, 2]);
336    /// assert_eq!(u.shape(), &[2, 2]);
337    /// assert_eq!(parity.shape(), &[] as &[usize]);
338    /// # Ok::<(), tenferro_tensor::Error>(())
339    /// ```
340    fn lu(
341        &self,
342        session: &mut dyn Engine,
343    ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor, Tensor)>;
344    /// # Errors
345    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
346    /// Returns validation, unsupported-backend, numerical, or output-contract errors.
347    /// # Examples
348    ///
349    /// ```rust
350    /// # use tenferro_cpu::CpuEngine;
351    /// # use tenferro_linalg::TensorLinalgExt;
352    /// # use tenferro_tensor::Tensor;
353    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 3.0, 2.0, 4.0])?;
354    /// # let mut host = tenferro_cpu::CpuEngine::serial();
355    /// let (p, _l, _u, q, parity) = a.full_piv_lu(&mut host)?;
356    /// assert_eq!(p.shape(), &[2, 2]);
357    /// assert_eq!(q.shape(), &[2, 2]);
358    /// assert_eq!(parity.shape(), &[] as &[usize]);
359    /// # Ok::<(), tenferro_tensor::Error>(())
360    /// ```
361    fn full_piv_lu(
362        &self,
363        session: &mut dyn Engine,
364    ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor, Tensor, Tensor)>;
365    /// # Errors
366    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
367    /// Returns validation errors for incompatible inputs or a backend/singular-system error.
368    /// # Examples
369    ///
370    /// ```rust
371    /// # use tenferro_cpu::CpuEngine;
372    /// # use tenferro_linalg::TensorLinalgExt;
373    /// # use tenferro_tensor::Tensor;
374    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![0.0_f64, 2.0, 1.0, 3.0])?;
375    /// # let b = Tensor::from_vec_col_major(vec![2, 1], vec![-1.0_f64, 5.0])?;
376    /// # let mut host = tenferro_cpu::CpuEngine::serial();
377    /// let x = a.full_piv_lu_solve(&b, &mut host)?;
378    /// assert_eq!(x.shape(), &[2, 1]);
379    /// # Ok::<(), tenferro_tensor::Error>(())
380    /// ```
381    fn full_piv_lu_solve(
382        &self,
383        b: &Tensor,
384        session: &mut dyn Engine,
385    ) -> tenferro_tensor::Result<Tensor>;
386    /// The CUDA provider does not inspect the cuSOLVER `getrf` status flag on
387    /// the forward solve path, so it performs no per-call non-singularity check
388    /// and issues no host round trip. An exactly singular input is left to the
389    /// caller's verification, as in `torch.linalg.solve`. CPU providers may
390    /// still report their own exactly singular factor as `Error::Singular`.
391    ///
392    /// # Errors
393    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
394    /// Returns validation errors for incompatible inputs or a backend/singular-system error.
395    /// # Examples
396    ///
397    /// ```rust
398    /// # use tenferro_cpu::CpuEngine;
399    /// # use tenferro_linalg::TensorLinalgExt;
400    /// # use tenferro_tensor::Tensor;
401    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 0.0, 4.0])?;
402    /// # let b = Tensor::from_vec_col_major(vec![2, 1], vec![4.0_f64, 8.0])?;
403    /// # let mut host = tenferro_cpu::CpuEngine::serial();
404    /// let x = a.solve(&b, &mut host)?;
405    /// assert_eq!(x.as_slice::<f64>()?, &[2.0, 2.0]);
406    /// # Ok::<(), tenferro_tensor::Error>(())
407    /// ```
408    fn solve(&self, b: &Tensor, session: &mut dyn Engine) -> tenferro_tensor::Result<Tensor>;
409    /// # Errors
410    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
411    /// Returns validation errors for a non-square input or backend/numerical errors.
412    /// # Examples
413    ///
414    /// ```rust
415    /// # use tenferro_cpu::CpuEngine;
416    /// # use tenferro_linalg::TensorLinalgExt;
417    /// # use tenferro_tensor::Tensor;
418    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![4.0_f64, 2.0, 2.0, 3.0])?;
419    /// # let mut host = tenferro_cpu::CpuEngine::serial();
420    /// let l = a.cholesky(&mut host)?;
421    /// assert_eq!(l.shape(), &[2, 2]);
422    /// # Ok::<(), tenferro_tensor::Error>(())
423    /// ```
424    fn cholesky(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<Tensor>;
425    /// # Errors
426    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
427    /// Returns validation, unsupported-backend, convergence, or output-contract errors.
428    /// # Examples
429    ///
430    /// ```rust
431    /// # use tenferro_cpu::CpuEngine;
432    /// # use tenferro_linalg::TensorLinalgExt;
433    /// # use tenferro_tensor::Tensor;
434    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 3.0])?;
435    /// # let mut host = tenferro_cpu::CpuEngine::serial();
436    /// let (values, vectors) = a.eigh(&mut host)?;
437    /// assert_eq!(values.as_slice::<f64>()?, &[1.0, 3.0]);
438    /// assert_eq!(vectors.shape(), &[2, 2]);
439    /// # Ok::<(), tenferro_tensor::Error>(())
440    /// ```
441    fn eigh(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<(Tensor, Tensor)>;
442    /// # Errors
443    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
444    /// Returns validation errors for matrix metadata or options, plus Eigh backend errors.
445    /// # Examples
446    ///
447    /// ```rust
448    /// # use tenferro_cpu::CpuEngine;
449    /// # use tenferro_linalg::{EighOptions, TensorLinalgExt};
450    /// # use tenferro_tensor::Tensor;
451    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 3.0])?;
452    /// # let mut host = tenferro_cpu::CpuEngine::serial();
453    /// let (values, vectors) = { a.eigh_with_options(EighOptions::default(), &mut host) }?;
454    /// assert_eq!(values.shape(), &[2]);
455    /// assert_eq!(vectors.shape(), &[2, 2]);
456    /// # Ok::<(), tenferro_tensor::Error>(())
457    /// ```
458    fn eigh_with_options(
459        &self,
460        options: EighOptions,
461        session: &mut dyn Engine,
462    ) -> tenferro_tensor::Result<(Tensor, Tensor)>;
463    /// # Errors
464    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
465    /// Returns validation, unsupported-backend, convergence, or output-contract errors.
466    /// # Examples
467    ///
468    /// ```rust
469    /// # use tenferro_cpu::CpuEngine;
470    /// # use tenferro_linalg::TensorLinalgExt;
471    /// # use tenferro_tensor::Tensor;
472    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 2.0])?;
473    /// # let mut host = tenferro_cpu::CpuEngine::serial();
474    /// let (values, vectors) = a.eig(&mut host)?;
475    /// assert_eq!(values.shape(), &[2]);
476    /// assert_eq!(vectors.shape(), &[2, 2]);
477    /// # Ok::<(), tenferro_tensor::Error>(())
478    /// ```
479    /// Eigenvectors come back with unit 2-norm columns, whichever provider computed them;
480    /// a column the provider left non-finite is reported as a numerical failure instead
481    /// of being returned as `NaN` (#2148).
482    fn eig(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<(Tensor, Tensor)>;
483    /// # Errors
484    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
485    /// Returns validation errors for incompatible inputs/flags or backend/numerical errors.
486    #[allow(clippy::too_many_arguments)]
487    /// # Examples
488    ///
489    /// ```rust
490    /// # use tenferro_cpu::CpuEngine;
491    /// # use tenferro_linalg::TensorLinalgExt;
492    /// # use tenferro_tensor::Tensor;
493    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 1.0, 3.0])?;
494    /// # let b = Tensor::from_vec_col_major(vec![2, 1], vec![4.0_f64, 9.0])?;
495    /// # let mut host = tenferro_cpu::CpuEngine::serial();
496    /// let x = { a.triangular_solve(&b, true, false, false, false, &mut host) }?;
497    /// assert_eq!(x.shape(), &[2, 1]);
498    /// # Ok::<(), tenferro_tensor::Error>(())
499    /// ```
500    fn triangular_solve(
501        &self,
502        b: &Tensor,
503        left_side: bool,
504        lower: bool,
505        transpose_a: bool,
506        unit_diagonal: bool,
507        session: &mut dyn Engine,
508    ) -> tenferro_tensor::Result<Tensor>;
509    /// # Errors
510    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
511    /// Returns validation, unsupported-backend, numerical, or LU output-contract errors.
512    /// # Examples
513    ///
514    /// ```rust
515    /// # use tenferro_cpu::CpuEngine;
516    /// # use tenferro_linalg::TensorLinalgExt;
517    /// # use tenferro_tensor::Tensor;
518    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 0.0, 4.0])?;
519    /// # let mut host = tenferro_cpu::CpuEngine::serial();
520    /// let (sign, logabsdet) = a.slogdet(&mut host)?;
521    /// assert_eq!(sign.as_slice::<f64>()?, &[1.0]);
522    /// assert_eq!(logabsdet.shape(), &[] as &[usize]);
523    /// # Ok::<(), tenferro_tensor::Error>(())
524    /// ```
525    fn slogdet(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<(Tensor, Tensor)>;
526    /// # Errors
527    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
528    /// Returns the validation, backend, numerical, or contract errors from [`Self::slogdet`].
529    /// # Examples
530    ///
531    /// ```rust
532    /// # use tenferro_cpu::CpuEngine;
533    /// # use tenferro_linalg::TensorLinalgExt;
534    /// # use tenferro_tensor::Tensor;
535    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 0.0, 4.0])?;
536    /// # let mut host = tenferro_cpu::CpuEngine::serial();
537    /// let determinant = a.det(&mut host)?;
538    /// assert!((determinant.as_slice::<f64>()?[0] - 8.0).abs() < 1.0e-12);
539    /// # Ok::<(), tenferro_tensor::Error>(())
540    /// ```
541    fn det(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<Tensor>;
542    /// # Errors
543    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
544    /// Returns validation, unsupported-backend, or singular-solve numerical errors.
545    /// # Examples
546    ///
547    /// ```rust
548    /// # use tenferro_cpu::CpuEngine;
549    /// # use tenferro_linalg::TensorLinalgExt;
550    /// # use tenferro_tensor::Tensor;
551    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 0.0, 4.0])?;
552    /// # let mut host = tenferro_cpu::CpuEngine::serial();
553    /// let inverse = a.inv(&mut host)?;
554    /// assert_eq!(inverse.as_slice::<f64>()?, &[0.5, 0.0, 0.0, 0.25]);
555    /// # Ok::<(), tenferro_tensor::Error>(())
556    /// ```
557    fn inv(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<Tensor>;
558    /// # Errors
559    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
560    /// Returns the validation, backend, convergence, or contract errors from [`Self::eigh`].
561    /// # Examples
562    ///
563    /// ```rust
564    /// # use tenferro_cpu::CpuEngine;
565    /// # use tenferro_linalg::TensorLinalgExt;
566    /// # use tenferro_tensor::Tensor;
567    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 3.0])?;
568    /// # let mut host = tenferro_cpu::CpuEngine::serial();
569    /// let values = a.eigvalsh(&mut host)?;
570    /// assert_eq!(values.as_slice::<f64>()?, &[1.0, 3.0]);
571    /// # Ok::<(), tenferro_tensor::Error>(())
572    /// ```
573    fn eigvalsh(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<Tensor>;
574    /// # Errors
575    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
576    /// Returns the validation, backend, convergence, or contract errors from [`Self::eig`].
577    /// # Examples
578    ///
579    /// ```rust
580    /// # use tenferro_cpu::CpuEngine;
581    /// # use tenferro_linalg::TensorLinalgExt;
582    /// # use tenferro_tensor::Tensor;
583    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 3.0])?;
584    /// # let mut host = tenferro_cpu::CpuEngine::serial();
585    /// let values = a.eigvals(&mut host)?;
586    /// assert_eq!(values.shape(), &[2]);
587    /// # Ok::<(), tenferro_tensor::Error>(())
588    /// ```
589    fn eigvals(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<Tensor>;
590    /// Compute the Moore-Penrose pseudoinverse with the dtype-default cutoff.
591    ///
592    /// The cutoff thresholds an SVD whose default driver is accuracy-preserving
593    /// (`SvdDriver::Auto`), so a rank-deficient rank is retained or dropped the
594    /// same way as on CPU. CPU providers and CUDA share this contract.
595    ///
596    /// # Errors
597    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
598    /// Returns validation, unsupported-backend, numerical, or SVD contract errors.
599    /// # Examples
600    ///
601    /// ```rust
602    /// # use tenferro_cpu::CpuEngine;
603    /// # use tenferro_linalg::TensorLinalgExt;
604    /// # use tenferro_tensor::Tensor;
605    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 0.0, 4.0])?;
606    /// # let mut host = tenferro_cpu::CpuEngine::serial();
607    /// let pseudoinverse = a.pinv(&mut host)?;
608    /// assert_eq!(pseudoinverse.shape(), &[2, 2]);
609    /// # Ok::<(), tenferro_tensor::Error>(())
610    /// ```
611    fn pinv(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<Tensor>;
612    /// Compute the Moore-Penrose pseudoinverse with an explicit relative cutoff.
613    ///
614    /// Like [`Self::pinv`], the threshold is applied to an accuracy-preserving
615    /// default SVD (`SvdDriver::Auto`), so the retained rank matches the CPU
616    /// reference.
617    ///
618    /// # Errors
619    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
620    /// Returns a validation error for invalid `rtol`, plus errors from [`Self::pinv`].
621    /// # Examples
622    ///
623    /// ```rust
624    /// # use tenferro_cpu::CpuEngine;
625    /// # use tenferro_linalg::TensorLinalgExt;
626    /// # use tenferro_tensor::Tensor;
627    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 0.0, 4.0])?;
628    /// # let mut host = tenferro_cpu::CpuEngine::serial();
629    /// let pseudoinverse = a.pinv_with_rtol(1.0e-12, &mut host)?;
630    /// assert_eq!(pseudoinverse.shape(), &[2, 2]);
631    /// # Ok::<(), tenferro_tensor::Error>(())
632    /// ```
633    fn pinv_with_rtol(
634        &self,
635        rtol: f64,
636        session: &mut dyn Engine,
637    ) -> tenferro_tensor::Result<Tensor>;
638    /// # Errors
639    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
640    /// Returns validation errors for axes/order combinations or required backend operations.
641    /// # Examples
642    ///
643    /// ```rust
644    /// # use tenferro_cpu::CpuEngine;
645    /// # use tenferro_linalg::TensorLinalgExt;
646    /// # use tenferro_tensor::Tensor;
647    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![3.0_f64, 0.0, 0.0, 4.0])?;
648    /// # let mut host = tenferro_cpu::CpuEngine::serial();
649    /// let frobenius = a.norm(None, None, false, &mut host)?;
650    /// assert_eq!(frobenius.shape(), &[] as &[usize]);
651    /// # Ok::<(), tenferro_tensor::Error>(())
652    /// ```
653    fn norm(
654        &self,
655        ord: Option<f64>,
656        dim: Option<&[usize]>,
657        keepdim: bool,
658        session: &mut dyn Engine,
659    ) -> tenferro_tensor::Result<Tensor>;
660}
661
662/// Linear algebra methods for borrowed tensor reads.
663///
664/// # Examples
665///
666/// ```rust
667/// use tenferro_cpu::CpuEngine;
668/// use tenferro_linalg::TensorReadLinalgExt;
669/// use tenferro_tensor::{Tensor, TensorRead};
670///
671/// let input = Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 0.0, 4.0])?;
672/// let mut host = tenferro_cpu::CpuEngine::serial();
673/// let (_q, r) = {
674///     TensorRead::from_tensor(&input).qr_read(&mut host)
675/// }?;
676/// assert_eq!(r.shape(), &[2, 2]);
677/// # Ok::<(), tenferro_tensor::Error>(())
678/// ```
679pub trait TensorReadLinalgExt {
680    /// # Errors
681    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
682    /// Returns validation, same-placement materialization, backend, numerical, or contract errors.
683    /// # Examples
684    ///
685    /// ```rust
686    /// # use tenferro_cpu::CpuEngine;
687    /// # use tenferro_linalg::TensorReadLinalgExt;
688    /// # use tenferro_tensor::{Tensor, TensorRead};
689    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 0.0, 4.0])?;
690    /// # let mut host = tenferro_cpu::CpuEngine::serial();
691    /// let (_u, s, _vt) = {
692    ///     TensorRead::from_tensor(&a).svd_read(&mut host)
693    /// }?;
694    /// assert_eq!(s.shape(), &[2]);
695    /// # Ok::<(), tenferro_tensor::Error>(())
696    /// ```
697    fn svd_read(
698        self,
699        session: &mut dyn Engine,
700    ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor)>;
701    /// Compute singular values from a borrowed tensor read target.
702    ///
703    /// Eligible faer host views are consumed without a full input copy. A
704    /// provider that requires owned compact storage may materialize explicitly;
705    /// unsupported providers return a typed error.
706    ///
707    /// # Errors
708    /// Returns [`tenferro_tensor::Error::Unsupported`] when the selected
709    /// backend has no borrowed values-only capability.
710    ///
711    /// # Examples
712    ///
713    /// ```rust
714    /// # use tenferro_cpu::CpuEngine;
715    /// # use tenferro_linalg::TensorReadLinalgExt;
716    /// # use tenferro_tensor::{Tensor, TensorRead};
717    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 0.0, 4.0])?;
718    /// # let mut host = tenferro_cpu::CpuEngine::serial();
719    /// let values = TensorRead::from_tensor(&a).svdvals_read(&mut host)?;
720    /// assert_eq!(values.as_slice::<f64>()?, &[4.0, 2.0]);
721    /// # Ok::<(), tenferro_tensor::Error>(())
722    /// ```
723    fn svdvals_read(self, session: &mut dyn Engine) -> tenferro_tensor::Result<Tensor>;
724    /// # Errors
725    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
726    /// Returns validation errors for metadata/options, plus read/materialization or SVD errors.
727    /// # Examples
728    ///
729    /// ```rust
730    /// # use tenferro_cpu::CpuEngine;
731    /// # use tenferro_linalg::{SvdOptions, TensorReadLinalgExt};
732    /// # use tenferro_tensor::{Tensor, TensorRead};
733    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 0.0, 4.0])?;
734    /// # let mut host = tenferro_cpu::CpuEngine::serial();
735    /// let (_u, s, _vt) = {
736    ///     TensorRead::from_tensor(&a).svd_with_options_read(SvdOptions::default(), &mut host)
737    /// }?;
738    /// assert_eq!(s.shape(), &[2]);
739    /// # Ok::<(), tenferro_tensor::Error>(())
740    /// ```
741    fn svd_with_options_read(
742        self,
743        options: SvdOptions,
744        session: &mut dyn Engine,
745    ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor)>;
746    /// Compute the full-matrices SVD `(U, S, Vt)` from a borrowed tensor read
747    /// target, with `U` shaped `m x m` and `Vt` shaped `n x n`.
748    ///
749    /// Eligible faer host views (rank 2, host placement, non-negative strides)
750    /// are consumed without an input copy. A provider that requires owned
751    /// compact storage materializes explicitly inside the provider boundary;
752    /// unsupported providers return a typed error instead of a thin result. The
753    /// borrowed source is never modified.
754    ///
755    /// # Errors
756    /// Returns [`tenferro_tensor::Error::Unsupported`] when the selected
757    /// backend or CPU provider has no borrowed full-matrices capability.
758    /// Returns validation errors for an unsupported rank, dtype, or placement,
759    /// plus same-placement materialization, backend, numerical, or
760    /// output-contract errors.
761    /// # Examples
762    ///
763    /// ```rust
764    /// # use tenferro_cpu::CpuEngine;
765    /// # use tenferro_linalg::TensorReadLinalgExt;
766    /// # use tenferro_tensor::{Tensor, TensorRead};
767    /// # let a = Tensor::from_vec_col_major(vec![1, 2], vec![1.0_f64, 1.0])?;
768    /// # let mut host = tenferro_cpu::CpuEngine::serial();
769    /// let (u, s, vt) = {
770    ///     TensorRead::from_tensor(&a).svd_full_read(&mut host)
771    /// }?;
772    /// assert_eq!(u.shape(), &[1, 1]);
773    /// assert_eq!(s.shape(), &[1]);
774    /// assert_eq!(vt.shape(), &[2, 2]);
775    /// # Ok::<(), tenferro_tensor::Error>(())
776    /// ```
777    fn svd_full_read(
778        self,
779        session: &mut dyn Engine,
780    ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor)>;
781    /// # Errors
782    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
783    /// Returns validation, same-placement materialization, backend, numerical, or contract errors.
784    /// # Examples
785    ///
786    /// ```rust
787    /// # use tenferro_cpu::CpuEngine;
788    /// # use tenferro_linalg::TensorReadLinalgExt;
789    /// # use tenferro_tensor::{Tensor, TensorRead};
790    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 1.0])?;
791    /// # let mut host = tenferro_cpu::CpuEngine::serial();
792    /// let (q, r) = { TensorRead::from_tensor(&a).qr_read(&mut host) }?;
793    /// assert_eq!(q.shape(), &[2, 2]);
794    /// assert_eq!(r.shape(), &[2, 2]);
795    /// # Ok::<(), tenferro_tensor::Error>(())
796    /// ```
797    fn qr_read(self, session: &mut dyn Engine) -> tenferro_tensor::Result<(Tensor, Tensor)>;
798    /// # Errors
799    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
800    /// Returns validation errors for metadata/options, plus read/materialization or QR errors.
801    /// # Examples
802    ///
803    /// ```rust
804    /// # use tenferro_cpu::CpuEngine;
805    /// # use tenferro_linalg::{QrOptions, TensorReadLinalgExt};
806    /// # use tenferro_tensor::{Tensor, TensorRead};
807    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 1.0])?;
808    /// # let mut host = tenferro_cpu::CpuEngine::serial();
809    /// let (q, r) = {
810    ///     TensorRead::from_tensor(&a).qr_with_options_read(QrOptions::default(), &mut host)
811    /// }?;
812    /// assert_eq!(q.shape(), &[2, 2]);
813    /// assert_eq!(r.shape(), &[2, 2]);
814    /// # Ok::<(), tenferro_tensor::Error>(())
815    /// ```
816    fn qr_with_options_read(
817        self,
818        options: QrOptions,
819        session: &mut dyn Engine,
820    ) -> tenferro_tensor::Result<(Tensor, Tensor)>;
821    /// Compute rank-revealing QR from a borrowed tensor read.
822    ///
823    /// # Errors
824    /// Returns validation, same-placement materialization, numerical, backend,
825    /// or explicit unsupported errors.
826    ///
827    /// # Examples
828    ///
829    /// ```rust
830    /// # use tenferro_cpu::CpuEngine;
831    /// # use tenferro_linalg::{RankRevealingQrOptions, TensorReadLinalgExt};
832    /// # use tenferro_tensor::{Tensor, TensorRead};
833    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 2.0])?;
834    /// # let mut host = tenferro_cpu::CpuEngine::serial();
835    /// let result = {
836    ///     TensorRead::from_tensor(&a).rank_revealing_qr_read(
837    ///         RankRevealingQrOptions::default(), &mut host)
838    /// }?;
839    /// assert_eq!(result.rank.as_slice::<i64>()?, &[2]);
840    /// # Ok::<(), tenferro_tensor::Error>(())
841    /// ```
842    fn rank_revealing_qr_read(
843        self,
844        options: RankRevealingQrOptions,
845        session: &mut dyn Engine,
846    ) -> tenferro_tensor::Result<RankRevealingQrResult<Tensor>>;
847    /// # Errors
848    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
849    /// Returns validation, same-placement materialization, backend, numerical, or contract errors.
850    /// # Examples
851    ///
852    /// ```rust
853    /// # use tenferro_cpu::CpuEngine;
854    /// # use tenferro_linalg::TensorReadLinalgExt;
855    /// # use tenferro_tensor::{Tensor, TensorRead};
856    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 3.0, 2.0, 4.0])?;
857    /// # let mut host = tenferro_cpu::CpuEngine::serial();
858    /// let (_p, l, u, _parity) = { TensorRead::from_tensor(&a).lu_read(&mut host) }?;
859    /// assert_eq!(l.shape(), &[2, 2]);
860    /// assert_eq!(u.shape(), &[2, 2]);
861    /// # Ok::<(), tenferro_tensor::Error>(())
862    /// ```
863    fn lu_read(
864        self,
865        session: &mut dyn Engine,
866    ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor, Tensor)>;
867    /// # Errors
868    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
869    /// Returns validation, same-placement materialization, backend, numerical, or contract errors.
870    /// # Examples
871    ///
872    /// ```rust
873    /// # use tenferro_cpu::CpuEngine;
874    /// # use tenferro_linalg::TensorReadLinalgExt;
875    /// # use tenferro_tensor::{Tensor, TensorRead};
876    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 3.0, 2.0, 4.0])?;
877    /// # let mut host = tenferro_cpu::CpuEngine::serial();
878    /// let (p, _l, _u, q, _parity) = { TensorRead::from_tensor(&a).full_piv_lu_read(&mut host) }?;
879    /// assert_eq!(p.shape(), &[2, 2]);
880    /// assert_eq!(q.shape(), &[2, 2]);
881    /// # Ok::<(), tenferro_tensor::Error>(())
882    /// ```
883    fn full_piv_lu_read(
884        self,
885        session: &mut dyn Engine,
886    ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor, Tensor, Tensor)>;
887    /// # Errors
888    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
889    /// Returns incompatible-input validation, read/materialization, backend, or singular errors.
890    /// # Examples
891    ///
892    /// ```rust
893    /// # use tenferro_cpu::CpuEngine;
894    /// # use tenferro_linalg::TensorReadLinalgExt;
895    /// # use tenferro_tensor::{Tensor, TensorRead};
896    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![0.0_f64, 2.0, 1.0, 3.0])?;
897    /// # let b = Tensor::from_vec_col_major(vec![2, 1], vec![-1.0_f64, 5.0])?;
898    /// # let mut host = tenferro_cpu::CpuEngine::serial();
899    /// let x = {
900    ///     TensorRead::from_tensor(&a).full_piv_lu_solve_read(TensorRead::from_tensor(&b), &mut host)
901    /// }?;
902    /// assert_eq!(x.shape(), &[2, 1]);
903    /// # Ok::<(), tenferro_tensor::Error>(())
904    /// ```
905    fn full_piv_lu_solve_read(
906        self,
907        b: TensorRead<'_>,
908        session: &mut dyn Engine,
909    ) -> tenferro_tensor::Result<Tensor>;
910    /// The CUDA provider does not inspect the cuSOLVER `getrf` status flag on
911    /// the forward solve path, so it performs no per-call non-singularity check
912    /// and issues no host round trip. An exactly singular input is left to the
913    /// caller's verification, as in `torch.linalg.solve`. CPU providers may
914    /// still report their own exactly singular factor as `Error::Singular`.
915    ///
916    /// # Errors
917    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
918    /// Returns incompatible-input validation, read/materialization, backend, or singular errors.
919    /// # Examples
920    ///
921    /// ```rust
922    /// # use tenferro_cpu::CpuEngine;
923    /// # use tenferro_linalg::TensorReadLinalgExt;
924    /// # use tenferro_tensor::{Tensor, TensorRead};
925    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 0.0, 4.0])?;
926    /// # let b = Tensor::from_vec_col_major(vec![2, 1], vec![4.0_f64, 8.0])?;
927    /// # let mut host = tenferro_cpu::CpuEngine::serial();
928    /// let x = { TensorRead::from_tensor(&a).solve_read(TensorRead::from_tensor(&b), &mut host) }?;
929    /// assert_eq!(x.as_slice::<f64>()?, &[2.0, 2.0]);
930    /// # Ok::<(), tenferro_tensor::Error>(())
931    /// ```
932    fn solve_read(
933        self,
934        b: TensorRead<'_>,
935        session: &mut dyn Engine,
936    ) -> tenferro_tensor::Result<Tensor>;
937    /// Solve into a caller-owned destination without allocating the result at
938    /// the public API boundary.
939    ///
940    /// Backends with a native path may write directly into a compatible target;
941    /// the trait default preserves the ordinary solve-read plus copy behavior.
942    ///
943    /// The CUDA provider does not inspect the cuSOLVER `getrf` status flag on
944    /// the forward solve path, so it performs no per-call non-singularity check
945    /// and issues no host round trip. An exactly singular input is left to the
946    /// caller's verification, as in `torch.linalg.solve`. CPU providers may
947    /// still report their own exactly singular factor as `Error::Singular`.
948    ///
949    /// # Errors
950    /// Returns `tenferro_tensor_core::ShapeMismatch` or
951    /// `tenferro_tensor_core::ValidationError::DTypeMismatch` for incompatible
952    /// destination metadata, `tenferro_tensor_core::ValidationError::InvalidArgument`
953    /// for aliasing or placement violations, `Error::Unsupported` when the
954    /// extension implementation or provider is unavailable, and `Error::Singular`
955    /// for a singular system.
956    /// # Examples
957    ///
958    /// ```rust
959    /// # use tenferro_cpu::CpuEngine;
960    /// # use tenferro_linalg::TensorReadLinalgExt;
961    /// # use tenferro_tensor::{Tensor, TensorRead, TensorWrite};
962    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 0.0, 4.0])?;
963    /// # let b = Tensor::from_vec_col_major(vec![2, 1], vec![4.0_f64, 8.0])?;
964    /// # let mut out = Tensor::from_vec_col_major(vec![2, 1], vec![0.0_f64; 2])?;
965    /// # let mut host = tenferro_cpu::CpuEngine::serial();
966    /// {
967    ///         TensorRead::from_tensor(&a).solve_read_into(
968    ///             TensorRead::from_tensor(&b),
969    ///             TensorWrite::from_tensor(&mut out),
970    ///             &mut host,
971    ///         )
972    ///     }?;
973    /// assert_eq!(out.as_slice::<f64>()?, &[2.0, 2.0]);
974    /// # Ok::<(), tenferro_tensor::Error>(())
975    /// ```
976    fn solve_read_into(
977        self,
978        b: TensorRead<'_>,
979        out: TensorWrite<'_>,
980        session: &mut dyn Engine,
981    ) -> tenferro_tensor::Result<()>
982    where
983        Self: Sized,
984    {
985        let _ = (self, b, out, session);
986        Err(tenferro_tensor::Error::unsupported(
987            "solve_read_into",
988            "this tensor-read extension implementation does not accept borrowed solve targets",
989        ))
990    }
991    /// # Errors
992    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
993    /// Returns matrix validation, read/materialization, backend, or positive-definiteness errors.
994    /// # Examples
995    ///
996    /// ```rust
997    /// # use tenferro_cpu::CpuEngine;
998    /// # use tenferro_linalg::TensorReadLinalgExt;
999    /// # use tenferro_tensor::{Tensor, TensorRead};
1000    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![4.0_f64, 2.0, 2.0, 3.0])?;
1001    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1002    /// let l = { TensorRead::from_tensor(&a).cholesky_read(&mut host) }?;
1003    /// assert_eq!(l.shape(), &[2, 2]);
1004    /// # Ok::<(), tenferro_tensor::Error>(())
1005    /// ```
1006    fn cholesky_read(self, session: &mut dyn Engine) -> tenferro_tensor::Result<Tensor>;
1007    /// # Errors
1008    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
1009    /// Returns validation, read/materialization, backend, convergence, or contract errors.
1010    /// # Examples
1011    ///
1012    /// ```rust
1013    /// # use tenferro_cpu::CpuEngine;
1014    /// # use tenferro_linalg::TensorReadLinalgExt;
1015    /// # use tenferro_tensor::{Tensor, TensorRead};
1016    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 3.0])?;
1017    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1018    /// let (values, vectors) = { TensorRead::from_tensor(&a).eigh_read(&mut host) }?;
1019    /// assert_eq!(values.as_slice::<f64>()?, &[1.0, 3.0]);
1020    /// assert_eq!(vectors.shape(), &[2, 2]);
1021    /// # Ok::<(), tenferro_tensor::Error>(())
1022    /// ```
1023    fn eigh_read(self, session: &mut dyn Engine) -> tenferro_tensor::Result<(Tensor, Tensor)>;
1024    /// # Errors
1025    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
1026    /// Returns validation errors for metadata/options, plus read or Eigh backend errors.
1027    /// # Examples
1028    ///
1029    /// ```rust
1030    /// # use tenferro_cpu::CpuEngine;
1031    /// # use tenferro_linalg::{EighOptions, TensorReadLinalgExt};
1032    /// # use tenferro_tensor::{Tensor, TensorRead};
1033    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 3.0])?;
1034    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1035    /// let (values, vectors) = {
1036    ///     TensorRead::from_tensor(&a).eigh_with_options_read(EighOptions::default(), &mut host)
1037    /// }?;
1038    /// assert_eq!(values.shape(), &[2]);
1039    /// assert_eq!(vectors.shape(), &[2, 2]);
1040    /// # Ok::<(), tenferro_tensor::Error>(())
1041    /// ```
1042    fn eigh_with_options_read(
1043        self,
1044        options: EighOptions,
1045        session: &mut dyn Engine,
1046    ) -> tenferro_tensor::Result<(Tensor, Tensor)>;
1047    /// # Errors
1048    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
1049    /// Returns validation, read/materialization, backend, convergence, or contract errors.
1050    /// # Examples
1051    ///
1052    /// ```rust
1053    /// # use tenferro_cpu::CpuEngine;
1054    /// # use tenferro_linalg::TensorReadLinalgExt;
1055    /// # use tenferro_tensor::{Tensor, TensorRead};
1056    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 2.0])?;
1057    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1058    /// let (values, vectors) = { TensorRead::from_tensor(&a).eig_read(&mut host) }?;
1059    /// assert_eq!(values.shape(), &[2]);
1060    /// assert_eq!(vectors.shape(), &[2, 2]);
1061    /// # Ok::<(), tenferro_tensor::Error>(())
1062    /// ```
1063    fn eig_read(self, session: &mut dyn Engine) -> tenferro_tensor::Result<(Tensor, Tensor)>;
1064    /// # Errors
1065    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
1066    /// Returns incompatible-input/flag validation, read, backend, or singular errors.
1067    #[allow(clippy::too_many_arguments)]
1068    /// # Examples
1069    ///
1070    /// ```rust
1071    /// # use tenferro_cpu::CpuEngine;
1072    /// # use tenferro_linalg::TensorReadLinalgExt;
1073    /// # use tenferro_tensor::{Tensor, TensorRead};
1074    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 1.0, 3.0])?;
1075    /// # let b = Tensor::from_vec_col_major(vec![2, 1], vec![4.0_f64, 9.0])?;
1076    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1077    /// let x = {
1078    ///     TensorRead::from_tensor(&a).triangular_solve_read(
1079    ///         TensorRead::from_tensor(&b),
1080    ///         true,
1081    ///         false,
1082    ///         false,
1083    ///         false,
1084    ///         &mut host,
1085    ///     )
1086    /// }?;
1087    /// assert_eq!(x.shape(), &[2, 1]);
1088    /// # Ok::<(), tenferro_tensor::Error>(())
1089    /// ```
1090    fn triangular_solve_read(
1091        self,
1092        b: TensorRead<'_>,
1093        left_side: bool,
1094        lower: bool,
1095        transpose_a: bool,
1096        unit_diagonal: bool,
1097        session: &mut dyn Engine,
1098    ) -> tenferro_tensor::Result<Tensor>;
1099    /// # Errors
1100    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
1101    /// Returns validation, read/materialization, backend, numerical, or LU contract errors.
1102    /// # Examples
1103    ///
1104    /// ```rust
1105    /// # use tenferro_cpu::CpuEngine;
1106    /// # use tenferro_linalg::TensorReadLinalgExt;
1107    /// # use tenferro_tensor::{Tensor, TensorRead};
1108    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 0.0, 4.0])?;
1109    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1110    /// let (sign, logabsdet) = { TensorRead::from_tensor(&a).slogdet_read(&mut host) }?;
1111    /// assert_eq!(sign.as_slice::<f64>()?, &[1.0]);
1112    /// assert_eq!(logabsdet.shape(), &[] as &[usize]);
1113    /// # Ok::<(), tenferro_tensor::Error>(())
1114    /// ```
1115    fn slogdet_read(self, session: &mut dyn Engine) -> tenferro_tensor::Result<(Tensor, Tensor)>;
1116    /// # Errors
1117    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
1118    /// Returns validation, read/materialization, backend, numerical, or contract errors.
1119    /// # Examples
1120    ///
1121    /// ```rust
1122    /// # use tenferro_cpu::CpuEngine;
1123    /// # use tenferro_linalg::TensorReadLinalgExt;
1124    /// # use tenferro_tensor::{Tensor, TensorRead};
1125    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 0.0, 4.0])?;
1126    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1127    /// let determinant = { TensorRead::from_tensor(&a).det_read(&mut host) }?;
1128    /// assert!((determinant.as_slice::<f64>()?[0] - 8.0).abs() < 1.0e-12);
1129    /// # Ok::<(), tenferro_tensor::Error>(())
1130    /// ```
1131    fn det_read(self, session: &mut dyn Engine) -> tenferro_tensor::Result<Tensor>;
1132    /// # Errors
1133    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
1134    /// Returns validation, read/materialization, backend, or singular-solve errors.
1135    /// # Examples
1136    ///
1137    /// ```rust
1138    /// # use tenferro_cpu::CpuEngine;
1139    /// # use tenferro_linalg::TensorReadLinalgExt;
1140    /// # use tenferro_tensor::{Tensor, TensorRead};
1141    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 0.0, 4.0])?;
1142    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1143    /// let inverse = { TensorRead::from_tensor(&a).inv_read(&mut host) }?;
1144    /// assert_eq!(inverse.as_slice::<f64>()?, &[0.5, 0.0, 0.0, 0.25]);
1145    /// # Ok::<(), tenferro_tensor::Error>(())
1146    /// ```
1147    fn inv_read(self, session: &mut dyn Engine) -> tenferro_tensor::Result<Tensor>;
1148    /// # Errors
1149    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
1150    /// Returns validation, read/materialization, backend, convergence, or contract errors.
1151    /// # Examples
1152    ///
1153    /// ```rust
1154    /// # use tenferro_cpu::CpuEngine;
1155    /// # use tenferro_linalg::TensorReadLinalgExt;
1156    /// # use tenferro_tensor::{Tensor, TensorRead};
1157    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 3.0])?;
1158    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1159    /// let values = { TensorRead::from_tensor(&a).eigvalsh_read(&mut host) }?;
1160    /// assert_eq!(values.as_slice::<f64>()?, &[1.0, 3.0]);
1161    /// # Ok::<(), tenferro_tensor::Error>(())
1162    /// ```
1163    fn eigvalsh_read(self, session: &mut dyn Engine) -> tenferro_tensor::Result<Tensor>;
1164    /// # Errors
1165    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
1166    /// Returns validation, read/materialization, backend, convergence, or contract errors.
1167    /// # Examples
1168    ///
1169    /// ```rust
1170    /// # use tenferro_cpu::CpuEngine;
1171    /// # use tenferro_linalg::TensorReadLinalgExt;
1172    /// # use tenferro_tensor::{Tensor, TensorRead};
1173    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 2.0])?;
1174    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1175    /// let values = { TensorRead::from_tensor(&a).eigvals_read(&mut host) }?;
1176    /// assert_eq!(values.shape(), &[2]);
1177    /// # Ok::<(), tenferro_tensor::Error>(())
1178    /// ```
1179    fn eigvals_read(self, session: &mut dyn Engine) -> tenferro_tensor::Result<Tensor>;
1180    /// Compute the Moore-Penrose pseudoinverse with the dtype-default cutoff.
1181    ///
1182    /// The threshold is applied to an accuracy-preserving default SVD
1183    /// (`SvdDriver::Auto`), so a rank-deficient rank is retained or dropped the
1184    /// same way as on CPU.
1185    ///
1186    /// # Errors
1187    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
1188    /// Returns validation, read/materialization, backend, numerical, or SVD contract errors.
1189    /// # Examples
1190    ///
1191    /// ```rust
1192    /// # use tenferro_cpu::CpuEngine;
1193    /// # use tenferro_linalg::TensorReadLinalgExt;
1194    /// # use tenferro_tensor::{Tensor, TensorRead};
1195    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 0.0, 4.0])?;
1196    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1197    /// let pseudoinverse = { TensorRead::from_tensor(&a).pinv_read(&mut host) }?;
1198    /// assert_eq!(pseudoinverse.shape(), &[2, 2]);
1199    /// # Ok::<(), tenferro_tensor::Error>(())
1200    /// ```
1201    fn pinv_read(self, session: &mut dyn Engine) -> tenferro_tensor::Result<Tensor>;
1202    /// # Errors
1203    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
1204    /// Returns a validation error for invalid `rtol`, plus errors from [`Self::pinv_read`].
1205    /// # Examples
1206    ///
1207    /// ```rust
1208    /// # use tenferro_cpu::CpuEngine;
1209    /// # use tenferro_linalg::TensorReadLinalgExt;
1210    /// # use tenferro_tensor::{Tensor, TensorRead};
1211    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 0.0, 4.0])?;
1212    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1213    /// let pseudoinverse = { TensorRead::from_tensor(&a).pinv_with_rtol_read(1.0e-12, &mut host) }?;
1214    /// assert_eq!(pseudoinverse.shape(), &[2, 2]);
1215    /// # Ok::<(), tenferro_tensor::Error>(())
1216    /// ```
1217    fn pinv_with_rtol_read(
1218        self,
1219        rtol: f64,
1220        session: &mut dyn Engine,
1221    ) -> tenferro_tensor::Result<Tensor>;
1222    /// # Errors
1223    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
1224    /// Returns validation errors for axes/order combinations or required read/backend operations.
1225    /// # Examples
1226    ///
1227    /// ```rust
1228    /// # use tenferro_cpu::CpuEngine;
1229    /// # use tenferro_linalg::TensorReadLinalgExt;
1230    /// # use tenferro_tensor::{Tensor, TensorRead};
1231    /// # let a = Tensor::from_vec_col_major(vec![2, 2], vec![3.0_f64, 0.0, 0.0, 4.0])?;
1232    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1233    /// let frobenius = { TensorRead::from_tensor(&a).norm_read(None, None, false, &mut host) }?;
1234    /// assert_eq!(frobenius.shape(), &[] as &[usize]);
1235    /// # Ok::<(), tenferro_tensor::Error>(())
1236    /// ```
1237    fn norm_read(
1238        self,
1239        ord: Option<f64>,
1240        dim: Option<&[usize]>,
1241        keepdim: bool,
1242        session: &mut dyn Engine,
1243    ) -> tenferro_tensor::Result<Tensor>;
1244}
1245
1246/// Linear algebra methods with statically typed inputs and outputs.
1247///
1248/// Singular values, Hermitian eigenvalues, determinant log-magnitudes, and
1249/// norms use `T::Real`; general eigenvalues and eigenvectors use `T::Complex`.
1250///
1251/// # Examples
1252///
1253/// ```rust
1254/// use tenferro_cpu::CpuEngine;
1255/// use tenferro_linalg::TypedTensorLinalgExt;
1256/// use tenferro_tensor::TypedTensor;
1257///
1258/// let input = TypedTensor::<f64>::from_vec_col_major(
1259///     vec![2, 2],
1260///     vec![2.0, 0.0, 0.0, 4.0],
1261/// )?;
1262/// let mut host = tenferro_cpu::CpuEngine::serial();
1263/// let (_u, singular_values, _vt) = input.svd(&mut host)?;
1264/// assert_eq!(singular_values.as_slice()?, &[4.0, 2.0]);
1265/// # Ok::<(), tenferro_tensor::Error>(())
1266/// ```
1267pub trait TypedTensorLinalgExt<T: LinalgScalar> {
1268    /// # Errors
1269    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
1270    /// Returns validation, backend, numerical, output-contract, or typed-downcast errors.
1271    /// # Examples
1272    ///
1273    /// ```rust
1274    /// # use tenferro_cpu::CpuEngine;
1275    /// # use tenferro_linalg::TypedTensorLinalgExt;
1276    /// # use tenferro_tensor::TypedTensor;
1277    /// # let a = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![2.0, 0.0, 0.0, 4.0])?;
1278    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1279    /// let (_u, s, _vt) = a.svd(&mut host)?;
1280    /// assert_eq!(s.as_slice()?, &[4.0, 2.0]);
1281    /// # Ok::<(), tenferro_tensor::Error>(())
1282    /// ```
1283    fn svd(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<TypedSvd<T>>;
1284    /// Compute singular values without allocating singular-vector outputs.
1285    ///
1286    /// # Errors
1287    /// Returns [`tenferro_tensor::Error::Unsupported`] when the selected
1288    /// backend has no values-only capability.
1289    ///
1290    /// # Examples
1291    ///
1292    /// ```rust
1293    /// # use tenferro_cpu::CpuEngine;
1294    /// # use tenferro_linalg::TypedTensorLinalgExt;
1295    /// # use tenferro_tensor::TypedTensor;
1296    /// # let a = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![2.0, 0.0, 0.0, 4.0])?;
1297    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1298    /// let values = a.svdvals(&mut host)?;
1299    /// assert_eq!(values.as_slice()?, &[4.0, 2.0]);
1300    /// # Ok::<(), tenferro_tensor::Error>(())
1301    /// ```
1302    fn svdvals(
1303        &self,
1304        session: &mut dyn Engine,
1305    ) -> tenferro_tensor::Result<TypedTensor<<T as TensorScalar>::Real>>;
1306    /// # Errors
1307    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
1308    /// Returns validation errors for metadata/options, plus backend or typed-output errors.
1309    /// # Examples
1310    ///
1311    /// ```rust
1312    /// # use tenferro_cpu::CpuEngine;
1313    /// # use tenferro_linalg::{SvdOptions, TypedTensorLinalgExt};
1314    /// # use tenferro_tensor::TypedTensor;
1315    /// # let a = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![2.0, 0.0, 0.0, 4.0])?;
1316    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1317    /// let (_u, s, _vt) = { a.svd_with_options(SvdOptions::default(), &mut host) }?;
1318    /// assert_eq!(s.as_slice()?, &[4.0, 2.0]);
1319    /// # Ok::<(), tenferro_tensor::Error>(())
1320    /// ```
1321    fn svd_with_options(
1322        &self,
1323        options: SvdOptions,
1324        session: &mut dyn Engine,
1325    ) -> tenferro_tensor::Result<TypedSvd<T>>;
1326    /// Compute the full-matrices SVD `(U, S, Vt)` with `U` shaped `m x m` and
1327    /// `Vt` shaped `n x n`. The singular values keep the real counterpart dtype
1328    /// of `T`, exactly as [`TypedTensorLinalgExt::svd`] does.
1329    ///
1330    /// # Errors
1331    /// Returns [`tenferro_tensor::Error::Unsupported`] when the selected
1332    /// backend or CPU provider has no full-matrices kernel. Returns validation
1333    /// errors for an unsupported rank or dtype, plus backend, numerical,
1334    /// output-contract, or typed-downcast errors.
1335    /// # Examples
1336    ///
1337    /// ```rust
1338    /// # use tenferro_cpu::CpuEngine;
1339    /// # use tenferro_linalg::TypedTensorLinalgExt;
1340    /// # use tenferro_tensor::TypedTensor;
1341    /// # let a = TypedTensor::<f64>::from_vec_col_major(vec![1, 2], vec![1.0, 1.0])?;
1342    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1343    /// let (u, s, vt) = a.svd_full(&mut host)?;
1344    /// assert_eq!(u.shape(), &[1, 1]);
1345    /// assert_eq!(vt.shape(), &[2, 2]);
1346    /// assert!((s.as_slice()?[0] - 2.0_f64.sqrt()).abs() < 1e-12);
1347    /// # Ok::<(), tenferro_tensor::Error>(())
1348    /// ```
1349    fn svd_full(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<TypedSvd<T>>;
1350    /// # Errors
1351    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
1352    /// Returns validation, backend, numerical, output-contract, or typed-downcast errors.
1353    /// # Examples
1354    ///
1355    /// ```rust
1356    /// # use tenferro_cpu::CpuEngine;
1357    /// # use tenferro_linalg::TypedTensorLinalgExt;
1358    /// # use tenferro_tensor::TypedTensor;
1359    /// # let a = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![1.0, 0.0, 0.0, 1.0])?;
1360    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1361    /// let (q, r) = a.qr(&mut host)?;
1362    /// assert_eq!(q.shape(), &[2, 2]);
1363    /// assert_eq!(r.shape(), &[2, 2]);
1364    /// # Ok::<(), tenferro_tensor::Error>(())
1365    /// ```
1366    fn qr(
1367        &self,
1368        session: &mut dyn Engine,
1369    ) -> tenferro_tensor::Result<(TypedTensor<T>, TypedTensor<T>)>;
1370    /// # Errors
1371    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
1372    /// Returns validation errors for metadata/options, plus backend or typed-output errors.
1373    /// # Examples
1374    ///
1375    /// ```rust
1376    /// # use tenferro_cpu::CpuEngine;
1377    /// # use tenferro_linalg::{QrOptions, TypedTensorLinalgExt};
1378    /// # use tenferro_tensor::TypedTensor;
1379    /// # let a = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![1.0, 0.0, 0.0, 1.0])?;
1380    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1381    /// let (q, r) = { a.qr_with_options(QrOptions::default(), &mut host) }?;
1382    /// assert_eq!(q.shape(), &[2, 2]);
1383    /// assert_eq!(r.shape(), &[2, 2]);
1384    /// # Ok::<(), tenferro_tensor::Error>(())
1385    /// ```
1386    fn qr_with_options(
1387        &self,
1388        options: QrOptions,
1389        session: &mut dyn Engine,
1390    ) -> tenferro_tensor::Result<(TypedTensor<T>, TypedTensor<T>)>;
1391    /// Compute typed rank-revealing QR with `i64` permutation and rank tensors.
1392    ///
1393    /// # Errors
1394    /// Returns validation, numerical, backend, unsupported, or typed-output
1395    /// contract errors.
1396    ///
1397    /// # Examples
1398    ///
1399    /// ```rust
1400    /// # use tenferro_cpu::CpuEngine;
1401    /// # use tenferro_linalg::{RankRevealingQrOptions, TypedTensorLinalgExt};
1402    /// # use tenferro_tensor::TypedTensor;
1403    /// # let a = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![1.0, 0.0, 0.0, 2.0])?;
1404    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1405    /// let result = {
1406    ///     a.rank_revealing_qr(RankRevealingQrOptions::default(), &mut host)
1407    /// }?;
1408    /// assert_eq!(result.rank.as_slice()?, &[2_i64]);
1409    /// # Ok::<(), tenferro_tensor::Error>(())
1410    /// ```
1411    fn rank_revealing_qr(
1412        &self,
1413        options: RankRevealingQrOptions,
1414        session: &mut dyn Engine,
1415    ) -> tenferro_tensor::Result<TypedRankRevealingQrResult<T>>;
1416    /// # Errors
1417    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
1418    /// Returns validation, backend, numerical, output-contract, or typed-downcast errors.
1419    /// # Examples
1420    ///
1421    /// ```rust
1422    /// # use tenferro_cpu::CpuEngine;
1423    /// # use tenferro_linalg::TypedTensorLinalgExt;
1424    /// # use tenferro_tensor::TypedTensor;
1425    /// # let a = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![1.0, 3.0, 2.0, 4.0])?;
1426    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1427    /// let (_p, l, u, _parity) = a.lu(&mut host)?;
1428    /// assert_eq!(l.shape(), &[2, 2]);
1429    /// assert_eq!(u.shape(), &[2, 2]);
1430    /// # Ok::<(), tenferro_tensor::Error>(())
1431    /// ```
1432    fn lu(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<TypedLu<T>>;
1433    /// # Errors
1434    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
1435    /// Returns validation, backend, numerical, output-contract, or typed-downcast errors.
1436    /// # Examples
1437    ///
1438    /// ```rust
1439    /// # use tenferro_cpu::CpuEngine;
1440    /// # use tenferro_linalg::TypedTensorLinalgExt;
1441    /// # use tenferro_tensor::TypedTensor;
1442    /// # let a = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![1.0, 3.0, 2.0, 4.0])?;
1443    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1444    /// let (p, _l, _u, q, _parity) = a.full_piv_lu(&mut host)?;
1445    /// assert_eq!(p.shape(), &[2, 2]);
1446    /// assert_eq!(q.shape(), &[2, 2]);
1447    /// # Ok::<(), tenferro_tensor::Error>(())
1448    /// ```
1449    fn full_piv_lu(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<TypedFullPivLu<T>>;
1450    /// # Errors
1451    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
1452    /// Returns incompatible-input validation, backend, singular, or typed-downcast errors.
1453    /// # Examples
1454    ///
1455    /// ```rust
1456    /// # use tenferro_cpu::CpuEngine;
1457    /// # use tenferro_linalg::TypedTensorLinalgExt;
1458    /// # use tenferro_tensor::TypedTensor;
1459    /// # let a = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![0.0, 2.0, 1.0, 3.0])?;
1460    /// # let b = TypedTensor::<f64>::from_vec_col_major(vec![2, 1], vec![-1.0, 5.0])?;
1461    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1462    /// let x = a.full_piv_lu_solve(&b, &mut host)?;
1463    /// assert_eq!(x.shape(), &[2, 1]);
1464    /// # Ok::<(), tenferro_tensor::Error>(())
1465    /// ```
1466    fn full_piv_lu_solve(
1467        &self,
1468        b: &TypedTensor<T>,
1469        session: &mut dyn Engine,
1470    ) -> tenferro_tensor::Result<TypedTensor<T>>;
1471    /// The CUDA provider does not inspect the cuSOLVER `getrf` status flag on
1472    /// the forward solve path, so it performs no per-call non-singularity check
1473    /// and issues no host round trip. An exactly singular input is left to the
1474    /// caller's verification, as in `torch.linalg.solve`. CPU providers may
1475    /// still report their own exactly singular factor as `Error::Singular`.
1476    ///
1477    /// # Errors
1478    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
1479    /// Returns incompatible-input validation, backend, singular, or typed-downcast errors.
1480    /// # Examples
1481    ///
1482    /// ```rust
1483    /// # use tenferro_cpu::CpuEngine;
1484    /// # use tenferro_linalg::TypedTensorLinalgExt;
1485    /// # use tenferro_tensor::TypedTensor;
1486    /// # let a = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![2.0, 0.0, 0.0, 4.0])?;
1487    /// # let b = TypedTensor::<f64>::from_vec_col_major(vec![2, 1], vec![4.0, 8.0])?;
1488    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1489    /// let x = a.solve(&b, &mut host)?;
1490    /// assert_eq!(x.as_slice()?, &[2.0, 2.0]);
1491    /// # Ok::<(), tenferro_tensor::Error>(())
1492    /// ```
1493    fn solve(
1494        &self,
1495        b: &TypedTensor<T>,
1496        session: &mut dyn Engine,
1497    ) -> tenferro_tensor::Result<TypedTensor<T>>;
1498    /// # Errors
1499    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
1500    /// Returns matrix validation, backend, positive-definiteness, or typed-downcast errors.
1501    /// # Examples
1502    ///
1503    /// ```rust
1504    /// # use tenferro_cpu::CpuEngine;
1505    /// # use tenferro_linalg::TypedTensorLinalgExt;
1506    /// # use tenferro_tensor::TypedTensor;
1507    /// # let a = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![4.0, 2.0, 2.0, 3.0])?;
1508    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1509    /// let l = a.cholesky(&mut host)?;
1510    /// assert_eq!(l.shape(), &[2, 2]);
1511    /// # Ok::<(), tenferro_tensor::Error>(())
1512    /// ```
1513    fn cholesky(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<TypedTensor<T>>;
1514    /// # Errors
1515    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
1516    /// Returns validation, backend, convergence, output-contract, or typed-downcast errors.
1517    /// # Examples
1518    ///
1519    /// ```rust
1520    /// # use tenferro_cpu::CpuEngine;
1521    /// # use tenferro_linalg::TypedTensorLinalgExt;
1522    /// # use tenferro_tensor::TypedTensor;
1523    /// # let a = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![1.0, 0.0, 0.0, 3.0])?;
1524    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1525    /// let (values, vectors) = a.eigh(&mut host)?;
1526    /// assert_eq!(values.as_slice()?, &[1.0, 3.0]);
1527    /// assert_eq!(vectors.shape(), &[2, 2]);
1528    /// # Ok::<(), tenferro_tensor::Error>(())
1529    /// ```
1530    fn eigh(
1531        &self,
1532        session: &mut dyn Engine,
1533    ) -> tenferro_tensor::Result<(TypedTensor<<T as TensorScalar>::Real>, TypedTensor<T>)>;
1534    /// # Errors
1535    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
1536    /// Returns validation errors for metadata/options, plus backend or typed-output errors.
1537    /// # Examples
1538    ///
1539    /// ```rust
1540    /// # use tenferro_cpu::CpuEngine;
1541    /// # use tenferro_linalg::{EighOptions, TypedTensorLinalgExt};
1542    /// # use tenferro_tensor::TypedTensor;
1543    /// # let a = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![1.0, 0.0, 0.0, 3.0])?;
1544    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1545    /// let (values, vectors) = { a.eigh_with_options(EighOptions::default(), &mut host) }?;
1546    /// assert_eq!(values.shape(), &[2]);
1547    /// assert_eq!(vectors.shape(), &[2, 2]);
1548    /// # Ok::<(), tenferro_tensor::Error>(())
1549    /// ```
1550    fn eigh_with_options(
1551        &self,
1552        options: EighOptions,
1553        session: &mut dyn Engine,
1554    ) -> tenferro_tensor::Result<(TypedTensor<<T as TensorScalar>::Real>, TypedTensor<T>)>;
1555    /// # Errors
1556    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
1557    /// Returns validation, backend, convergence, output-contract, or typed-downcast errors.
1558    /// # Examples
1559    ///
1560    /// ```rust
1561    /// # use tenferro_cpu::CpuEngine;
1562    /// # use tenferro_linalg::TypedTensorLinalgExt;
1563    /// # use tenferro_tensor::TypedTensor;
1564    /// # let a = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![1.0, 0.0, 0.0, 2.0])?;
1565    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1566    /// let (values, vectors) = a.eig(&mut host)?;
1567    /// assert_eq!(values.shape(), &[2]);
1568    /// assert_eq!(vectors.shape(), &[2, 2]);
1569    /// # Ok::<(), tenferro_tensor::Error>(())
1570    /// ```
1571    /// Eigenvectors come back with unit 2-norm columns, whichever provider computed them;
1572    /// a column the provider left non-finite is reported as a numerical failure instead
1573    /// of being returned as `NaN` (#2148).
1574    fn eig(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<TypedEig<T>>;
1575    /// # Errors
1576    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
1577    /// Returns incompatible-input/flag validation, backend, singular, or typed-output errors.
1578    #[allow(clippy::too_many_arguments)]
1579    /// # Examples
1580    ///
1581    /// ```rust
1582    /// # use tenferro_cpu::CpuEngine;
1583    /// # use tenferro_linalg::TypedTensorLinalgExt;
1584    /// # use tenferro_tensor::TypedTensor;
1585    /// # let a = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![2.0, 0.0, 1.0, 3.0])?;
1586    /// # let b = TypedTensor::<f64>::from_vec_col_major(vec![2, 1], vec![4.0, 9.0])?;
1587    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1588    /// let x = { a.triangular_solve(&b, true, false, false, false, &mut host) }?;
1589    /// assert_eq!(x.shape(), &[2, 1]);
1590    /// # Ok::<(), tenferro_tensor::Error>(())
1591    /// ```
1592    fn triangular_solve(
1593        &self,
1594        b: &TypedTensor<T>,
1595        left_side: bool,
1596        lower: bool,
1597        transpose_a: bool,
1598        unit_diagonal: bool,
1599        session: &mut dyn Engine,
1600    ) -> tenferro_tensor::Result<TypedTensor<T>>;
1601    /// # Errors
1602    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
1603    /// Returns validation, backend, numerical, output-contract, or typed-downcast errors.
1604    /// # Examples
1605    ///
1606    /// ```rust
1607    /// # use tenferro_cpu::CpuEngine;
1608    /// # use tenferro_linalg::TypedTensorLinalgExt;
1609    /// # use tenferro_tensor::TypedTensor;
1610    /// # let a = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![2.0, 0.0, 0.0, 4.0])?;
1611    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1612    /// let (sign, logabsdet) = a.slogdet(&mut host)?;
1613    /// assert_eq!(sign.as_slice()?, &[1.0]);
1614    /// assert_eq!(logabsdet.shape(), &[] as &[usize]);
1615    /// # Ok::<(), tenferro_tensor::Error>(())
1616    /// ```
1617    fn slogdet(
1618        &self,
1619        session: &mut dyn Engine,
1620    ) -> tenferro_tensor::Result<(TypedTensor<T>, TypedTensor<<T as TensorScalar>::Real>)>;
1621    /// # Errors
1622    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
1623    /// Returns validation, backend, numerical, output-contract, or typed-downcast errors.
1624    /// # Examples
1625    ///
1626    /// ```rust
1627    /// # use tenferro_cpu::CpuEngine;
1628    /// # use tenferro_linalg::TypedTensorLinalgExt;
1629    /// # use tenferro_tensor::TypedTensor;
1630    /// # let a = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![2.0, 0.0, 0.0, 4.0])?;
1631    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1632    /// let determinant = a.det(&mut host)?;
1633    /// assert!((determinant.as_slice()?[0] - 8.0).abs() < 1.0e-12);
1634    /// # Ok::<(), tenferro_tensor::Error>(())
1635    /// ```
1636    fn det(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<TypedTensor<T>>;
1637    /// # Errors
1638    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
1639    /// Returns validation, backend, singular-solve, or typed-downcast errors.
1640    /// # Examples
1641    ///
1642    /// ```rust
1643    /// # use tenferro_cpu::CpuEngine;
1644    /// # use tenferro_linalg::TypedTensorLinalgExt;
1645    /// # use tenferro_tensor::TypedTensor;
1646    /// # let a = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![2.0, 0.0, 0.0, 4.0])?;
1647    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1648    /// let inverse = a.inv(&mut host)?;
1649    /// assert_eq!(inverse.as_slice()?, &[0.5, 0.0, 0.0, 0.25]);
1650    /// # Ok::<(), tenferro_tensor::Error>(())
1651    /// ```
1652    fn inv(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<TypedTensor<T>>;
1653    /// # Errors
1654    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
1655    /// Returns validation, backend, convergence, output-contract, or typed-downcast errors.
1656    /// # Examples
1657    ///
1658    /// ```rust
1659    /// # use tenferro_cpu::CpuEngine;
1660    /// # use tenferro_linalg::TypedTensorLinalgExt;
1661    /// # use tenferro_tensor::TypedTensor;
1662    /// # let a = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![1.0, 0.0, 0.0, 3.0])?;
1663    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1664    /// let values = a.eigvalsh(&mut host)?;
1665    /// assert_eq!(values.as_slice()?, &[1.0, 3.0]);
1666    /// # Ok::<(), tenferro_tensor::Error>(())
1667    /// ```
1668    fn eigvalsh(
1669        &self,
1670        session: &mut dyn Engine,
1671    ) -> tenferro_tensor::Result<TypedTensor<<T as TensorScalar>::Real>>;
1672    /// # Errors
1673    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
1674    /// Returns validation, backend, convergence, output-contract, or typed-downcast errors.
1675    /// # Examples
1676    ///
1677    /// ```rust
1678    /// # use tenferro_cpu::CpuEngine;
1679    /// # use tenferro_linalg::TypedTensorLinalgExt;
1680    /// # use tenferro_tensor::TypedTensor;
1681    /// # let a = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![1.0, 0.0, 0.0, 2.0])?;
1682    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1683    /// let values = a.eigvals(&mut host)?;
1684    /// assert_eq!(values.shape(), &[2]);
1685    /// # Ok::<(), tenferro_tensor::Error>(())
1686    /// ```
1687    fn eigvals(&self, session: &mut dyn Engine)
1688        -> tenferro_tensor::Result<TypedTensor<T::Complex>>;
1689    /// # Errors
1690    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
1691    /// Returns validation, backend, numerical, output-contract, or typed-downcast errors.
1692    /// # Examples
1693    ///
1694    /// ```rust
1695    /// # use tenferro_cpu::CpuEngine;
1696    /// # use tenferro_linalg::TypedTensorLinalgExt;
1697    /// # use tenferro_tensor::TypedTensor;
1698    /// # let a = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![2.0, 0.0, 0.0, 4.0])?;
1699    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1700    /// let pseudoinverse = a.pinv(&mut host)?;
1701    /// assert_eq!(pseudoinverse.shape(), &[2, 2]);
1702    /// # Ok::<(), tenferro_tensor::Error>(())
1703    /// ```
1704    fn pinv(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<TypedTensor<T>>;
1705    /// # Errors
1706    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
1707    /// Returns a validation error for invalid `rtol`, plus backend or typed-output errors.
1708    /// # Examples
1709    ///
1710    /// ```rust
1711    /// # use tenferro_cpu::CpuEngine;
1712    /// # use tenferro_linalg::TypedTensorLinalgExt;
1713    /// # use tenferro_tensor::TypedTensor;
1714    /// # let a = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![2.0, 0.0, 0.0, 4.0])?;
1715    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1716    /// let pseudoinverse = { a.pinv_with_rtol(1.0e-12, &mut host) }?;
1717    /// assert_eq!(pseudoinverse.shape(), &[2, 2]);
1718    /// # Ok::<(), tenferro_tensor::Error>(())
1719    /// ```
1720    fn pinv_with_rtol(
1721        &self,
1722        rtol: f64,
1723        session: &mut dyn Engine,
1724    ) -> tenferro_tensor::Result<TypedTensor<T>>;
1725    /// # Errors
1726    /// Returns `tenferro_tensor::Error::Unsupported` when the selected backend does not support the operation.
1727    /// Returns validation errors for axes/order combinations, backend, or typed-output errors.
1728    /// # Examples
1729    ///
1730    /// ```rust
1731    /// # use tenferro_cpu::CpuEngine;
1732    /// # use tenferro_linalg::TypedTensorLinalgExt;
1733    /// # use tenferro_tensor::TypedTensor;
1734    /// # let a = TypedTensor::<f64>::from_vec_col_major(vec![2, 2], vec![3.0, 0.0, 0.0, 4.0])?;
1735    /// # let mut host = tenferro_cpu::CpuEngine::serial();
1736    /// let frobenius = a.norm(None, None, false, &mut host)?;
1737    /// assert_eq!(frobenius.shape(), &[] as &[usize]);
1738    /// # Ok::<(), tenferro_tensor::Error>(())
1739    /// ```
1740    fn norm(
1741        &self,
1742        ord: Option<f64>,
1743        dim: Option<&[usize]>,
1744        keepdim: bool,
1745        session: &mut dyn Engine,
1746    ) -> tenferro_tensor::Result<TypedTensor<<T as TensorScalar>::Real>>;
1747}
1748
1749impl TensorLinalgExt for Tensor {
1750    fn svd(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor)> {
1751        with_linalg_backend(session, "svd", |backend| three(backend.svd(self)?, "svd"))
1752    }
1753    fn svdvals(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<Tensor> {
1754        with_linalg_backend(session, "svdvals", |backend| backend.svd_values(self))
1755    }
1756
1757    fn svd_with_options(
1758        &self,
1759        options: SvdOptions,
1760        session: &mut dyn Engine,
1761    ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor)> {
1762        with_linalg_backend(session, "svd_with_options", |backend| {
1763            three(backend.svd_with_options(self, options)?, "svd_with_options")
1764        })
1765    }
1766    fn svd_full(
1767        &self,
1768        session: &mut dyn Engine,
1769    ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor)> {
1770        with_linalg_backend(session, "svd_full", |backend| {
1771            three(backend.svd_full(self)?, "svd_full")
1772        })
1773    }
1774    fn qr(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<(Tensor, Tensor)> {
1775        with_linalg_backend(session, "qr", |backend| two(backend.qr(self)?, "qr"))
1776    }
1777    fn householder_qr(
1778        &self,
1779        session: &mut dyn Engine,
1780    ) -> tenferro_tensor::Result<crate::HouseholderQr<Tensor>> {
1781        with_linalg_backend(session, "householder_qr", |backend| {
1782            backend
1783                .householder_qr(self)
1784                .map(crate::HouseholderQr::from_backend)
1785        })
1786    }
1787    fn qr_with_options(
1788        &self,
1789        options: QrOptions,
1790        session: &mut dyn Engine,
1791    ) -> tenferro_tensor::Result<(Tensor, Tensor)> {
1792        with_linalg_backend(session, "qr_with_options", |backend| {
1793            two(backend.qr_with_options(self, options)?, "qr_with_options")
1794        })
1795    }
1796    fn rank_revealing_qr(
1797        &self,
1798        options: RankRevealingQrOptions,
1799        session: &mut dyn Engine,
1800    ) -> tenferro_tensor::Result<RankRevealingQrResult<Tensor>> {
1801        with_linalg_backend(session, "rank_revealing_qr", |backend| {
1802            rank_revealing_qr_result(backend.rank_revealing_qr(self, options)?)
1803        })
1804    }
1805    fn lu(
1806        &self,
1807        session: &mut dyn Engine,
1808    ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor, Tensor)> {
1809        with_linalg_backend(session, "lu", |backend| four(backend.lu(self)?, "lu"))
1810    }
1811    fn full_piv_lu(
1812        &self,
1813        session: &mut dyn Engine,
1814    ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor, Tensor, Tensor)> {
1815        with_linalg_backend(session, "full_piv_lu", |backend| {
1816            five(backend.full_piv_lu(self)?, "full_piv_lu")
1817        })
1818    }
1819    fn full_piv_lu_solve(
1820        &self,
1821        b: &Tensor,
1822        session: &mut dyn Engine,
1823    ) -> tenferro_tensor::Result<Tensor> {
1824        with_linalg_backend(session, "full_piv_lu_solve", |backend| {
1825            backend.full_piv_lu_solve(self, b, false)
1826        })
1827    }
1828    fn solve(&self, b: &Tensor, session: &mut dyn Engine) -> tenferro_tensor::Result<Tensor> {
1829        with_linalg_backend(session, "solve", |backend| backend.solve(self, b))
1830    }
1831    fn cholesky(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<Tensor> {
1832        with_linalg_backend(session, "cholesky", |backend| backend.cholesky(self))
1833    }
1834    fn eigh(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<(Tensor, Tensor)> {
1835        with_linalg_backend(session, "eigh", |backend| two(backend.eigh(self)?, "eigh"))
1836    }
1837    fn eigh_with_options(
1838        &self,
1839        options: EighOptions,
1840        session: &mut dyn Engine,
1841    ) -> tenferro_tensor::Result<(Tensor, Tensor)> {
1842        with_linalg_backend(session, "eigh_with_options", |backend| {
1843            two(
1844                backend.eigh_with_options(self, options)?,
1845                "eigh_with_options",
1846            )
1847        })
1848    }
1849    fn eig(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<(Tensor, Tensor)> {
1850        with_linalg_backend(session, "eig", |backend| two(backend.eig(self)?, "eig"))
1851    }
1852    fn triangular_solve(
1853        &self,
1854        b: &Tensor,
1855        left_side: bool,
1856        lower: bool,
1857        transpose_a: bool,
1858        unit_diagonal: bool,
1859        session: &mut dyn Engine,
1860    ) -> tenferro_tensor::Result<Tensor> {
1861        with_linalg_backend(session, "triangular_solve", |backend| {
1862            backend.triangular_solve(self, b, left_side, lower, transpose_a, unit_diagonal)
1863        })
1864    }
1865    fn slogdet(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<(Tensor, Tensor)> {
1866        with_linalg_backend(session, "slogdet", |backend| {
1867            slogdet_from_lu(four(backend.lu(self)?, "slogdet")?, backend)
1868        })
1869    }
1870    fn det(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<Tensor> {
1871        with_linalg_backend(session, "det", |backend| {
1872            det_impl(self.slogdet(backend)?, backend)
1873        })
1874    }
1875    fn inv(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<Tensor> {
1876        with_linalg_backend(session, "inv", |backend| inv_owned(self, backend))
1877    }
1878    fn eigvalsh(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<Tensor> {
1879        with_linalg_backend(session, "eigvalsh", |backend| backend.eigh_values(self))
1880    }
1881    fn eigvals(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<Tensor> {
1882        with_linalg_backend(session, "eigvals", |backend| backend.eig_values(self))
1883    }
1884    fn pinv(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<Tensor> {
1885        with_linalg_backend(session, "pinv", |backend| pinv_owned(self, None, backend))
1886    }
1887    fn pinv_with_rtol(
1888        &self,
1889        rtol: f64,
1890        session: &mut dyn Engine,
1891    ) -> tenferro_tensor::Result<Tensor> {
1892        with_linalg_backend(session, "pinv_with_rtol", |backend| {
1893            pinv_owned(self, Some(rtol), backend)
1894        })
1895    }
1896    fn norm(
1897        &self,
1898        ord: Option<f64>,
1899        dim: Option<&[usize]>,
1900        keepdim: bool,
1901        session: &mut dyn Engine,
1902    ) -> tenferro_tensor::Result<Tensor> {
1903        with_linalg_backend(session, "norm", |backend| {
1904            norm_from_read(TensorRead::from_tensor(self), ord, dim, keepdim, backend)
1905        })
1906    }
1907}
1908
1909impl TensorReadLinalgExt for TensorRead<'_> {
1910    fn svd_read(
1911        self,
1912        session: &mut dyn Engine,
1913    ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor)> {
1914        with_linalg_backend(session, "svd_read", |backend| {
1915            three(backend.svd_read(self)?, "svd_read")
1916        })
1917    }
1918    fn svdvals_read(self, session: &mut dyn Engine) -> tenferro_tensor::Result<Tensor> {
1919        with_linalg_backend(session, "svdvals_read", |backend| {
1920            backend.svd_values_read(self)
1921        })
1922    }
1923
1924    fn svd_with_options_read(
1925        self,
1926        options: SvdOptions,
1927        session: &mut dyn Engine,
1928    ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor)> {
1929        with_linalg_backend(session, "svd_with_options_read", |backend| {
1930            three(
1931                backend.svd_with_options_read(self, options)?,
1932                "svd_with_options_read",
1933            )
1934        })
1935    }
1936    fn svd_full_read(
1937        self,
1938        session: &mut dyn Engine,
1939    ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor)> {
1940        with_linalg_backend(session, "svd_full_read", |backend| {
1941            three(backend.svd_full_read(self)?, "svd_full_read")
1942        })
1943    }
1944    fn qr_read(self, session: &mut dyn Engine) -> tenferro_tensor::Result<(Tensor, Tensor)> {
1945        with_linalg_backend(session, "qr_read", |backend| {
1946            two(backend.qr_read(self)?, "qr_read")
1947        })
1948    }
1949    fn qr_with_options_read(
1950        self,
1951        options: QrOptions,
1952        session: &mut dyn Engine,
1953    ) -> tenferro_tensor::Result<(Tensor, Tensor)> {
1954        with_linalg_backend(session, "qr_with_options_read", |backend| {
1955            two(
1956                backend.qr_with_options_read(self, options)?,
1957                "qr_with_options_read",
1958            )
1959        })
1960    }
1961    fn rank_revealing_qr_read(
1962        self,
1963        options: RankRevealingQrOptions,
1964        session: &mut dyn Engine,
1965    ) -> tenferro_tensor::Result<RankRevealingQrResult<Tensor>> {
1966        with_linalg_backend(session, "rank_revealing_qr_read", |backend| {
1967            rank_revealing_qr_result(backend.rank_revealing_qr_read(self, options)?)
1968        })
1969    }
1970    fn lu_read(
1971        self,
1972        session: &mut dyn Engine,
1973    ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor, Tensor)> {
1974        with_linalg_backend(session, "lu_read", |backend| {
1975            four(backend.lu_read(self)?, "lu_read")
1976        })
1977    }
1978    fn full_piv_lu_read(
1979        self,
1980        session: &mut dyn Engine,
1981    ) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor, Tensor, Tensor)> {
1982        with_linalg_backend(session, "full_piv_lu_read", |backend| {
1983            five(backend.full_piv_lu_read(self)?, "full_piv_lu_read")
1984        })
1985    }
1986    fn full_piv_lu_solve_read(
1987        self,
1988        b: TensorRead<'_>,
1989        session: &mut dyn Engine,
1990    ) -> tenferro_tensor::Result<Tensor> {
1991        with_linalg_backend(session, "full_piv_lu_solve_read", |backend| {
1992            let b_is_vector = b.shape().len() + 1 == self.shape().len();
1993            let original_b_shape = b.shape().to_vec();
1994            let vector_as_matrix = if b_is_vector {
1995                let mut shape = vec![b.shape()[0], 1];
1996                shape.extend_from_slice(&b.shape()[1..]);
1997                Some(backend.reshape_read(b.clone(), &shape)?)
1998            } else {
1999                None
2000            };
2001            let b = vector_as_matrix.as_ref().map_or(b, TensorRead::from_tensor);
2002            let (p, l, u, q, _parity) = self.full_piv_lu_read(backend)?;
2003            let pb = linalg_matmul_read(&p, b, false, backend)?;
2004            let z = backend.triangular_solve_read(
2005                TensorRead::from_tensor(&l),
2006                TensorRead::from_tensor(&pb),
2007                true,
2008                true,
2009                false,
2010                true,
2011            )?;
2012            let w = backend.triangular_solve_read(
2013                TensorRead::from_tensor(&u),
2014                TensorRead::from_tensor(&z),
2015                true,
2016                false,
2017                false,
2018                false,
2019            )?;
2020            let mut perm: Vec<usize> = (0..q.shape().len()).collect();
2021            perm.swap(0, 1);
2022            let qt = transpose(&q, &perm, backend)?;
2023            let solution = linalg_matmul_read(&qt, TensorRead::from_tensor(&w), false, backend)?;
2024            if b_is_vector {
2025                reshape(&solution, &original_b_shape, backend)
2026            } else {
2027                Ok(solution)
2028            }
2029        })
2030    }
2031    fn solve_read(
2032        self,
2033        b: TensorRead<'_>,
2034        session: &mut dyn Engine,
2035    ) -> tenferro_tensor::Result<Tensor> {
2036        with_linalg_backend(session, "solve_read", |backend| backend.solve_read(self, b))
2037    }
2038    fn solve_read_into(
2039        self,
2040        b: TensorRead<'_>,
2041        out: TensorWrite<'_>,
2042        session: &mut dyn Engine,
2043    ) -> tenferro_tensor::Result<()> {
2044        with_linalg_backend(session, "solve_read_into", |backend| {
2045            backend.solve_read_into(self, b, out)
2046        })
2047    }
2048    fn cholesky_read(self, session: &mut dyn Engine) -> tenferro_tensor::Result<Tensor> {
2049        with_linalg_backend(session, "cholesky_read", |backend| {
2050            backend.cholesky_read(self)
2051        })
2052    }
2053    fn eigh_read(self, session: &mut dyn Engine) -> tenferro_tensor::Result<(Tensor, Tensor)> {
2054        with_linalg_backend(session, "eigh_read", |backend| {
2055            two(backend.eigh_read(self)?, "eigh_read")
2056        })
2057    }
2058    fn eigh_with_options_read(
2059        self,
2060        options: EighOptions,
2061        session: &mut dyn Engine,
2062    ) -> tenferro_tensor::Result<(Tensor, Tensor)> {
2063        with_linalg_backend(session, "eigh_with_options_read", |backend| {
2064            two(
2065                backend.eigh_with_options_read(self, options)?,
2066                "eigh_with_options_read",
2067            )
2068        })
2069    }
2070    fn eig_read(self, session: &mut dyn Engine) -> tenferro_tensor::Result<(Tensor, Tensor)> {
2071        with_linalg_backend(session, "eig_read", |backend| {
2072            two(backend.eig_read(self)?, "eig_read")
2073        })
2074    }
2075    fn triangular_solve_read(
2076        self,
2077        b: TensorRead<'_>,
2078        left_side: bool,
2079        lower: bool,
2080        transpose_a: bool,
2081        unit_diagonal: bool,
2082        session: &mut dyn Engine,
2083    ) -> tenferro_tensor::Result<Tensor> {
2084        with_linalg_backend(session, "triangular_solve_read", |backend| {
2085            backend.triangular_solve_read(self, b, left_side, lower, transpose_a, unit_diagonal)
2086        })
2087    }
2088    fn slogdet_read(self, session: &mut dyn Engine) -> tenferro_tensor::Result<(Tensor, Tensor)> {
2089        with_linalg_backend(session, "slogdet_read", |backend| {
2090            slogdet_from_lu(four(backend.lu_read(self)?, "slogdet_read")?, backend)
2091        })
2092    }
2093    fn det_read(self, session: &mut dyn Engine) -> tenferro_tensor::Result<Tensor> {
2094        with_linalg_backend(session, "det_read", |backend| {
2095            det_impl(self.slogdet_read(backend)?, backend)
2096        })
2097    }
2098    fn inv_read(self, session: &mut dyn Engine) -> tenferro_tensor::Result<Tensor> {
2099        with_linalg_backend(session, "inv_read", |backend| inv_read(self, backend))
2100    }
2101    fn eigvalsh_read(self, session: &mut dyn Engine) -> tenferro_tensor::Result<Tensor> {
2102        with_linalg_backend(session, "eigvalsh_read", |backend| {
2103            backend.eigh_values_read(self)
2104        })
2105    }
2106    fn eigvals_read(self, session: &mut dyn Engine) -> tenferro_tensor::Result<Tensor> {
2107        with_linalg_backend(session, "eigvals_read", |backend| {
2108            backend.eig_values_read(self)
2109        })
2110    }
2111    fn pinv_read(self, session: &mut dyn Engine) -> tenferro_tensor::Result<Tensor> {
2112        with_linalg_backend(session, "pinv_read", |backend| {
2113            pinv_read(self, None, backend)
2114        })
2115    }
2116    fn pinv_with_rtol_read(
2117        self,
2118        rtol: f64,
2119        session: &mut dyn Engine,
2120    ) -> tenferro_tensor::Result<Tensor> {
2121        with_linalg_backend(session, "pinv_with_rtol_read", |backend| {
2122            pinv_read(self, Some(rtol), backend)
2123        })
2124    }
2125    fn norm_read(
2126        self,
2127        ord: Option<f64>,
2128        dim: Option<&[usize]>,
2129        keepdim: bool,
2130        session: &mut dyn Engine,
2131    ) -> tenferro_tensor::Result<Tensor> {
2132        with_linalg_backend(session, "norm_read", |backend| {
2133            norm_from_read(self, ord, dim, keepdim, backend)
2134        })
2135    }
2136}
2137
2138impl<T: LinalgScalar> TypedTensorLinalgExt<T> for TypedTensor<T> {
2139    fn svd(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<TypedSvd<T>> {
2140        with_linalg_backend(session, "svd", |backend| {
2141            typed_svd(T::tensor_read(self).svd_read(backend)?)
2142        })
2143    }
2144
2145    fn svdvals(
2146        &self,
2147        session: &mut dyn Engine,
2148    ) -> tenferro_tensor::Result<TypedTensor<<T as TensorScalar>::Real>> {
2149        with_linalg_backend(session, "svdvals", |backend| {
2150            typed_output::<<T as TensorScalar>::Real>(T::tensor_read(self).svdvals_read(backend)?)
2151        })
2152    }
2153
2154    fn svd_with_options(
2155        &self,
2156        options: SvdOptions,
2157        session: &mut dyn Engine,
2158    ) -> tenferro_tensor::Result<TypedSvd<T>> {
2159        with_linalg_backend(session, "svd_with_options", |backend| {
2160            typed_svd(T::tensor_read(self).svd_with_options_read(options, backend)?)
2161        })
2162    }
2163
2164    fn svd_full(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<TypedSvd<T>> {
2165        with_linalg_backend(session, "svd_full", |backend| {
2166            typed_svd(T::tensor_read(self).svd_full_read(backend)?)
2167        })
2168    }
2169
2170    fn qr(
2171        &self,
2172        session: &mut dyn Engine,
2173    ) -> tenferro_tensor::Result<(TypedTensor<T>, TypedTensor<T>)> {
2174        with_linalg_backend(session, "qr", |backend| {
2175            typed_pair_same(T::tensor_read(self).qr_read(backend)?)
2176        })
2177    }
2178
2179    fn qr_with_options(
2180        &self,
2181        options: QrOptions,
2182        session: &mut dyn Engine,
2183    ) -> tenferro_tensor::Result<(TypedTensor<T>, TypedTensor<T>)> {
2184        with_linalg_backend(session, "qr_with_options", |backend| {
2185            typed_pair_same(T::tensor_read(self).qr_with_options_read(options, backend)?)
2186        })
2187    }
2188
2189    fn rank_revealing_qr(
2190        &self,
2191        options: RankRevealingQrOptions,
2192        session: &mut dyn Engine,
2193    ) -> tenferro_tensor::Result<TypedRankRevealingQrResult<T>> {
2194        with_linalg_backend(session, "rank_revealing_qr", |backend| {
2195            let result = T::tensor_read(self).rank_revealing_qr_read(options, backend)?;
2196            Ok(RankRevealingQrResult {
2197                q: typed_output::<T>(result.q)?,
2198                r: typed_output::<T>(result.r)?,
2199                column_permutation: typed_output::<i64>(result.column_permutation)?,
2200                rank: typed_output::<i64>(result.rank)?,
2201            })
2202        })
2203    }
2204
2205    fn lu(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<TypedLu<T>> {
2206        with_linalg_backend(session, "lu", |backend| {
2207            let (p, l, u, parity) = T::tensor_read(self).lu_read(backend)?;
2208            Ok((
2209                typed_output::<T>(p)?,
2210                typed_output::<T>(l)?,
2211                typed_output::<T>(u)?,
2212                typed_output::<<T as TensorScalar>::Real>(parity)?,
2213            ))
2214        })
2215    }
2216
2217    fn full_piv_lu_solve(
2218        &self,
2219        b: &TypedTensor<T>,
2220        session: &mut dyn Engine,
2221    ) -> tenferro_tensor::Result<TypedTensor<T>> {
2222        with_linalg_backend(session, "full_piv_lu_solve", |backend| {
2223            typed_output::<T>(
2224                T::tensor_read(self).full_piv_lu_solve_read(T::tensor_read(b), backend)?,
2225            )
2226        })
2227    }
2228
2229    fn full_piv_lu(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<TypedFullPivLu<T>> {
2230        with_linalg_backend(session, "full_piv_lu", |backend| {
2231            let (p, l, u, q, parity) = T::tensor_read(self).full_piv_lu_read(backend)?;
2232            Ok((
2233                typed_output::<T>(p)?,
2234                typed_output::<T>(l)?,
2235                typed_output::<T>(u)?,
2236                typed_output::<T>(q)?,
2237                typed_output::<<T as TensorScalar>::Real>(parity)?,
2238            ))
2239        })
2240    }
2241
2242    fn solve(
2243        &self,
2244        b: &TypedTensor<T>,
2245        session: &mut dyn Engine,
2246    ) -> tenferro_tensor::Result<TypedTensor<T>> {
2247        with_linalg_backend(session, "solve", |backend| {
2248            typed_output::<T>(T::tensor_read(self).solve_read(T::tensor_read(b), backend)?)
2249        })
2250    }
2251
2252    fn cholesky(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<TypedTensor<T>> {
2253        with_linalg_backend(session, "cholesky", |backend| {
2254            typed_output::<T>(T::tensor_read(self).cholesky_read(backend)?)
2255        })
2256    }
2257
2258    fn eigh(
2259        &self,
2260        session: &mut dyn Engine,
2261    ) -> tenferro_tensor::Result<(TypedTensor<<T as TensorScalar>::Real>, TypedTensor<T>)> {
2262        with_linalg_backend(session, "eigh", |backend| {
2263            typed_eigh(T::tensor_read(self).eigh_read(backend)?)
2264        })
2265    }
2266
2267    fn eigh_with_options(
2268        &self,
2269        options: EighOptions,
2270        session: &mut dyn Engine,
2271    ) -> tenferro_tensor::Result<(TypedTensor<<T as TensorScalar>::Real>, TypedTensor<T>)> {
2272        with_linalg_backend(session, "eigh_with_options", |backend| {
2273            typed_eigh(T::tensor_read(self).eigh_with_options_read(options, backend)?)
2274        })
2275    }
2276
2277    fn eig(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<TypedEig<T>> {
2278        with_linalg_backend(session, "eig", |backend| {
2279            let (values, vectors) = T::tensor_read(self).eig_read(backend)?;
2280            Ok((
2281                typed_output::<T::Complex>(values)?,
2282                typed_output::<T::Complex>(vectors)?,
2283            ))
2284        })
2285    }
2286
2287    fn triangular_solve(
2288        &self,
2289        b: &TypedTensor<T>,
2290        left_side: bool,
2291        lower: bool,
2292        transpose_a: bool,
2293        unit_diagonal: bool,
2294        session: &mut dyn Engine,
2295    ) -> tenferro_tensor::Result<TypedTensor<T>> {
2296        with_linalg_backend(session, "triangular_solve", |backend| {
2297            typed_output::<T>(T::tensor_read(self).triangular_solve_read(
2298                T::tensor_read(b),
2299                left_side,
2300                lower,
2301                transpose_a,
2302                unit_diagonal,
2303                backend,
2304            )?)
2305        })
2306    }
2307
2308    fn slogdet(
2309        &self,
2310        session: &mut dyn Engine,
2311    ) -> tenferro_tensor::Result<(TypedTensor<T>, TypedTensor<<T as TensorScalar>::Real>)> {
2312        with_linalg_backend(session, "slogdet", |backend| {
2313            let (sign, logabsdet) = T::tensor_read(self).slogdet_read(backend)?;
2314            Ok((
2315                typed_output::<T>(sign)?,
2316                typed_output::<<T as TensorScalar>::Real>(logabsdet)?,
2317            ))
2318        })
2319    }
2320
2321    fn det(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<TypedTensor<T>> {
2322        with_linalg_backend(session, "det", |backend| {
2323            typed_output::<T>(T::tensor_read(self).det_read(backend)?)
2324        })
2325    }
2326
2327    fn inv(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<TypedTensor<T>> {
2328        with_linalg_backend(session, "inv", |backend| {
2329            typed_output::<T>(T::tensor_read(self).inv_read(backend)?)
2330        })
2331    }
2332
2333    fn eigvalsh(
2334        &self,
2335        session: &mut dyn Engine,
2336    ) -> tenferro_tensor::Result<TypedTensor<<T as TensorScalar>::Real>> {
2337        with_linalg_backend(session, "eigvalsh", |backend| {
2338            typed_output::<<T as TensorScalar>::Real>(T::tensor_read(self).eigvalsh_read(backend)?)
2339        })
2340    }
2341
2342    fn eigvals(
2343        &self,
2344        session: &mut dyn Engine,
2345    ) -> tenferro_tensor::Result<TypedTensor<T::Complex>> {
2346        with_linalg_backend(session, "eigvals", |backend| {
2347            typed_output::<T::Complex>(T::tensor_read(self).eigvals_read(backend)?)
2348        })
2349    }
2350
2351    fn pinv(&self, session: &mut dyn Engine) -> tenferro_tensor::Result<TypedTensor<T>> {
2352        with_linalg_backend(session, "pinv", |backend| {
2353            typed_output::<T>(T::tensor_read(self).pinv_read(backend)?)
2354        })
2355    }
2356
2357    fn pinv_with_rtol(
2358        &self,
2359        rtol: f64,
2360        session: &mut dyn Engine,
2361    ) -> tenferro_tensor::Result<TypedTensor<T>> {
2362        with_linalg_backend(session, "pinv_with_rtol", |backend| {
2363            typed_output::<T>(T::tensor_read(self).pinv_with_rtol_read(rtol, backend)?)
2364        })
2365    }
2366
2367    fn norm(
2368        &self,
2369        ord: Option<f64>,
2370        dim: Option<&[usize]>,
2371        keepdim: bool,
2372        session: &mut dyn Engine,
2373    ) -> tenferro_tensor::Result<TypedTensor<<T as TensorScalar>::Real>> {
2374        with_linalg_backend(session, "norm", |backend| {
2375            typed_output::<<T as TensorScalar>::Real>(
2376                T::tensor_read(self).norm_read(ord, dim, keepdim, backend)?,
2377            )
2378        })
2379    }
2380}
2381
2382fn rank_revealing_qr_result(
2383    outputs: Vec<Tensor>,
2384) -> tenferro_tensor::Result<RankRevealingQrResult<Tensor>> {
2385    let (q, r, column_permutation, rank) = four(outputs, "rank_revealing_qr")?;
2386    Ok(RankRevealingQrResult {
2387        q,
2388        r,
2389        column_permutation,
2390        rank,
2391    })
2392}
2393
2394fn typed_svd<T: LinalgScalar>(
2395    (u, s, vt): (Tensor, Tensor, Tensor),
2396) -> tenferro_tensor::Result<TypedSvd<T>> {
2397    Ok((
2398        typed_output::<T>(u)?,
2399        typed_output::<<T as TensorScalar>::Real>(s)?,
2400        typed_output::<T>(vt)?,
2401    ))
2402}
2403
2404fn typed_pair_same<T: LinalgScalar>(
2405    (a, b): (Tensor, Tensor),
2406) -> tenferro_tensor::Result<(TypedTensor<T>, TypedTensor<T>)> {
2407    Ok((typed_output::<T>(a)?, typed_output::<T>(b)?))
2408}
2409
2410fn typed_eigh<T: LinalgScalar>(
2411    (values, vectors): (Tensor, Tensor),
2412) -> tenferro_tensor::Result<(TypedTensor<<T as TensorScalar>::Real>, TypedTensor<T>)> {
2413    Ok((
2414        typed_output::<<T as TensorScalar>::Real>(values)?,
2415        typed_output::<T>(vectors)?,
2416    ))
2417}
2418
2419/// Run a concrete linalg body against the built-in linalg execution sessions
2420/// (CPU/CUDA) carried by `session`, returning a typed capability error when
2421/// the session does not expose a linalg execution capability.
2422///
2423/// This is the built-in dispatch shared by the concrete linalg surface;
2424/// callers never downcast themselves (issue #1680 Phase 3). Third-party
2425/// [`LinalgBackend`] implementations remain supported through the SPI trait,
2426/// but the concrete op path is built-in-session only. The composite bodies
2427/// run on the borrowed `&mut dyn LinalgBackend` exactly as before.
2428pub(crate) fn with_linalg_backend<X>(
2429    session: &mut dyn Engine,
2430    op: &'static str,
2431    f: impl FnOnce(&mut dyn LinalgBackend) -> tenferro_tensor::Result<X>,
2432) -> tenferro_tensor::Result<X> {
2433    // The capability branches are mutually exclusive, so `f` runs exactly
2434    // once. Probe the marker first, then re-extract the same exec session and
2435    // run the composite body on it (FnOnce cannot be captured by several
2436    // branch closures).
2437    if with_cpu_engine(session, |_| ()).is_some() {
2438        return with_cpu_engine(session, |exec| f(exec as &mut dyn LinalgBackend))
2439            .expect("marker probe matched a CPU execution session");
2440    }
2441    #[cfg(feature = "cuda")]
2442    if with_cuda_engine(session, |_| ()).is_some() {
2443        return with_cuda_engine(session, |exec| f(exec as &mut dyn LinalgBackend))
2444            .expect("marker probe matched a CUDA execution session");
2445    }
2446    Err(tenferro_tensor::Error::unsupported(
2447        op,
2448        "selected backend session does not expose a linalg execution capability",
2449    ))
2450}
2451
2452fn typed_output<T: TensorScalar>(tensor: Tensor) -> tenferro_tensor::Result<TypedTensor<T>> {
2453    if tensor.dtype() != T::dtype() {
2454        return Err(tenferro_tensor::Error::Internal(format!(
2455            "typed linalg backend contract expected {:?}, got {:?}",
2456            T::dtype(),
2457            tensor.dtype()
2458        )));
2459    }
2460    match T::into_typed(tensor) {
2461        Ok(typed) => Ok(typed),
2462        // INVARIANT: the dtype guard above already accepted this tensor, so the
2463        // refusal arm is unreachable for a matching preset scalar.
2464        Err(failure) => Err(tenferro_tensor::Error::Internal(format!(
2465            "typed linalg backend contract lost its dtype guard: {}",
2466            failure.error()
2467        ))),
2468    }
2469}
2470
2471fn arity(name: &'static str, expected: usize, actual: usize) -> tenferro_tensor::Error {
2472    tenferro_tensor::Error::Internal(format!(
2473        "{name} backend contract expected {expected} outputs, got {actual}"
2474    ))
2475}
2476fn two(mut out: Vec<Tensor>, name: &'static str) -> tenferro_tensor::Result<(Tensor, Tensor)> {
2477    if out.len() != 2 {
2478        return Err(arity(name, 2, out.len()));
2479    }
2480    let b = out.pop().unwrap();
2481    let a = out.pop().unwrap();
2482    Ok((a, b))
2483}
2484fn three(
2485    mut out: Vec<Tensor>,
2486    name: &'static str,
2487) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor)> {
2488    if out.len() != 3 {
2489        return Err(arity(name, 3, out.len()));
2490    }
2491    let c = out.pop().unwrap();
2492    let b = out.pop().unwrap();
2493    let a = out.pop().unwrap();
2494    Ok((a, b, c))
2495}
2496fn four(
2497    mut out: Vec<Tensor>,
2498    name: &'static str,
2499) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor, Tensor)> {
2500    if out.len() != 4 {
2501        return Err(arity(name, 4, out.len()));
2502    }
2503    let d = out.pop().unwrap();
2504    let c = out.pop().unwrap();
2505    let b = out.pop().unwrap();
2506    let a = out.pop().unwrap();
2507    Ok((a, b, c, d))
2508}
2509fn five(
2510    mut out: Vec<Tensor>,
2511    name: &'static str,
2512) -> tenferro_tensor::Result<(Tensor, Tensor, Tensor, Tensor, Tensor)> {
2513    if out.len() != 5 {
2514        return Err(arity(name, 5, out.len()));
2515    }
2516    let e = out.pop().unwrap();
2517    let d = out.pop().unwrap();
2518    let c = out.pop().unwrap();
2519    let b = out.pop().unwrap();
2520    let a = out.pop().unwrap();
2521    Ok((a, b, c, d, e))
2522}
2523
2524// Composite implementations are kept below the surface adapters so every
2525// backend-visible operation remains explicit and testable.
2526fn slogdet_from_lu<B: LinalgBackend + ?Sized>(
2527    (_p, _l, u, parity): (Tensor, Tensor, Tensor, Tensor),
2528    backend: &mut B,
2529) -> tenferro_tensor::Result<(Tensor, Tensor)> {
2530    let diag = backend.extract_diagonal(&u, 0, 1)?;
2531    let sign = backend.sign_read(TensorRead::from_tensor(&diag))?;
2532    let sign_u = backend.reduce_prod_read(TensorRead::from_tensor(&sign), &[0])?;
2533    let sign = backend.mul_read(
2534        TensorRead::from_tensor(&parity),
2535        TensorRead::from_tensor(&sign_u),
2536    )?;
2537    let abs = backend.abs_read(TensorRead::from_tensor(&diag))?;
2538    let log = backend.log_read(TensorRead::from_tensor(&abs))?;
2539    let logabsdet = backend.reduce_sum_read(TensorRead::from_tensor(&log), &[0])?;
2540    Ok((sign, logabsdet))
2541}
2542fn det_impl<B: LinalgBackend + ?Sized>(
2543    (sign, logabsdet): (Tensor, Tensor),
2544    backend: &mut B,
2545) -> tenferro_tensor::Result<Tensor> {
2546    let magnitude = backend.exp_read(TensorRead::from_tensor(&logabsdet))?;
2547    backend.mul_read(
2548        TensorRead::from_tensor(&sign),
2549        TensorRead::from_tensor(&magnitude),
2550    )
2551}
2552fn inv_owned<B: LinalgBackend + ?Sized>(
2553    a: &Tensor,
2554    backend: &mut B,
2555) -> tenferro_tensor::Result<Tensor> {
2556    let eye = eye_like(a.dtype(), a.shape(), backend)?;
2557    backend.solve(a, &eye)
2558}
2559fn inv_read<B: LinalgBackend + ?Sized>(
2560    a: TensorRead<'_>,
2561    backend: &mut B,
2562) -> tenferro_tensor::Result<Tensor> {
2563    let eye = eye_like(a.dtype(), a.shape(), backend)?;
2564    backend.solve_read(a, TensorRead::from_tensor(&eye))
2565}
2566fn pinv_owned<B: LinalgBackend + ?Sized>(
2567    a: &Tensor,
2568    rtol: Option<f64>,
2569    backend: &mut B,
2570) -> tenferro_tensor::Result<Tensor> {
2571    ensure_float_or_complex("pinv", a.dtype())?;
2572    let outputs = three(backend.svd(a)?, "pinv")?;
2573    pinv_from_svd(
2574        outputs,
2575        rtol.unwrap_or_else(|| default_pinv_rtol(a.dtype(), a.shape())),
2576        backend,
2577    )
2578}
2579fn pinv_read<B: LinalgBackend + ?Sized>(
2580    a: TensorRead<'_>,
2581    rtol: Option<f64>,
2582    backend: &mut B,
2583) -> tenferro_tensor::Result<Tensor> {
2584    ensure_float_or_complex("pinv", a.dtype())?;
2585    let default_rtol = default_pinv_rtol(a.dtype(), a.shape());
2586    let outputs = three(backend.svd_read(a)?, "pinv_read")?;
2587    pinv_from_svd(outputs, rtol.unwrap_or(default_rtol), backend)
2588}
2589fn norm_from_read<B: LinalgBackend + ?Sized>(
2590    input: TensorRead<'_>,
2591    ord: Option<f64>,
2592    dim: Option<&[usize]>,
2593    keepdim: bool,
2594    backend: &mut B,
2595) -> tenferro_tensor::Result<Tensor> {
2596    ensure_float_or_complex("norm", input.dtype())?;
2597    let original_shape = input.shape().to_vec();
2598    let axes = dim.map_or_else(
2599        || (0..original_shape.len()).collect::<Vec<_>>(),
2600        <[usize]>::to_vec,
2601    );
2602    validate_axes("norm", original_shape.len(), &axes)?;
2603    if axes.is_empty() {
2604        return backend.to_contiguous_read(input);
2605    }
2606    let reduced = if ord.is_none() && axes.len() == original_shape.len() {
2607        // Full Frobenius reduction: one fused `|x|^2` pass with no `abs`
2608        // temporary and no leading-axis layout copy. Every backend that owns
2609        // the norm surface implements the fused primitive.
2610        let squared = backend.norm_squared_read(input.clone())?;
2611        backend.sqrt_read(TensorRead::from_tensor(&squared))?
2612    } else if can_square_without_abs(input.dtype(), axes.len(), ord) {
2613        frobenius_norm_read(input.clone(), &axes, backend)?
2614    } else if axes.len() == 2 {
2615        matrix_norm(input, &axes, ord, backend)?
2616    } else {
2617        let abs = backend.abs_read(input)?;
2618        norm_over_axes(&abs, &axes, ord, backend)?
2619    };
2620    if !keepdim {
2621        return Ok(reduced);
2622    }
2623    let mut shape = original_shape;
2624    for &axis in &axes {
2625        shape[axis] = 1;
2626    }
2627    reshape(&reduced, &shape, backend)
2628}
2629
2630fn scalar_real(dtype: DType, value: f64) -> tenferro_tensor::Result<Tensor> {
2631    match dtype {
2632        DType::F32 => Tensor::from_vec_col_major(vec![], vec![value as f32]),
2633        DType::F64 => Tensor::from_vec_col_major(vec![], vec![value]),
2634        DType::C32 => Tensor::from_vec_col_major(vec![], vec![Complex32::new(value as f32, 0.0)]),
2635        DType::C64 => Tensor::from_vec_col_major(vec![], vec![Complex64::new(value, 0.0)]),
2636        _ => Err(crate::error::unsupported_dtype("linalg_scalar", dtype)),
2637    }
2638}
2639
2640/// Make a freshly built host tensor resident on `backend`.
2641///
2642/// The composite helpers below build their internal constants on the host - a
2643/// ones scalar, a pseudoinverse threshold, a norm order - and a device backend
2644/// rejects a host tensor at the first operation that reads it; the caller has no
2645/// way to supply a replacement (#2147). This is the concrete-session counterpart
2646/// of the eager composites' `constant_from_host`: on a CPU backend it is a host
2647/// copy, and every constant here is rank-0 or a broadcast of one, so the copy is
2648/// a scalar.
2649fn resident<B: LinalgBackend + ?Sized>(
2650    tensor: Tensor,
2651    backend: &mut B,
2652) -> tenferro_tensor::Result<Tensor> {
2653    backend.upload_host_tensor(TensorRead::from_tensor(&tensor))
2654}
2655
2656fn broadcast<B: LinalgBackend + ?Sized>(
2657    input: &Tensor,
2658    shape: &[usize],
2659    dims: &[usize],
2660    backend: &mut B,
2661) -> tenferro_tensor::Result<Tensor> {
2662    if input.shape() == shape {
2663        return input.duplicate();
2664    }
2665    backend.broadcast_in_dim_read(TensorRead::from_tensor(input), shape, dims)
2666}
2667
2668fn eye_like<B: LinalgBackend + ?Sized>(
2669    dtype: DType,
2670    shape: &[usize],
2671    backend: &mut B,
2672) -> tenferro_tensor::Result<Tensor> {
2673    if shape.len() < 2 {
2674        return Err(tenferro_tensor::Error::rank_mismatch("inv", 2, shape.len()));
2675    }
2676    let mut diagonal_shape = vec![shape[0]];
2677    diagonal_shape.extend_from_slice(&shape[2..]);
2678    let scalar = resident(scalar_real(dtype, 1.0)?, backend)?;
2679    let diagonal = broadcast(&scalar, &diagonal_shape, &[], backend)?;
2680    backend.embed_diagonal(&diagonal, 0, 1)
2681}
2682
2683fn ensure_float_or_complex(op: &'static str, dtype: DType) -> tenferro_tensor::Result<()> {
2684    match dtype {
2685        DType::F32 | DType::F64 | DType::C32 | DType::C64 => Ok(()),
2686        _ => Err(crate::error::unsupported_dtype(op, dtype)),
2687    }
2688}
2689
2690fn can_square_without_abs(dtype: DType, axes_len: usize, ord: Option<f64>) -> bool {
2691    matches!(dtype, DType::F32 | DType::F64)
2692        && (ord.is_none() || (ord == Some(2.0) && axes_len != 2))
2693}
2694
2695fn validate_axes(op: &'static str, rank: usize, axes: &[usize]) -> tenferro_tensor::Result<()> {
2696    tenferro_tensor::validate::validate_unique_axes(op, "dim", rank, axes)
2697}
2698
2699fn default_pinv_rtol(dtype: DType, shape: &[usize]) -> f64 {
2700    let max_dim = shape
2701        .first()
2702        .copied()
2703        .unwrap_or(0)
2704        .max(shape.get(1).copied().unwrap_or(0));
2705    let eps = match dtype {
2706        DType::F32 | DType::C32 => f32::EPSILON as f64,
2707        DType::F64 | DType::C64 => f64::EPSILON,
2708        _ => 0.0,
2709    };
2710    eps * max_dim as f64
2711}
2712
2713fn pinv_from_svd<B: LinalgBackend + ?Sized>(
2714    (u, s, vt): (Tensor, Tensor, Tensor),
2715    rtol: f64,
2716    backend: &mut B,
2717) -> tenferro_tensor::Result<Tensor> {
2718    let abs_s = backend.abs_read(TensorRead::from_tensor(&s))?;
2719    let s_max = reduce_max(&abs_s, &[0], backend)?;
2720    let threshold_scalar = resident(scalar_real(abs_s.dtype(), rtol.max(0.0))?, backend)?;
2721    let threshold = binary_mul(&s_max, &threshold_scalar, backend)?;
2722    let threshold = broadcast_batch_scalar_to_leading_axis(&threshold, s.shape(), backend)?;
2723    let mask = {
2724        let mask = backend.compare_read(
2725            TensorRead::from_tensor(&abs_s),
2726            TensorRead::from_tensor(&threshold),
2727            &CompareDir::Gt,
2728        )?;
2729        backend.convert(&mask, s.dtype())?
2730    };
2731    let ones = ones_like(&s, backend)?;
2732    let neg_mask = backend.neg_read(TensorRead::from_tensor(&mask))?;
2733    let offset = binary_add(&ones, &neg_mask, backend)?;
2734    let denom = binary_add(&s, &offset, backend)?;
2735    let s_inv = backend.div_read(
2736        TensorRead::from_tensor(&mask),
2737        TensorRead::from_tensor(&denom),
2738    )?;
2739    let v = conjugate_transpose(&vt, backend)?;
2740    let uh = conjugate_transpose(&u, backend)?;
2741    // A complex input has real singular values, so `s_inv` is real while `v` is
2742    // complex, and `scale_matrix_columns` multiplies them through `binary_mul`,
2743    // which requires one dtype. Casting the real factor into the complex type is
2744    // exact (#2049).
2745    let s_inv = if s_inv.dtype() == v.dtype() {
2746        s_inv
2747    } else {
2748        backend.convert(&s_inv, v.dtype())?
2749    };
2750    let vs = scale_matrix_columns(&v, &s_inv, backend)?;
2751    matmul_preserve_trailing_batch(&vs, &uh, backend)
2752}
2753
2754fn ones_like<B: LinalgBackend + ?Sized>(
2755    input: &Tensor,
2756    backend: &mut B,
2757) -> tenferro_tensor::Result<Tensor> {
2758    let one = resident(scalar_real(input.dtype(), 1.0)?, backend)?;
2759    broadcast(&one, input.shape(), &[], backend)
2760}
2761
2762fn binary_add<B: LinalgBackend + ?Sized>(
2763    lhs: &Tensor,
2764    rhs: &Tensor,
2765    backend: &mut B,
2766) -> tenferro_tensor::Result<Tensor> {
2767    backend.add_read(TensorRead::from_tensor(lhs), TensorRead::from_tensor(rhs))
2768}
2769
2770fn binary_mul<B: LinalgBackend + ?Sized>(
2771    lhs: &Tensor,
2772    rhs: &Tensor,
2773    backend: &mut B,
2774) -> tenferro_tensor::Result<Tensor> {
2775    backend.mul_read(TensorRead::from_tensor(lhs), TensorRead::from_tensor(rhs))
2776}
2777
2778fn reduce_max<B: LinalgBackend + ?Sized>(
2779    input: &Tensor,
2780    axes: &[usize],
2781    backend: &mut B,
2782) -> tenferro_tensor::Result<Tensor> {
2783    backend.reduce_max_read(TensorRead::from_tensor(input), axes)
2784}
2785
2786fn reduce_sum<B: LinalgBackend + ?Sized>(
2787    input: &Tensor,
2788    axes: &[usize],
2789    backend: &mut B,
2790) -> tenferro_tensor::Result<Tensor> {
2791    backend.reduce_sum_read(TensorRead::from_tensor(input), axes)
2792}
2793
2794fn reduce_min<B: LinalgBackend + ?Sized>(
2795    input: &Tensor,
2796    axes: &[usize],
2797    backend: &mut B,
2798) -> tenferro_tensor::Result<Tensor> {
2799    backend.reduce_min_read(TensorRead::from_tensor(input), axes)
2800}
2801
2802fn reshape<B: LinalgBackend + ?Sized>(
2803    input: &Tensor,
2804    shape: &[usize],
2805    backend: &mut B,
2806) -> tenferro_tensor::Result<Tensor> {
2807    backend.reshape_read(TensorRead::from_tensor(input), shape)
2808}
2809
2810fn transpose<B: LinalgBackend + ?Sized>(
2811    input: &Tensor,
2812    perm: &[usize],
2813    backend: &mut B,
2814) -> tenferro_tensor::Result<Tensor> {
2815    backend.transpose_read(TensorRead::from_tensor(input), perm)
2816}
2817
2818fn broadcast_batch_scalar_to_leading_axis<B: LinalgBackend + ?Sized>(
2819    input: &Tensor,
2820    shape: &[usize],
2821    backend: &mut B,
2822) -> tenferro_tensor::Result<Tensor> {
2823    let dims: Vec<usize> = (1..shape.len()).collect();
2824    broadcast(input, shape, &dims, backend)
2825}
2826
2827fn conjugate_transpose<B: LinalgBackend + ?Sized>(
2828    input: &Tensor,
2829    backend: &mut B,
2830) -> tenferro_tensor::Result<Tensor> {
2831    let conj = backend.conj_read(TensorRead::from_tensor(input))?;
2832    let mut perm: Vec<usize> = (0..input.shape().len()).collect();
2833    perm.swap(0, 1);
2834    transpose(&conj, &perm, backend)
2835}
2836
2837fn scale_matrix_columns<B: LinalgBackend + ?Sized>(
2838    matrix: &Tensor,
2839    scale: &Tensor,
2840    backend: &mut B,
2841) -> tenferro_tensor::Result<Tensor> {
2842    let mut scale_shape = vec![1, scale.shape()[0]];
2843    scale_shape.extend_from_slice(&matrix.shape()[2..]);
2844    let reshaped = reshape(scale, &scale_shape, backend)?;
2845    let dims: Vec<usize> = (0..matrix.shape().len()).collect();
2846    let expanded = broadcast(&reshaped, matrix.shape(), &dims, backend)?;
2847    binary_mul(matrix, &expanded, backend)
2848}
2849
2850fn matmul_preserve_trailing_batch<B: Engine + ?Sized>(
2851    lhs: &Tensor,
2852    rhs: &Tensor,
2853    session: &mut B,
2854) -> tenferro_tensor::Result<Tensor> {
2855    let batch: Vec<usize> = (2..lhs.shape().len()).collect();
2856    let config = DotGeneralConfig {
2857        lhs_contracting_dims: [1].as_slice().into(),
2858        rhs_contracting_dims: [0].as_slice().into(),
2859        lhs_batch_dims: batch.clone().iter().copied().collect(),
2860        rhs_batch_dims: batch.iter().copied().collect(),
2861    };
2862    session.dot_general_read(
2863        TensorRead::from_tensor(lhs),
2864        TensorRead::from_tensor(rhs),
2865        &config,
2866    )
2867}
2868
2869fn linalg_matmul_read<B: Engine + ?Sized>(
2870    lhs: &Tensor,
2871    rhs: TensorRead<'_>,
2872    rhs_is_vector: bool,
2873    session: &mut B,
2874) -> tenferro_tensor::Result<Tensor> {
2875    let lhs_batch_dims: Vec<usize> = (2..lhs.shape().len()).collect();
2876    let rhs_batch_start = if rhs_is_vector { 1 } else { 2 };
2877    let rhs_batch_dims: Vec<usize> = (rhs_batch_start..rhs.shape().len()).collect();
2878    let config = DotGeneralConfig {
2879        lhs_contracting_dims: [1].as_slice().into(),
2880        rhs_contracting_dims: [0].as_slice().into(),
2881        lhs_batch_dims: lhs_batch_dims.iter().copied().collect(),
2882        rhs_batch_dims: rhs_batch_dims.iter().copied().collect(),
2883    };
2884    session.dot_general_read(TensorRead::from_tensor(lhs), rhs, &config)
2885}
2886
2887fn frobenius_norm<B: LinalgBackend + ?Sized>(
2888    abs: &Tensor,
2889    axes: &[usize],
2890    backend: &mut B,
2891) -> tenferro_tensor::Result<Tensor> {
2892    let sum = backend.reduce_sum_squares_read(TensorRead::from_tensor(abs), axes)?;
2893    backend.sqrt_read(TensorRead::from_tensor(&sum))
2894}
2895
2896fn frobenius_norm_read<B: LinalgBackend + ?Sized>(
2897    input: TensorRead<'_>,
2898    axes: &[usize],
2899    backend: &mut B,
2900) -> tenferro_tensor::Result<Tensor> {
2901    let sum = backend.reduce_sum_squares_read(input, axes)?;
2902    backend.sqrt_read(TensorRead::from_tensor(&sum))
2903}
2904
2905fn p_norm<B: LinalgBackend + ?Sized>(
2906    abs: &Tensor,
2907    axes: &[usize],
2908    p: f64,
2909    backend: &mut B,
2910) -> tenferro_tensor::Result<Tensor> {
2911    if !p.is_finite() || p == 0.0 {
2912        return Err(tenferro_tensor::Error::invalid_argument(
2913            "norm",
2914            "p",
2915            format!("p-norm order must be finite and nonzero, got {p}"),
2916        ));
2917    }
2918    if p == 2.0 {
2919        return frobenius_norm(abs, axes, backend);
2920    }
2921    let power = resident(scalar_real(abs.dtype(), p)?, backend)?;
2922    let powered = backend.pow_read(
2923        TensorRead::from_tensor(abs),
2924        TensorRead::from_tensor(&power),
2925    )?;
2926    let sum = reduce_sum(&powered, axes, backend)?;
2927    let inverse = resident(scalar_real(abs.dtype(), 1.0 / p)?, backend)?;
2928    backend.pow_read(
2929        TensorRead::from_tensor(&sum),
2930        TensorRead::from_tensor(&inverse),
2931    )
2932}
2933
2934fn count_nonzero<B: LinalgBackend + ?Sized>(
2935    abs: &Tensor,
2936    axes: &[usize],
2937    backend: &mut B,
2938) -> tenferro_tensor::Result<Tensor> {
2939    let zero = resident(scalar_real(abs.dtype(), 0.0)?, backend)?;
2940    let zero = broadcast(&zero, abs.shape(), &[], backend)?;
2941    let mask = backend.compare_read(
2942        TensorRead::from_tensor(abs),
2943        TensorRead::from_tensor(&zero),
2944        &CompareDir::Gt,
2945    )?;
2946    let mask = backend.convert(&mask, abs.dtype())?;
2947    reduce_sum(&mask, axes, backend)
2948}
2949
2950fn norm_over_axes<B: LinalgBackend + ?Sized>(
2951    abs: &Tensor,
2952    axes: &[usize],
2953    ord: Option<f64>,
2954    backend: &mut B,
2955) -> tenferro_tensor::Result<Tensor> {
2956    match ord {
2957        None => frobenius_norm(abs, axes, backend),
2958        Some(p) if p == f64::INFINITY => reduce_max(abs, axes, backend),
2959        Some(p) if p == f64::NEG_INFINITY => reduce_min(abs, axes, backend),
2960        Some(0.0) => count_nonzero(abs, axes, backend),
2961        Some(p) => p_norm(abs, axes, p, backend),
2962    }
2963}
2964
2965fn matrix_norm<B: LinalgBackend + ?Sized>(
2966    input: TensorRead<'_>,
2967    axes: &[usize],
2968    ord: Option<f64>,
2969    backend: &mut B,
2970) -> tenferro_tensor::Result<Tensor> {
2971    let matrix = move_axes_to_front(input, axes, backend)?;
2972    if matches!(ord, Some(2.0) | Some(-2.0)) {
2973        let (_, singular_values, _) = three(backend.svd(&matrix)?, "norm")?;
2974        return if ord == Some(2.0) {
2975            reduce_max(&singular_values, &[0], backend)
2976        } else {
2977            reduce_min(&singular_values, &[0], backend)
2978        };
2979    }
2980
2981    let abs = backend.abs_read(TensorRead::from_tensor(&matrix))?;
2982    match ord {
2983        None => frobenius_norm(&abs, &[0, 1], backend),
2984        Some(p) if p == f64::INFINITY => row_sum_norm(&abs, true, backend),
2985        Some(p) if p == f64::NEG_INFINITY => row_sum_norm(&abs, false, backend),
2986        Some(1.0) => col_sum_norm(&abs, true, backend),
2987        Some(-1.0) => col_sum_norm(&abs, false, backend),
2988        Some(0.0) => count_nonzero(&abs, &[0, 1], backend),
2989        Some(p) => p_norm(&abs, &[0, 1], p, backend),
2990    }
2991}
2992
2993fn row_sum_norm<B: LinalgBackend + ?Sized>(
2994    input: &Tensor,
2995    take_max: bool,
2996    backend: &mut B,
2997) -> tenferro_tensor::Result<Tensor> {
2998    let sums = reduce_sum(input, &[1], backend)?;
2999    if take_max {
3000        reduce_max(&sums, &[0], backend)
3001    } else {
3002        reduce_min(&sums, &[0], backend)
3003    }
3004}
3005
3006fn col_sum_norm<B: LinalgBackend + ?Sized>(
3007    input: &Tensor,
3008    take_max: bool,
3009    backend: &mut B,
3010) -> tenferro_tensor::Result<Tensor> {
3011    let sums = reduce_sum(input, &[0], backend)?;
3012    if take_max {
3013        reduce_max(&sums, &[0], backend)
3014    } else {
3015        reduce_min(&sums, &[0], backend)
3016    }
3017}
3018
3019fn move_axes_to_front<B: LinalgBackend + ?Sized>(
3020    input: TensorRead<'_>,
3021    axes: &[usize],
3022    backend: &mut B,
3023) -> tenferro_tensor::Result<Tensor> {
3024    if axes.iter().enumerate().all(|(index, &axis)| index == axis) {
3025        return backend.to_contiguous_read(input);
3026    }
3027    let mut selected = vec![false; input.shape().len()];
3028    for &axis in axes {
3029        selected[axis] = true;
3030    }
3031    let mut perm = axes.to_vec();
3032    perm.extend(
3033        selected
3034            .iter()
3035            .enumerate()
3036            .filter_map(|(axis, selected)| (!selected).then_some(axis)),
3037    );
3038    backend.transpose_read(input, &perm)
3039}