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