Skip to main content

tenferro_linalg/
eager_ext.rs

1use std::sync::Arc;
2
3use tenferro_ad::error::{Error, Result};
4use tenferro_ad::extension::{
5    apply_eager_with_targeted_extension_session, EagerExtensionBackendKind, EagerExtensionTarget,
6};
7use tenferro_ad::EagerTensor;
8use tenferro_cpu::CpuBackend;
9#[cfg(feature = "cuda")]
10use tenferro_gpu::cuda::CudaBackend;
11#[cfg(feature = "webgpu")]
12use tenferro_gpu::webgpu::WebGpuBackend;
13use tenferro_runtime::{ErrorPhase, ExtensionModule};
14
15use crate::eager_composites;
16use crate::extension::{
17    extension_module, validate_derivative_eps, EighOptions, LinalgExtensionOp, LinalgOp, QrOptions,
18    SvdOptions,
19};
20use crate::rank_revealing_qr::validate_rank_revealing_qr_options;
21use crate::{RankRevealingQrOptions, RankRevealingQrResult};
22
23/// Linear algebra extension methods for [`EagerTensor`].
24pub trait EagerTensorLinalgExt {
25    /// # Errors
26    ///
27    /// Returns `Error::Validation` for an invalid rank, shape, or dtype,
28    /// `Error::Extension` with an unsupported-operation or unsupported-dtype
29    /// source when the selected backend cannot execute the decomposition, and
30    /// `Error::RuntimeState` when the eager runtime or backend is unavailable.
31    /// # Examples
32    ///
33    /// ```rust
34    /// # use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
35    /// # use tenferro_cpu::CpuBackend;
36    /// # use tenferro_linalg::EagerTensorLinalgExt;
37    /// # let ctx = EagerRuntime::with_cpu_backend(CpuBackend::new())?;
38    /// # let a = EagerTensor::from_tensor_in(
39    /// #     Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 0.0, 4.0]).unwrap(),
40    /// #     ctx,
41    /// # )?;
42    /// let (_u, s, _vt) = a.svd()?;
43    /// assert_eq!(s.shape(), &[2]);
44    /// # Ok::<(), tenferro_ad::Error>(())
45    /// ```
46    fn svd(&self) -> Result<(EagerTensor, EagerTensor, EagerTensor)>;
47    /// # Errors
48    ///
49    /// Returns `Error::Validation` when `derivative_eps` is non-finite or
50    /// non-positive, `Error::Extension` for unsupported dtypes or numerical
51    /// non-convergence, and `Error::Internal` if the extension violates its
52    /// output-count contract.
53    /// # Examples
54    ///
55    /// ```rust
56    /// # use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
57    /// # use tenferro_cpu::CpuBackend;
58    /// # use tenferro_linalg::{EagerTensorLinalgExt, SvdOptions};
59    /// # let ctx = EagerRuntime::with_cpu_backend(CpuBackend::new())?;
60    /// # let a = EagerTensor::from_tensor_in(
61    /// #     Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 0.0, 4.0]).unwrap(),
62    /// #     ctx,
63    /// # )?;
64    /// let (_u, s, _vt) = a.svd_with_options(SvdOptions::default())?;
65    /// assert_eq!(s.shape(), &[2]);
66    /// # Ok::<(), tenferro_ad::Error>(())
67    /// ```
68    fn svd_with_options(
69        &self,
70        options: SvdOptions,
71    ) -> Result<(EagerTensor, EagerTensor, EagerTensor)>;
72    /// Full-matrices SVD returning square `U (m x m)` and `Vh (n x n)`, whose
73    /// trailing `n - rank` rows span the input's right nullspace.
74    ///
75    /// # Errors
76    ///
77    /// Returns `Error::Validation` for an invalid rank, and `Error::Extension`
78    /// with an unsupported-operation source when the active backend does not
79    /// implement full-matrices SVD (the CPU faer provider supports it; the
80    /// LAPACK provider and GPU backends return an unsupported error in this
81    /// slice). Automatic differentiation through the full variant is
82    /// unsupported and surfaces a typed error rather than a silent thin
83    /// fallback.
84    /// # Examples
85    ///
86    /// ```rust
87    /// # #[cfg(feature = "cpu-faer")]
88    /// # {
89    /// # use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
90    /// # use tenferro_cpu::{CpuBackend, CpuBackendKind};
91    /// # use tenferro_linalg::EagerTensorLinalgExt;
92    /// # let faer = CpuBackend::with_threads_and_kind(1, CpuBackendKind::Faer)
93    /// #     .expect("faer CPU backend");
94    /// # let ctx = EagerRuntime::with_cpu_backend(faer)?;
95    /// # let a = EagerTensor::from_tensor_in(
96    /// #     Tensor::from_vec_col_major(vec![1, 2], vec![1.0_f64, 1.0]).unwrap(),
97    /// #     ctx,
98    /// # )?;
99    /// let (u, s, vh) = a.svd_full()?;
100    /// assert_eq!(u.shape(), &[1, 1]);
101    /// assert_eq!(s.shape(), &[1]);
102    /// assert_eq!(vh.shape(), &[2, 2]);
103    /// let singular_values = s.value()?.as_slice::<f64>()?;
104    /// assert!((singular_values[0] - 2.0_f64.sqrt()).abs() < 1e-12);
105    /// # }
106    /// # #[cfg(all(feature = "cpu-blas", not(feature = "cpu-faer")))]
107    /// # {
108    /// # use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
109    /// # use tenferro_cpu::{CpuBackend, CpuBackendKind};
110    /// # use tenferro_linalg::EagerTensorLinalgExt;
111    /// # let blas = CpuBackend::with_threads_and_kind(1, CpuBackendKind::Blas)
112    /// #     .expect("BLAS CPU backend");
113    /// # let ctx = EagerRuntime::with_cpu_backend(blas)?;
114    /// # let a = EagerTensor::from_tensor_in(
115    /// #     Tensor::from_vec_col_major(vec![1, 2], vec![1.0_f64, 1.0]).unwrap(),
116    /// #     ctx,
117    /// # )?;
118    /// # let error = match a.svd_full() {
119    /// #     Ok(_) => panic!("expected full-matrices SVD to be unsupported for BLAS"),
120    /// #     Err(error) => error,
121    /// # };
122    /// # assert!(error.to_string().contains("full-matrices SVD"));
123    /// # }
124    /// # Ok::<(), tenferro_ad::Error>(())
125    /// ```
126    fn svd_full(&self) -> Result<(EagerTensor, EagerTensor, EagerTensor)>;
127    /// # Errors
128    ///
129    /// Returns `Error::Validation` for invalid matrix rank or shape,
130    /// `Error::Extension` for unsupported dtypes or numerical failure, and
131    /// `Error::RuntimeState` when the eager runtime or backend is unavailable.
132    /// # Examples
133    ///
134    /// ```rust
135    /// # use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
136    /// # use tenferro_cpu::CpuBackend;
137    /// # use tenferro_linalg::EagerTensorLinalgExt;
138    /// # let ctx = EagerRuntime::with_cpu_backend(CpuBackend::new())?;
139    /// # let a = EagerTensor::from_tensor_in(
140    /// #     Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 1.0]).unwrap(),
141    /// #     ctx,
142    /// # )?;
143    /// let (q, r) = a.qr()?;
144    /// assert_eq!(q.shape(), &[2, 2]);
145    /// assert_eq!(r.shape(), &[2, 2]);
146    /// # Ok::<(), tenferro_ad::Error>(())
147    /// ```
148    fn qr(&self) -> Result<(EagerTensor, EagerTensor)>;
149
150    /// Initialize opaque compact Householder QR state.
151    ///
152    /// # Errors
153    ///
154    /// Returns `Error::Validation` for known invalid metadata,
155    /// `Error::Extension` for unsupported or provider failures, or
156    /// `Error::RuntimeState` when eager execution is unavailable.
157    ///
158    /// # Examples
159    ///
160    /// ```rust
161    /// # use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
162    /// # use tenferro_cpu::CpuBackend;
163    /// # use tenferro_linalg::EagerTensorLinalgExt;
164    /// # let runtime = EagerRuntime::with_cpu_backend(CpuBackend::new())?;
165    /// # let a = EagerTensor::from_tensor_in(
166    /// #     Tensor::from_vec_col_major(vec![2, 1], vec![1.0_f64, 2.0])?, runtime)?;
167    /// let qr = a.householder_qr()?;
168    /// assert!(format!("{qr:?}").starts_with("HouseholderQr"));
169    /// # Ok::<(), tenferro_ad::Error>(())
170    /// ```
171    fn householder_qr(&self) -> Result<crate::HouseholderQr<EagerTensor>>;
172
173    /// # Errors
174    ///
175    /// Returns `Error::Validation` for invalid matrix rank or shape,
176    /// `Error::Extension` for unsupported dtypes or numerical failure, and
177    /// `Error::Internal` if the extension violates its output-count contract.
178    /// # Examples
179    ///
180    /// ```rust
181    /// # use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
182    /// # use tenferro_cpu::CpuBackend;
183    /// # use tenferro_linalg::{EagerTensorLinalgExt, QrOptions};
184    /// # let ctx = EagerRuntime::with_cpu_backend(CpuBackend::new())?;
185    /// # let a = EagerTensor::from_tensor_in(
186    /// #     Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 1.0]).unwrap(),
187    /// #     ctx,
188    /// # )?;
189    /// let (q, r) = a.qr_with_options(QrOptions::default())?;
190    /// assert_eq!(q.shape(), &[2, 2]);
191    /// assert_eq!(r.shape(), &[2, 2]);
192    /// # Ok::<(), tenferro_ad::Error>(())
193    /// ```
194    fn qr_with_options(&self, options: QrOptions) -> Result<(EagerTensor, EagerTensor)>;
195    /// Compute column-pivoted rank-revealing QR with four tensor outputs.
196    ///
197    /// # Errors
198    /// Returns validation errors for rank, dtype, or invalid tolerances;
199    /// numerical failure for non-finite values; and explicit unsupported,
200    /// backend, runtime, or output-contract errors.
201    ///
202    /// # Examples
203    ///
204    /// ```rust
205    /// # use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
206    /// # use tenferro_cpu::CpuBackend;
207    /// # use tenferro_linalg::{EagerTensorLinalgExt, RankRevealingQrOptions};
208    /// # let runtime = EagerRuntime::with_cpu_backend(CpuBackend::new())?;
209    /// # let a = EagerTensor::from_tensor_in(
210    /// #     Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 2.0])?, runtime)?;
211    /// let result = a.rank_revealing_qr(RankRevealingQrOptions::default())?;
212    /// assert_eq!(result.column_permutation.shape(), &[2]);
213    /// assert_eq!(result.rank.value()?.as_slice::<i64>()?, &[2]);
214    /// # Ok::<(), tenferro_ad::Error>(())
215    /// ```
216    fn rank_revealing_qr(
217        &self,
218        options: RankRevealingQrOptions,
219    ) -> Result<RankRevealingQrResult<EagerTensor>>;
220    /// # Errors
221    ///
222    /// Returns `Error::Validation` for an invalid matrix rank or shape,
223    /// `Error::Extension` for an unsupported dtype or singular numerical
224    /// result, and `Error::RuntimeState` when execution cannot access its
225    /// backend.
226    /// # Examples
227    ///
228    /// ```rust
229    /// # use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
230    /// # use tenferro_cpu::CpuBackend;
231    /// # use tenferro_linalg::EagerTensorLinalgExt;
232    /// # let ctx = EagerRuntime::with_cpu_backend(CpuBackend::new())?;
233    /// # let a = EagerTensor::from_tensor_in(
234    /// #     Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 3.0, 2.0, 4.0]).unwrap(),
235    /// #     ctx,
236    /// # )?;
237    /// let (_p, l, u, parity) = a.lu()?;
238    /// assert_eq!(l.shape(), &[2, 2]);
239    /// assert_eq!(u.shape(), &[2, 2]);
240    /// assert_eq!(parity.shape(), &[]);
241    /// # Ok::<(), tenferro_ad::Error>(())
242    /// ```
243    fn lu(&self) -> Result<(EagerTensor, EagerTensor, EagerTensor, EagerTensor)>;
244    /// # Errors
245    ///
246    /// Returns `Error::Validation` for an invalid matrix rank or shape,
247    /// `Error::Extension` for unsupported dtypes or singular numerical
248    /// results, and `Error::Internal` if the extension violates its output
249    /// contract.
250    /// # Examples
251    ///
252    /// ```rust
253    /// # use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
254    /// # use tenferro_cpu::CpuBackend;
255    /// # use tenferro_linalg::EagerTensorLinalgExt;
256    /// # let ctx = EagerRuntime::with_cpu_backend(CpuBackend::new())?;
257    /// # let a = EagerTensor::from_tensor_in(
258    /// #     Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 3.0, 2.0, 4.0]).unwrap(),
259    /// #     ctx,
260    /// # )?;
261    /// let (p, _l, _u, q, parity) = a.full_piv_lu()?;
262    /// assert_eq!(p.shape(), &[2, 2]);
263    /// assert_eq!(q.shape(), &[2, 2]);
264    /// assert_eq!(parity.shape(), &[]);
265    /// # Ok::<(), tenferro_ad::Error>(())
266    /// ```
267    fn full_piv_lu(
268        &self,
269    ) -> Result<(
270        EagerTensor,
271        EagerTensor,
272        EagerTensor,
273        EagerTensor,
274        EagerTensor,
275    )>;
276    /// # Errors
277    ///
278    /// Returns `Error::Validation` when `a` and `b` have incompatible matrix
279    /// or batch shapes, `Error::Extension` for an unsupported dtype or
280    /// singular system, and `Error::RuntimeState` when the backend is
281    /// unavailable.
282    /// # Examples
283    ///
284    /// ```rust
285    /// # use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
286    /// # use tenferro_cpu::CpuBackend;
287    /// # use tenferro_linalg::EagerTensorLinalgExt;
288    /// # let ctx = EagerRuntime::with_cpu_backend(CpuBackend::new())?;
289    /// # let a = EagerTensor::from_tensor_in(
290    /// #     Tensor::from_vec_col_major(vec![2, 2], vec![0.0_f64, 2.0, 1.0, 3.0]).unwrap(),
291    /// #     ctx.clone(),
292    /// # )?;
293    /// # let b = EagerTensor::from_tensor_in(
294    /// #     Tensor::from_vec_col_major(vec![2, 1], vec![-1.0_f64, 5.0]).unwrap(),
295    /// #     ctx,
296    /// # )?;
297    /// let x = a.full_piv_lu_solve(&b)?;
298    /// assert_eq!(x.shape(), &[2, 1]);
299    /// # Ok::<(), tenferro_ad::Error>(())
300    /// ```
301    fn full_piv_lu_solve(&self, b: &EagerTensor) -> Result<EagerTensor>;
302    /// # Errors
303    ///
304    /// Returns `Error::Validation` for incompatible matrix, batch, or dtype
305    /// metadata, `Error::Extension` for an unsupported dtype or singular
306    /// system, and `Error::RuntimeState` when the backend is unavailable.
307    /// # Examples
308    ///
309    /// ```rust
310    /// # use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
311    /// # use tenferro_cpu::CpuBackend;
312    /// # use tenferro_linalg::EagerTensorLinalgExt;
313    /// # let ctx = EagerRuntime::with_cpu_backend(CpuBackend::new())?;
314    /// # let a = EagerTensor::from_tensor_in(
315    /// #     Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 0.0, 4.0]).unwrap(),
316    /// #     ctx.clone(),
317    /// # )?;
318    /// # let b = EagerTensor::from_tensor_in(
319    /// #     Tensor::from_vec_col_major(vec![2, 1], vec![4.0_f64, 8.0]).unwrap(),
320    /// #     ctx,
321    /// # )?;
322    /// let x = a.solve(&b)?;
323    /// assert_eq!(x.value()?.as_slice::<f64>()?, &[2.0, 2.0]);
324    /// # Ok::<(), tenferro_ad::Error>(())
325    /// ```
326    fn solve(&self, b: &EagerTensor) -> Result<EagerTensor>;
327    /// Least-squares solve `argmin_x ||A x - b||_2` for a tall or square,
328    /// full-column-rank `A`, via the thin QR factorization.
329    ///
330    /// # Errors
331    ///
332    /// Returns `Error::Validation` when `A` or `b` is not a matrix (rank
333    /// `>= 2`), when `A` is wide (`rows < cols`; underdetermined), or when the
334    /// dtype is not floating-point or complex; `Error::Extension` for backend
335    /// QR or triangular-solve failures; and `Error::RuntimeState` when the
336    /// backend is unavailable. Rank-deficient `A` is not detected and yields an
337    /// ill-defined result.
338    /// # Examples
339    ///
340    /// ```rust
341    /// # use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
342    /// # use tenferro_cpu::CpuBackend;
343    /// # use tenferro_linalg::EagerTensorLinalgExt;
344    /// # let ctx = EagerRuntime::with_cpu_backend(CpuBackend::new())?;
345    /// # let a = EagerTensor::from_tensor_in(
346    /// #     Tensor::from_vec_col_major(
347    /// #         vec![3, 2],
348    /// #         vec![1.0_f64, 0.0, 1.0, 0.0, 1.0, 1.0],
349    /// #     )
350    /// #     .unwrap(),
351    /// #     ctx.clone(),
352    /// # )?;
353    /// # let b = EagerTensor::from_tensor_in(
354    /// #     Tensor::from_vec_col_major(vec![3, 1], vec![1.0_f64, 2.0, 3.0]).unwrap(),
355    /// #     ctx,
356    /// # )?;
357    /// let x = a.lstsq(&b)?;
358    /// assert_eq!(x.shape(), &[2, 1]);
359    /// let values = x.value()?.as_slice::<f64>()?;
360    /// assert!((values[0] - 1.0_f64).abs() < 1e-12);
361    /// assert!((values[1] - 2.0_f64).abs() < 1e-12);
362    /// # Ok::<(), tenferro_ad::Error>(())
363    /// ```
364    fn lstsq(&self, b: &EagerTensor) -> Result<EagerTensor>;
365    /// # Errors
366    ///
367    /// Returns `Error::Validation` for a non-square or invalid-rank input,
368    /// `Error::Extension` for unsupported dtypes or a non-positive-definite
369    /// matrix, and `Error::RuntimeState` when the backend is unavailable.
370    /// # Examples
371    ///
372    /// ```rust
373    /// # use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
374    /// # use tenferro_cpu::CpuBackend;
375    /// # use tenferro_linalg::EagerTensorLinalgExt;
376    /// # let ctx = EagerRuntime::with_cpu_backend(CpuBackend::new())?;
377    /// # let a = EagerTensor::from_tensor_in(
378    /// #     Tensor::from_vec_col_major(vec![2, 2], vec![4.0_f64, 2.0, 2.0, 3.0]).unwrap(),
379    /// #     ctx,
380    /// # )?;
381    /// let l = a.cholesky()?;
382    /// assert_eq!(l.shape(), &[2, 2]);
383    /// # Ok::<(), tenferro_ad::Error>(())
384    /// ```
385    fn cholesky(&self) -> Result<EagerTensor>;
386    /// # Errors
387    ///
388    /// Returns `Error::Validation` for a non-square or invalid-rank input,
389    /// `Error::Extension` for unsupported dtypes or numerical non-convergence,
390    /// and `Error::RuntimeState` when the backend is unavailable.
391    /// # Examples
392    ///
393    /// ```rust
394    /// # use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
395    /// # use tenferro_cpu::CpuBackend;
396    /// # use tenferro_linalg::EagerTensorLinalgExt;
397    /// # let ctx = EagerRuntime::with_cpu_backend(CpuBackend::new())?;
398    /// # let a = EagerTensor::from_tensor_in(
399    /// #     Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 3.0]).unwrap(),
400    /// #     ctx,
401    /// # )?;
402    /// let (values, vectors) = a.eigh()?;
403    /// assert_eq!(values.value()?.as_slice::<f64>()?, &[1.0, 3.0]);
404    /// assert_eq!(vectors.shape(), &[2, 2]);
405    /// # Ok::<(), tenferro_ad::Error>(())
406    /// ```
407    fn eigh(&self) -> Result<(EagerTensor, EagerTensor)>;
408    /// # Errors
409    ///
410    /// Returns `Error::Validation` for an invalid rank, shape, or
411    /// `derivative_eps`, `Error::Extension` for unsupported dtypes or
412    /// non-convergence, and `Error::Internal` for an output-count violation.
413    /// # Examples
414    ///
415    /// ```rust
416    /// # use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
417    /// # use tenferro_cpu::CpuBackend;
418    /// # use tenferro_linalg::{EagerTensorLinalgExt, EighOptions};
419    /// # let ctx = EagerRuntime::with_cpu_backend(CpuBackend::new())?;
420    /// # let a = EagerTensor::from_tensor_in(
421    /// #     Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 3.0]).unwrap(),
422    /// #     ctx,
423    /// # )?;
424    /// let (values, vectors) = a.eigh_with_options(EighOptions::default())?;
425    /// assert_eq!(values.shape(), &[2]);
426    /// assert_eq!(vectors.shape(), &[2, 2]);
427    /// # Ok::<(), tenferro_ad::Error>(())
428    /// ```
429    fn eigh_with_options(&self, options: EighOptions) -> Result<(EagerTensor, EagerTensor)>;
430    /// # Errors
431    ///
432    /// Returns `Error::Validation` for a non-square or invalid-rank input,
433    /// `Error::Extension` for unsupported dtypes or numerical non-convergence,
434    /// and `Error::RuntimeState` when the backend is unavailable.
435    /// # Examples
436    ///
437    /// ```rust
438    /// # use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
439    /// # use tenferro_cpu::CpuBackend;
440    /// # use tenferro_linalg::EagerTensorLinalgExt;
441    /// # let ctx = EagerRuntime::with_cpu_backend(CpuBackend::new())?;
442    /// # let a = EagerTensor::from_tensor_in(
443    /// #     Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 2.0]).unwrap(),
444    /// #     ctx,
445    /// # )?;
446    /// let (values, vectors) = a.eig()?;
447    /// assert_eq!(values.shape(), &[2]);
448    /// assert_eq!(vectors.shape(), &[2, 2]);
449    /// # Ok::<(), tenferro_ad::Error>(())
450    /// ```
451    fn eig(&self) -> Result<(EagerTensor, EagerTensor)>;
452    /// # Errors
453    ///
454    /// Returns `Error::Validation` for incompatible matrix, batch, or dtype
455    /// metadata, `Error::Extension` for unsupported dtypes or a singular
456    /// system, and `Error::RuntimeState` when the backend is unavailable.
457    /// # Examples
458    ///
459    /// ```rust
460    /// # use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
461    /// # use tenferro_cpu::CpuBackend;
462    /// # use tenferro_linalg::EagerTensorLinalgExt;
463    /// # let ctx = EagerRuntime::with_cpu_backend(CpuBackend::new())?;
464    /// # let a = EagerTensor::from_tensor_in(
465    /// #     Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 1.0, 3.0]).unwrap(),
466    /// #     ctx.clone(),
467    /// # )?;
468    /// # let b = EagerTensor::from_tensor_in(
469    /// #     Tensor::from_vec_col_major(vec![2, 1], vec![4.0_f64, 9.0]).unwrap(),
470    /// #     ctx,
471    /// # )?;
472    /// let x = a.triangular_solve(&b, true, false, false, false)?;
473    /// assert_eq!(x.shape(), &[2, 1]);
474    /// # Ok::<(), tenferro_ad::Error>(())
475    /// ```
476    fn triangular_solve(
477        &self,
478        b: &EagerTensor,
479        left_side: bool,
480        lower: bool,
481        transpose_a: bool,
482        unit_diagonal: bool,
483    ) -> Result<EagerTensor>;
484    /// Return the determinant sign and logarithm of its absolute value.
485    ///
486    /// # Errors
487    ///
488    /// Returns `Error::Validation` for a non-square input, `Error::Extension`
489    /// for an unsupported dtype or numerical failure, and `Error::Internal`
490    /// if a primitive violates its output contract.
491    /// # Examples
492    ///
493    /// ```rust
494    /// # use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
495    /// # use tenferro_cpu::CpuBackend;
496    /// # use tenferro_linalg::EagerTensorLinalgExt;
497    /// # let ctx = EagerRuntime::with_cpu_backend(CpuBackend::new())?;
498    /// # let a = EagerTensor::from_tensor_in(
499    /// #     Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 0.0, 4.0]).unwrap(),
500    /// #     ctx,
501    /// # )?;
502    /// let (sign, logabsdet) = a.slogdet()?;
503    /// assert_eq!(sign.value()?.as_slice::<f64>()?, &[1.0]);
504    /// assert_eq!(logabsdet.shape(), &[]);
505    /// # Ok::<(), tenferro_ad::Error>(())
506    /// ```
507    fn slogdet(&self) -> Result<(EagerTensor, EagerTensor)>;
508    /// Return the determinant.
509    ///
510    /// # Errors
511    ///
512    /// Returns `Error::Validation`, `Error::Extension`, `Error::RuntimeState`,
513    /// or `Error::Internal` under the conditions documented by [`Self::slogdet`].
514    /// # Examples
515    ///
516    /// ```rust
517    /// # use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
518    /// # use tenferro_cpu::CpuBackend;
519    /// # use tenferro_linalg::EagerTensorLinalgExt;
520    /// # let ctx = EagerRuntime::with_cpu_backend(CpuBackend::new())?;
521    /// # let a = EagerTensor::from_tensor_in(
522    /// #     Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 0.0, 4.0]).unwrap(),
523    /// #     ctx,
524    /// # )?;
525    /// let determinant = a.det()?;
526    /// assert!((determinant.value()?.as_slice::<f64>()?[0] - 8.0).abs() < 1.0e-12);
527    /// # Ok::<(), tenferro_ad::Error>(())
528    /// ```
529    fn det(&self) -> Result<EagerTensor>;
530    /// Return the matrix inverse.
531    ///
532    /// # Errors
533    ///
534    /// Returns `Error::Validation` for invalid matrix metadata,
535    /// `Error::Extension` for an unsupported dtype or singular solve, and
536    /// `Error::RuntimeState` when eager execution cannot access its backend.
537    /// # Examples
538    ///
539    /// ```rust
540    /// # use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
541    /// # use tenferro_cpu::CpuBackend;
542    /// # use tenferro_linalg::EagerTensorLinalgExt;
543    /// # let ctx = EagerRuntime::with_cpu_backend(CpuBackend::new())?;
544    /// # let a = EagerTensor::from_tensor_in(
545    /// #     Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 0.0, 4.0]).unwrap(),
546    /// #     ctx,
547    /// # )?;
548    /// let inverse = a.inv()?;
549    /// assert_eq!(inverse.value()?.as_slice::<f64>()?, &[0.5, 0.0, 0.0, 0.25]);
550    /// # Ok::<(), tenferro_ad::Error>(())
551    /// ```
552    fn inv(&self) -> Result<EagerTensor>;
553    /// Return Hermitian eigenvalues without eigenvectors.
554    ///
555    /// # Errors
556    ///
557    /// Returns the validation, unsupported-dtype, numerical-convergence,
558    /// runtime-state, or output-contract errors reported by [`Self::eigh`].
559    /// # Examples
560    ///
561    /// ```rust
562    /// # use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
563    /// # use tenferro_cpu::CpuBackend;
564    /// # use tenferro_linalg::EagerTensorLinalgExt;
565    /// # let ctx = EagerRuntime::with_cpu_backend(CpuBackend::new())?;
566    /// # let a = EagerTensor::from_tensor_in(
567    /// #     Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 3.0]).unwrap(),
568    /// #     ctx,
569    /// # )?;
570    /// let values = a.eigvalsh()?;
571    /// assert_eq!(values.value()?.as_slice::<f64>()?, &[1.0, 3.0]);
572    /// # Ok::<(), tenferro_ad::Error>(())
573    /// ```
574    fn eigvalsh(&self) -> Result<EagerTensor>;
575    /// Return general eigenvalues without eigenvectors.
576    ///
577    /// # Errors
578    ///
579    /// Returns the validation, unsupported-dtype, numerical-convergence,
580    /// runtime-state, or output-contract errors reported by [`Self::eig`].
581    /// # Examples
582    ///
583    /// ```rust
584    /// # use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
585    /// # use tenferro_cpu::CpuBackend;
586    /// # use tenferro_linalg::EagerTensorLinalgExt;
587    /// # let ctx = EagerRuntime::with_cpu_backend(CpuBackend::new())?;
588    /// # let a = EagerTensor::from_tensor_in(
589    /// #     Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 2.0]).unwrap(),
590    /// #     ctx,
591    /// # )?;
592    /// let values = a.eigvals()?;
593    /// assert_eq!(values.shape(), &[2]);
594    /// # Ok::<(), tenferro_ad::Error>(())
595    /// ```
596    fn eigvals(&self) -> Result<EagerTensor>;
597    /// Return the Moore-Penrose pseudoinverse with the default tolerance.
598    ///
599    /// # Errors
600    ///
601    /// Returns `Error::Validation` for invalid matrix metadata,
602    /// `Error::Extension` for unsupported SVD execution, and
603    /// `Error::Internal` for an unexpected decomposition output contract.
604    /// # Examples
605    ///
606    /// ```rust
607    /// # use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
608    /// # use tenferro_cpu::CpuBackend;
609    /// # use tenferro_linalg::EagerTensorLinalgExt;
610    /// # let ctx = EagerRuntime::with_cpu_backend(CpuBackend::new())?;
611    /// # let a = EagerTensor::from_tensor_in(
612    /// #     Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 0.0, 4.0]).unwrap(),
613    /// #     ctx,
614    /// # )?;
615    /// let pseudoinverse = a.pinv()?;
616    /// assert_eq!(pseudoinverse.shape(), &[2, 2]);
617    /// # Ok::<(), tenferro_ad::Error>(())
618    /// ```
619    fn pinv(&self) -> Result<EagerTensor>;
620    /// Return the Moore-Penrose pseudoinverse with an explicit relative tolerance.
621    ///
622    /// # Errors
623    ///
624    /// Returns `Error::Validation` when `rtol` is negative or non-finite, plus
625    /// the `Error::Extension` and output-contract errors reported by [`Self::pinv`].
626    /// # Examples
627    ///
628    /// ```rust
629    /// # use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
630    /// # use tenferro_cpu::CpuBackend;
631    /// # use tenferro_linalg::EagerTensorLinalgExt;
632    /// # let ctx = EagerRuntime::with_cpu_backend(CpuBackend::new())?;
633    /// # let a = EagerTensor::from_tensor_in(
634    /// #     Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 0.0, 4.0]).unwrap(),
635    /// #     ctx,
636    /// # )?;
637    /// let pseudoinverse = a.pinv_with_rtol(1.0e-12)?;
638    /// assert_eq!(pseudoinverse.shape(), &[2, 2]);
639    /// # Ok::<(), tenferro_ad::Error>(())
640    /// ```
641    fn pinv_with_rtol(&self, rtol: f64) -> Result<EagerTensor>;
642    /// Return a vector, matrix, or tensor norm.
643    ///
644    /// # Errors
645    ///
646    /// Returns `Error::Validation` for an unsupported order/dimension
647    /// combination or invalid axes, `Error::Extension` when a required matrix
648    /// decomposition is unsupported, and eager runtime/backend failures.
649    /// # Examples
650    ///
651    /// ```rust
652    /// # use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
653    /// # use tenferro_cpu::CpuBackend;
654    /// # use tenferro_linalg::EagerTensorLinalgExt;
655    /// # let ctx = EagerRuntime::with_cpu_backend(CpuBackend::new())?;
656    /// # let a = EagerTensor::from_tensor_in(
657    /// #     Tensor::from_vec_col_major(vec![2, 2], vec![3.0_f64, 0.0, 0.0, 4.0]).unwrap(),
658    /// #     ctx,
659    /// # )?;
660    /// let frobenius = a.norm(None, None, false)?;
661    /// assert_eq!(frobenius.shape(), &[]);
662    /// # Ok::<(), tenferro_ad::Error>(())
663    /// ```
664    fn norm(&self, ord: Option<f64>, dim: Option<&[usize]>, keepdim: bool) -> Result<EagerTensor>;
665}
666
667impl EagerTensorLinalgExt for EagerTensor {
668    fn svd(&self) -> Result<(EagerTensor, EagerTensor, EagerTensor)> {
669        svd(self)
670    }
671
672    fn svd_with_options(
673        &self,
674        options: SvdOptions,
675    ) -> Result<(EagerTensor, EagerTensor, EagerTensor)> {
676        svd_with_options(self, options)
677    }
678
679    fn svd_full(&self) -> Result<(EagerTensor, EagerTensor, EagerTensor)> {
680        svd_full(self)
681    }
682
683    fn qr(&self) -> Result<(EagerTensor, EagerTensor)> {
684        qr(self)
685    }
686
687    fn householder_qr(&self) -> Result<crate::HouseholderQr<EagerTensor>> {
688        householder_qr(self)
689    }
690
691    fn qr_with_options(&self, options: QrOptions) -> Result<(EagerTensor, EagerTensor)> {
692        qr_with_options(self, options)
693    }
694
695    fn rank_revealing_qr(
696        &self,
697        options: RankRevealingQrOptions,
698    ) -> Result<RankRevealingQrResult<EagerTensor>> {
699        rank_revealing_qr(self, options)
700    }
701
702    fn lu(&self) -> Result<(EagerTensor, EagerTensor, EagerTensor, EagerTensor)> {
703        lu(self)
704    }
705
706    fn full_piv_lu(
707        &self,
708    ) -> Result<(
709        EagerTensor,
710        EagerTensor,
711        EagerTensor,
712        EagerTensor,
713        EagerTensor,
714    )> {
715        full_piv_lu(self)
716    }
717
718    fn full_piv_lu_solve(&self, b: &EagerTensor) -> Result<EagerTensor> {
719        full_piv_lu_solve(self, b)
720    }
721
722    fn solve(&self, b: &EagerTensor) -> Result<EagerTensor> {
723        solve(self, b)
724    }
725
726    fn lstsq(&self, b: &EagerTensor) -> Result<EagerTensor> {
727        eager_composites::lstsq(self, b)
728    }
729
730    fn cholesky(&self) -> Result<EagerTensor> {
731        cholesky(self)
732    }
733
734    fn eigh(&self) -> Result<(EagerTensor, EagerTensor)> {
735        eigh(self)
736    }
737
738    fn eigh_with_options(&self, options: EighOptions) -> Result<(EagerTensor, EagerTensor)> {
739        eigh_with_options(self, options)
740    }
741
742    fn eig(&self) -> Result<(EagerTensor, EagerTensor)> {
743        eig(self)
744    }
745
746    fn triangular_solve(
747        &self,
748        b: &EagerTensor,
749        left_side: bool,
750        lower: bool,
751        transpose_a: bool,
752        unit_diagonal: bool,
753    ) -> Result<EagerTensor> {
754        triangular_solve(self, b, left_side, lower, transpose_a, unit_diagonal)
755    }
756
757    fn slogdet(&self) -> Result<(EagerTensor, EagerTensor)> {
758        eager_composites::slogdet(self)
759    }
760
761    fn det(&self) -> Result<EagerTensor> {
762        eager_composites::det(self)
763    }
764
765    fn inv(&self) -> Result<EagerTensor> {
766        eager_composites::inv(self)
767    }
768
769    fn eigvalsh(&self) -> Result<EagerTensor> {
770        eager_composites::eigvalsh(self)
771    }
772
773    fn eigvals(&self) -> Result<EagerTensor> {
774        eager_composites::eigvals(self)
775    }
776
777    fn pinv(&self) -> Result<EagerTensor> {
778        eager_composites::pinv(self)
779    }
780
781    fn pinv_with_rtol(&self, rtol: f64) -> Result<EagerTensor> {
782        eager_composites::pinv_with_rtol(self, rtol)
783    }
784
785    fn norm(&self, ord: Option<f64>, dim: Option<&[usize]>, keepdim: bool) -> Result<EagerTensor> {
786        eager_composites::norm(self, ord, dim, keepdim)
787    }
788}
789
790pub(crate) fn apply_linalg_eager(
791    op: LinalgOp,
792    inputs: &[&EagerTensor],
793) -> Result<Vec<EagerTensor>> {
794    let op = Arc::new(LinalgExtensionOp::new(op));
795    apply_eager_with_targeted_extension_session(op, inputs, eager_extension_module)
796}
797
798fn eager_extension_module(target: EagerExtensionTarget) -> Result<Arc<dyn ExtensionModule>> {
799    let EagerExtensionTarget {
800        engine_id,
801        backend_kind,
802    } = target;
803    match backend_kind {
804        EagerExtensionBackendKind::Cpu => {
805            extension_module::<CpuBackend>(engine_id).map_err(eager_runtime_config_error)
806        }
807        #[cfg(feature = "cuda")]
808        EagerExtensionBackendKind::Cuda => {
809            extension_module::<CudaBackend>(engine_id).map_err(eager_runtime_config_error)
810        }
811        #[cfg(feature = "webgpu")]
812        EagerExtensionBackendKind::WebGpu => {
813            extension_module::<WebGpuBackend>(engine_id).map_err(eager_runtime_config_error)
814        }
815    }
816}
817
818fn eager_runtime_config_error(source: tenferro_runtime::RuntimeConfigError) -> Error {
819    Error::runtime_state_source(
820        "tenferro_linalg::eager_extension_module",
821        ErrorPhase::Execution,
822        source,
823    )
824}
825
826/// Singular value decomposition for eager tensors.
827///
828/// # Examples
829///
830/// ```rust
831/// use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
832/// use tenferro_linalg::EagerTensorLinalgExt;
833///
834/// let ctx = EagerRuntime::new()?;
835/// let a = EagerTensor::from_tensor_in(
836///     Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 2.0]).unwrap(),
837///     ctx,
838/// ).unwrap();
839/// let (_u, s, _vt) = a.svd()?;
840/// assert_eq!(s.shape(), &[2]);
841/// # Ok::<(), tenferro_ad::Error>(())
842/// ```
843///
844/// # Errors
845///
846/// Returns `Error::Validation` for an invalid rank, matrix shape, or dtype,
847/// `Error::Extension` with an unsupported-dtype or non-convergence source when
848/// the backend cannot compute the decomposition, and `Error::RuntimeState`
849/// when the eager runtime or backend is unavailable.
850pub fn svd(a: &EagerTensor) -> Result<(EagerTensor, EagerTensor, EagerTensor)> {
851    svd_with_options(a, SvdOptions::default())
852}
853
854/// Singular value decomposition for eager tensors with explicit options.
855///
856/// `derivative_eps` regularizes decomposition derivative formulas. It is not a
857/// backend SVD solver tolerance.
858///
859/// # Examples
860///
861/// ```rust
862/// use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
863/// use tenferro_linalg::{EagerTensorLinalgExt, SvdGauge, SvdOptions};
864///
865/// let ctx = EagerRuntime::new()?;
866/// let a = EagerTensor::from_tensor_in(
867///     Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 2.0]).unwrap(),
868///     ctx,
869/// ).unwrap();
870/// let options = SvdOptions::default()
871///     .gauge(SvdGauge::CanonicalPivot)
872///     .derivative_eps(1.0e-10);
873/// let (_u, s, _vt) = a.svd_with_options(options)?;
874/// assert_eq!(s.shape(), &[2]);
875/// # Ok::<(), tenferro_ad::Error>(())
876/// ```
877///
878/// # Errors
879///
880/// Returns `Error::Validation` when `derivative_eps` is non-finite or
881/// non-positive, `Error::Extension` for unsupported dtypes or numerical
882/// non-convergence, and `Error::Internal` if the extension returns an
883/// unexpected number of outputs.
884pub fn svd_with_options(
885    a: &EagerTensor,
886    options: SvdOptions,
887) -> Result<(EagerTensor, EagerTensor, EagerTensor)> {
888    validate_derivative_eps("svd_with_options", options.derivative_eps)?;
889    let mut outputs = apply_linalg_eager(
890        LinalgOp::Svd {
891            derivative_eps: options.derivative_eps,
892            gauge: options.gauge,
893        },
894        &[a],
895    )?
896    .into_iter();
897    match (
898        outputs.next(),
899        outputs.next(),
900        outputs.next(),
901        outputs.next(),
902    ) {
903        (Some(u), Some(s), Some(vt), None) => Ok((u, s, vt)),
904        _ => Err(Error::Internal(
905            "svd eager op returned an unexpected number of outputs".to_string(),
906        )),
907    }
908}
909
910/// Full-matrices singular value decomposition for eager tensors.
911///
912/// Returns `(U, S, Vh)` with square `U` (`m x m`) and `Vh` (`n x n`); `S` holds
913/// `min(m, n)` singular values. The trailing `n - rank` rows of `Vh` span the
914/// input's right nullspace, which the thin [`svd`] cannot represent.
915///
916/// # Examples
917///
918/// ```rust
919/// # #[cfg(feature = "cpu-faer")]
920/// # {
921/// # use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
922/// # use tenferro_cpu::{CpuBackend, CpuBackendKind};
923/// # use tenferro_linalg::EagerTensorLinalgExt;
924/// # let faer = CpuBackend::with_threads_and_kind(1, CpuBackendKind::Faer)
925/// #     .expect("faer CPU backend");
926/// # let ctx = EagerRuntime::with_cpu_backend(faer)?;
927/// # let a = EagerTensor::from_tensor_in(
928/// #     Tensor::from_vec_col_major(vec![1, 2], vec![1.0_f64, 1.0]).unwrap(),
929/// #     ctx,
930/// # )?;
931/// let (u, s, vh) = a.svd_full()?;
932/// assert_eq!(u.shape(), &[1, 1]);
933/// assert_eq!(s.shape(), &[1]);
934/// assert_eq!(vh.shape(), &[2, 2]);
935/// let singular_values = s.value()?.as_slice::<f64>()?;
936/// assert!((singular_values[0] - 2.0_f64.sqrt()).abs() < 1e-12);
937/// # }
938/// # #[cfg(all(feature = "cpu-blas", not(feature = "cpu-faer")))]
939/// # {
940/// # use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
941/// # use tenferro_cpu::{CpuBackend, CpuBackendKind};
942/// # let blas = CpuBackend::with_threads_and_kind(1, CpuBackendKind::Blas)
943/// #     .expect("BLAS CPU backend");
944/// # let ctx = EagerRuntime::with_cpu_backend(blas)?;
945/// # let a = EagerTensor::from_tensor_in(
946/// #     Tensor::from_vec_col_major(vec![1, 2], vec![1.0_f64, 1.0]).unwrap(),
947/// #     ctx,
948/// # )?;
949/// # use tenferro_linalg::EagerTensorLinalgExt;
950/// # let error = match a.svd_full() {
951/// #     Ok(_) => panic!("expected full-matrices SVD to be unsupported for BLAS"),
952/// #     Err(error) => error,
953/// # };
954/// # assert!(error.to_string().contains("full-matrices SVD"));
955/// # }
956/// # Ok::<(), tenferro_ad::Error>(())
957/// ```
958///
959/// # Errors
960///
961/// Returns `Error::Validation` for an invalid rank and `Error::Extension` with
962/// an unsupported-operation source when the active backend does not implement
963/// full-matrices SVD (the CPU faer provider supports it; the LAPACK provider
964/// and GPU backends are unsupported in this slice). AD through the full variant
965/// is unsupported and surfaces a typed error, not a silent thin fallback.
966pub fn svd_full(a: &EagerTensor) -> Result<(EagerTensor, EagerTensor, EagerTensor)> {
967    let mut outputs = apply_linalg_eager(LinalgOp::SvdFull, &[a])?.into_iter();
968    match (
969        outputs.next(),
970        outputs.next(),
971        outputs.next(),
972        outputs.next(),
973    ) {
974        (Some(u), Some(s), Some(vh), None) => Ok((u, s, vh)),
975        _ => Err(Error::Internal(
976            "svd_full eager op returned an unexpected number of outputs".to_string(),
977        )),
978    }
979}
980
981/// QR decomposition for eager tensors.
982///
983/// # Examples
984///
985/// ```rust
986/// use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
987/// use tenferro_linalg::EagerTensorLinalgExt;
988///
989/// let ctx = EagerRuntime::new()?;
990/// let a = EagerTensor::from_tensor_in(
991///     Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 1.0]).unwrap(),
992///     ctx,
993/// ).unwrap();
994/// let (q, r) = a.qr()?;
995/// assert_eq!(q.shape(), &[2, 2]);
996/// assert_eq!(r.shape(), &[2, 2]);
997/// # Ok::<(), tenferro_ad::Error>(())
998/// ```
999///
1000/// # Errors
1001///
1002/// Returns `Error::Validation` for an invalid rank or matrix shape,
1003/// `Error::Extension` for an unsupported dtype or numerical failure, and
1004/// `Error::RuntimeState` when the eager runtime or backend is unavailable.
1005pub fn qr(a: &EagerTensor) -> Result<(EagerTensor, EagerTensor)> {
1006    qr_with_options(a, QrOptions::default())
1007}
1008
1009/// Initialize compact Householder QR state for an eager matrix.
1010///
1011/// # Errors
1012///
1013/// Returns `Error::Validation` for known invalid metadata,
1014/// `Error::Extension` for unsupported or provider failures, or
1015/// `Error::RuntimeState` when eager execution is unavailable.
1016pub fn householder_qr(a: &EagerTensor) -> Result<crate::HouseholderQr<EagerTensor>> {
1017    let mut outputs = apply_linalg_eager(LinalgOp::HouseholderQrFactor, &[a])?.into_iter();
1018    match (outputs.next(), outputs.next(), outputs.next()) {
1019        (Some(packed), Some(coeff), None) => Ok(
1020            crate::HouseholderQr::<EagerTensor>::from_eager_outputs(packed, coeff),
1021        ),
1022        _ => Err(Error::Internal(
1023            "householder_qr returned an unexpected output count".into(),
1024        )),
1025    }
1026}
1027
1028/// QR decomposition for eager tensors with explicit options.
1029///
1030/// `gauge` controls optional sign or phase post-processing.
1031///
1032/// # Examples
1033///
1034/// ```rust
1035/// use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
1036/// use tenferro_linalg::{EagerTensorLinalgExt, QrGauge, QrOptions};
1037///
1038/// let ctx = EagerRuntime::new()?;
1039/// let a = EagerTensor::from_tensor_in(
1040///     Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 1.0]).unwrap(),
1041///     ctx,
1042/// ).unwrap();
1043/// let (q, r) = a.qr_with_options(QrOptions::default().gauge(QrGauge::PositiveDiagonal))?;
1044/// assert_eq!(q.shape(), &[2, 2]);
1045/// assert_eq!(r.shape(), &[2, 2]);
1046/// # Ok::<(), tenferro_ad::Error>(())
1047/// ```
1048///
1049/// # Errors
1050///
1051/// Returns `Error::Validation` for an invalid rank or matrix shape,
1052/// `Error::Extension` for an unsupported dtype or numerical failure, and
1053/// `Error::Internal` if the extension returns an unexpected number of outputs.
1054pub fn qr_with_options(a: &EagerTensor, options: QrOptions) -> Result<(EagerTensor, EagerTensor)> {
1055    two_outputs(
1056        apply_linalg_eager(
1057            LinalgOp::Qr {
1058                gauge: options.gauge,
1059            },
1060            &[a],
1061        )?,
1062        "qr",
1063    )
1064}
1065
1066/// Column-pivoted rank-revealing QR for eager tensors.
1067///
1068/// # Errors
1069/// Returns validation errors for invalid rank, dtype, or tolerances; numerical
1070/// failure for non-finite values; and explicit unsupported, backend, runtime,
1071/// or output-contract errors.
1072///
1073/// # Examples
1074///
1075/// ```rust
1076/// # use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
1077/// # use tenferro_cpu::CpuBackend;
1078/// # use tenferro_linalg::{EagerTensorLinalgExt, RankRevealingQrOptions};
1079/// # let runtime = EagerRuntime::with_cpu_backend(CpuBackend::new())?;
1080/// # let a = EagerTensor::from_tensor_in(
1081/// #     Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 2.0])?, runtime)?;
1082/// let result = a.rank_revealing_qr(RankRevealingQrOptions::default())?;
1083/// assert_eq!(result.rank.shape(), &[]);
1084/// # Ok::<(), tenferro_ad::Error>(())
1085/// ```
1086pub fn rank_revealing_qr(
1087    a: &EagerTensor,
1088    options: RankRevealingQrOptions,
1089) -> Result<RankRevealingQrResult<EagerTensor>> {
1090    validate_rank_revealing_qr_options("rank_revealing_qr", options)?;
1091    let mut outputs = apply_linalg_eager(
1092        LinalgOp::RankRevealingQr {
1093            gauge: options.gauge,
1094            rtol: options.rtol,
1095            atol: options.atol,
1096        },
1097        &[a],
1098    )?
1099    .into_iter();
1100    match (
1101        outputs.next(),
1102        outputs.next(),
1103        outputs.next(),
1104        outputs.next(),
1105        outputs.next(),
1106    ) {
1107        (Some(q), Some(r), Some(column_permutation), Some(rank), None) => {
1108            Ok(RankRevealingQrResult {
1109                q,
1110                r,
1111                column_permutation,
1112                rank,
1113            })
1114        }
1115        _ => Err(Error::Internal(
1116            "rank_revealing_qr eager op returned an unexpected number of outputs".into(),
1117        )),
1118    }
1119}
1120
1121/// LU factorization for eager tensors.
1122///
1123/// # Examples
1124///
1125/// ```rust
1126/// use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
1127/// use tenferro_linalg::EagerTensorLinalgExt;
1128///
1129/// let ctx = EagerRuntime::new()?;
1130/// let a = EagerTensor::from_tensor_in(
1131///     Tensor::from_vec_col_major(vec![2, 2], vec![0.0_f64, 1.0, 1.0, 0.0]).unwrap(),
1132///     ctx,
1133/// ).unwrap();
1134/// let (_p, l, u, parity) = a.lu()?;
1135/// assert_eq!(l.shape(), &[2, 2]);
1136/// assert_eq!(u.shape(), &[2, 2]);
1137/// assert_eq!(parity.shape(), &[] as &[usize]);
1138/// # Ok::<(), tenferro_ad::Error>(())
1139/// ```
1140///
1141/// # Errors
1142///
1143/// Returns `Error::Validation` for an invalid rank or matrix shape,
1144/// `Error::Extension` for an unsupported dtype or singular numerical result,
1145/// and `Error::RuntimeState` when the eager runtime or backend is unavailable.
1146pub fn lu(a: &EagerTensor) -> Result<(EagerTensor, EagerTensor, EagerTensor, EagerTensor)> {
1147    let mut outputs = apply_linalg_eager(LinalgOp::Lu, &[a])?.into_iter();
1148    match (
1149        outputs.next(),
1150        outputs.next(),
1151        outputs.next(),
1152        outputs.next(),
1153        outputs.next(),
1154    ) {
1155        (Some(p), Some(l), Some(u), Some(parity), None) => Ok((p, l, u, parity)),
1156        _ => Err(Error::Internal(
1157            "lu eager op returned an unexpected number of outputs".to_string(),
1158        )),
1159    }
1160}
1161
1162/// Complete-pivot LU factorization for eager tensors.
1163///
1164/// Returns `(P, L, U, Q, parity)` with reconstruction convention
1165/// `A = P^T * L * U * Q`, equivalently `P * A * Q^T = L * U`. `parity` is a
1166/// scalar real tensor containing `+1` or `-1`: `F32` for `F32`/`C32` inputs and
1167/// `F64` for `F64`/`C64` inputs.
1168///
1169/// # Examples
1170///
1171/// ```rust
1172/// use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
1173/// use tenferro_linalg::EagerTensorLinalgExt;
1174///
1175/// let ctx = EagerRuntime::new()?;
1176/// let a = EagerTensor::from_tensor_in(
1177///     Tensor::from_vec_col_major(vec![2, 2], vec![0.0_f64, 2.0, 1.0, 3.0]).unwrap(),
1178///     ctx,
1179/// ).unwrap();
1180/// let (p, _l, _u, q, parity) = a.full_piv_lu()?;
1181/// assert_eq!(p.shape(), &[2, 2]);
1182/// assert_eq!(q.shape(), &[2, 2]);
1183/// assert_eq!(parity.shape(), &[] as &[usize]);
1184/// # Ok::<(), tenferro_ad::Error>(())
1185/// ```
1186///
1187/// # Errors
1188///
1189/// Returns `Error::Validation` for an invalid rank or matrix shape,
1190/// `Error::Extension` for an unsupported dtype or singular numerical result,
1191/// and `Error::Internal` if the extension returns an unexpected number of
1192/// outputs.
1193pub fn full_piv_lu(
1194    a: &EagerTensor,
1195) -> Result<(
1196    EagerTensor,
1197    EagerTensor,
1198    EagerTensor,
1199    EagerTensor,
1200    EagerTensor,
1201)> {
1202    let mut outputs = apply_linalg_eager(LinalgOp::FullPivLu, &[a])?.into_iter();
1203    match (
1204        outputs.next(),
1205        outputs.next(),
1206        outputs.next(),
1207        outputs.next(),
1208        outputs.next(),
1209        outputs.next(),
1210    ) {
1211        (Some(p), Some(l), Some(u), Some(q), Some(parity), None) => Ok((p, l, u, q, parity)),
1212        _ => Err(Error::Internal(
1213            "full_piv_lu eager op returned an unexpected number of outputs".to_string(),
1214        )),
1215    }
1216}
1217
1218/// Solve a linear system using complete-pivot LU behavior.
1219///
1220/// # Examples
1221///
1222/// ```rust
1223/// use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
1224/// use tenferro_linalg::EagerTensorLinalgExt;
1225///
1226/// let ctx = EagerRuntime::new()?;
1227/// let a = EagerTensor::from_tensor_in(
1228///     Tensor::from_vec_col_major(vec![2, 2], vec![0.0_f64, 2.0, 1.0, 3.0]).unwrap(),
1229///     ctx.clone(),
1230/// ).unwrap();
1231/// let b = EagerTensor::from_tensor_in(
1232///     Tensor::from_vec_col_major(vec![2, 1], vec![-1.0_f64, 5.0]).unwrap(),
1233///     ctx,
1234/// ).unwrap();
1235/// let x = a.full_piv_lu_solve(&b)?;
1236/// assert_eq!(x.shape(), &[2, 1]);
1237/// # Ok::<(), tenferro_ad::Error>(())
1238/// ```
1239///
1240/// # Errors
1241///
1242/// Returns `Error::Validation` when `a` and `b` have incompatible matrix or
1243/// batch shapes, `Error::Extension` for an unsupported dtype or singular
1244/// system, and `Error::RuntimeState` when the backend is unavailable.
1245pub fn full_piv_lu_solve(a: &EagerTensor, b: &EagerTensor) -> Result<EagerTensor> {
1246    one_output(
1247        apply_linalg_eager(LinalgOp::FullPivLuSolve { transpose_a: false }, &[a, b])?,
1248        "full_piv_lu_solve",
1249    )
1250}
1251
1252/// Solve a linear system for eager tensors.
1253///
1254/// # Examples
1255///
1256/// ```rust
1257/// use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
1258/// use tenferro_linalg::EagerTensorLinalgExt;
1259///
1260/// let ctx = EagerRuntime::new()?;
1261/// let a = EagerTensor::from_tensor_in(
1262///     Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 0.0, 4.0]).unwrap(),
1263///     ctx.clone(),
1264/// ).unwrap();
1265/// let b = EagerTensor::from_tensor_in(
1266///     Tensor::from_vec_col_major(vec![2, 1], vec![4.0_f64, 8.0]).unwrap(),
1267///     ctx,
1268/// ).unwrap();
1269/// let x = a.solve(&b)?;
1270/// assert_eq!(x.shape(), &[2, 1]);
1271/// # Ok::<(), tenferro_ad::Error>(())
1272/// ```
1273///
1274/// # Errors
1275///
1276/// Returns `Error::Validation` for incompatible matrix, batch, or dtype
1277/// metadata, `Error::Extension` for an unsupported dtype or singular system,
1278/// and `Error::RuntimeState` when the backend is unavailable.
1279pub fn solve(a: &EagerTensor, b: &EagerTensor) -> Result<EagerTensor> {
1280    one_output(apply_linalg_eager(LinalgOp::Solve, &[a, b])?, "solve")
1281}
1282
1283/// Cholesky factorization for eager tensors.
1284///
1285/// # Examples
1286///
1287/// ```rust
1288/// use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
1289/// use tenferro_linalg::EagerTensorLinalgExt;
1290///
1291/// let ctx = EagerRuntime::new()?;
1292/// let a = EagerTensor::from_tensor_in(
1293///     Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 1.0]).unwrap(),
1294///     ctx,
1295/// ).unwrap();
1296/// let l = a.cholesky()?;
1297/// assert_eq!(l.shape(), &[2, 2]);
1298/// # Ok::<(), tenferro_ad::Error>(())
1299/// ```
1300///
1301/// # Errors
1302///
1303/// Returns `Error::Validation` for a non-square or invalid-rank input,
1304/// `Error::Extension` for an unsupported dtype or a non-positive-definite
1305/// matrix, and `Error::RuntimeState` when the backend is unavailable.
1306pub fn cholesky(a: &EagerTensor) -> Result<EagerTensor> {
1307    one_output(apply_linalg_eager(LinalgOp::Cholesky, &[a])?, "cholesky")
1308}
1309
1310/// Hermitian eigenvalue decomposition for eager tensors.
1311///
1312/// # Examples
1313///
1314/// ```rust
1315/// use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
1316/// use tenferro_linalg::EagerTensorLinalgExt;
1317///
1318/// let ctx = EagerRuntime::new()?;
1319/// let a = EagerTensor::from_tensor_in(
1320///     Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 3.0]).unwrap(),
1321///     ctx,
1322/// ).unwrap();
1323/// let (values, vectors) = a.eigh()?;
1324/// assert_eq!(values.shape(), &[2]);
1325/// assert_eq!(vectors.shape(), &[2, 2]);
1326/// # Ok::<(), tenferro_ad::Error>(())
1327/// ```
1328///
1329/// # Errors
1330///
1331/// Returns `Error::Validation` for a non-square or invalid-rank input,
1332/// `Error::Extension` for an unsupported dtype or numerical non-convergence,
1333/// and `Error::RuntimeState` when the backend is unavailable.
1334pub fn eigh(a: &EagerTensor) -> Result<(EagerTensor, EagerTensor)> {
1335    eigh_with_options(a, EighOptions::default())
1336}
1337
1338/// Hermitian eigenvalue decomposition for eager tensors with explicit options.
1339///
1340/// `derivative_eps` regularizes derivative formulas for repeated or nearly
1341/// repeated eigenvalues. It is not a backend eigensolver tolerance.
1342///
1343/// # Examples
1344///
1345/// ```rust
1346/// use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
1347/// use tenferro_linalg::{EagerTensorLinalgExt, EighGauge, EighOptions};
1348///
1349/// let ctx = EagerRuntime::new()?;
1350/// let a = EagerTensor::from_tensor_in(
1351///     Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 3.0]).unwrap(),
1352///     ctx,
1353/// ).unwrap();
1354/// let (values, vectors) = a
1355///     .eigh_with_options(
1356///         EighOptions::default()
1357///             .gauge(EighGauge::CanonicalPivot)
1358///             .derivative_eps(1.0e-10),
1359///     )?;
1360/// assert_eq!(values.shape(), &[2]);
1361/// assert_eq!(vectors.shape(), &[2, 2]);
1362/// # Ok::<(), tenferro_ad::Error>(())
1363/// ```
1364///
1365/// # Errors
1366///
1367/// Returns `Error::Validation` for an invalid rank, shape, or
1368/// `derivative_eps`, `Error::Extension` for unsupported dtypes or numerical
1369/// non-convergence, and `Error::Internal` for an output-count violation.
1370pub fn eigh_with_options(
1371    a: &EagerTensor,
1372    options: EighOptions,
1373) -> Result<(EagerTensor, EagerTensor)> {
1374    validate_derivative_eps("eigh_with_options", options.derivative_eps)?;
1375    two_outputs(
1376        apply_linalg_eager(
1377            LinalgOp::Eigh {
1378                derivative_eps: options.derivative_eps,
1379                gauge: options.gauge,
1380            },
1381            &[a],
1382        )?,
1383        "eigh",
1384    )
1385}
1386
1387/// General eigenvalue decomposition for eager tensors.
1388///
1389/// # Examples
1390///
1391/// ```rust
1392/// use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
1393/// use tenferro_linalg::EagerTensorLinalgExt;
1394///
1395/// let ctx = EagerRuntime::new()?;
1396/// let a = EagerTensor::from_tensor_in(
1397///     Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 3.0]).unwrap(),
1398///     ctx,
1399/// ).unwrap();
1400/// let (values, vectors) = a.eig()?;
1401/// assert_eq!(values.shape(), &[2]);
1402/// assert_eq!(vectors.shape(), &[2, 2]);
1403/// # Ok::<(), tenferro_ad::Error>(())
1404/// ```
1405///
1406/// # Errors
1407///
1408/// Returns `Error::Validation` for a non-square or invalid-rank input,
1409/// `Error::Extension` for an unsupported dtype or numerical non-convergence,
1410/// and `Error::RuntimeState` when the backend is unavailable.
1411pub fn eig(a: &EagerTensor) -> Result<(EagerTensor, EagerTensor)> {
1412    two_outputs(
1413        apply_linalg_eager(
1414            LinalgOp::Eig {
1415                input_dtype: a.dtype(),
1416            },
1417            &[a],
1418        )?,
1419        "eig",
1420    )
1421}
1422
1423/// Triangular solve for eager tensors.
1424///
1425/// # Examples
1426///
1427/// ```rust
1428/// use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
1429/// use tenferro_linalg::EagerTensorLinalgExt;
1430///
1431/// let ctx = EagerRuntime::new()?;
1432/// let a = EagerTensor::from_tensor_in(
1433///     Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 1.0, 3.0]).unwrap(),
1434///     ctx.clone(),
1435/// ).unwrap();
1436/// let b = EagerTensor::from_tensor_in(
1437///     Tensor::from_vec_col_major(vec![2, 1], vec![2.0_f64, 7.0]).unwrap(),
1438///     ctx,
1439/// ).unwrap();
1440/// let x = a.triangular_solve(&b, true, true, false, false)?;
1441/// assert_eq!(x.shape(), &[2, 1]);
1442/// # Ok::<(), tenferro_ad::Error>(())
1443/// ```
1444///
1445/// # Errors
1446///
1447/// Returns `Error::Validation` for incompatible matrix, batch, or dtype
1448/// metadata, `Error::Extension` for an unsupported dtype or singular system,
1449/// and `Error::RuntimeState` when the backend is unavailable.
1450pub fn triangular_solve(
1451    a: &EagerTensor,
1452    b: &EagerTensor,
1453    left_side: bool,
1454    lower: bool,
1455    transpose_a: bool,
1456    unit_diagonal: bool,
1457) -> Result<EagerTensor> {
1458    one_output(
1459        apply_linalg_eager(
1460            LinalgOp::TriangularSolve {
1461                left_side,
1462                lower,
1463                transpose_a,
1464                unit_diagonal,
1465            },
1466            &[a, b],
1467        )?,
1468        "triangular_solve",
1469    )
1470}
1471
1472pub(crate) fn one_output(outputs: Vec<EagerTensor>, name: &str) -> Result<EagerTensor> {
1473    let mut outputs = outputs.into_iter();
1474    match (outputs.next(), outputs.next()) {
1475        (Some(output), None) => Ok(output),
1476        _ => Err(Error::Internal(format!(
1477            "{name} eager op returned an unexpected number of outputs"
1478        ))),
1479    }
1480}
1481
1482fn two_outputs(outputs: Vec<EagerTensor>, name: &str) -> Result<(EagerTensor, EagerTensor)> {
1483    let mut outputs = outputs.into_iter();
1484    match (outputs.next(), outputs.next(), outputs.next()) {
1485        (Some(lhs), Some(rhs), None) => Ok((lhs, rhs)),
1486        _ => Err(Error::Internal(format!(
1487            "{name} eager op returned an unexpected number of outputs"
1488        ))),
1489    }
1490}