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