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