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