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