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