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}