tenferro_linalg/backend.rs
1use tenferro_tensor::{BackendSession, Tensor, TensorRead, TensorWrite};
2
3pub(crate) use crate::error::unsupported_dtype;
4use crate::extension::{
5 apply_eigh_gauge, apply_qr_gauge, apply_svd_gauge, validate_derivative_eps, EighOptions,
6 QrOptions, SvdOptions,
7};
8
9/// Backend surface required by the linalg extension runtime.
10///
11/// # Examples
12///
13/// ```rust
14/// use tenferro_cpu::{with_cpu_exec_session, CpuBackend, CpuExecSession};
15/// use tenferro_linalg::backend::LinalgBackend;
16/// use tenferro_tensor::BackendSessionHost;
17///
18/// fn assert_linalg_backend<B: LinalgBackend>() {}
19///
20/// assert_linalg_backend::<CpuExecSession<'static>>();
21/// let mut host = CpuBackend::new();
22/// host.with_backend_session(|session| {
23/// with_cpu_exec_session(session, |_backend| ())
24/// .expect("CpuBackend must expose a CpuExecSession");
25/// });
26/// ```
27pub trait LinalgBackend: BackendSession {
28 /// Compute a Cholesky factorization.
29 ///
30 /// # Errors
31 ///
32 /// Returns `Error::Validation` for non-matrix, non-square, or unsupported
33 /// input dtypes; `Error::Extension` with `ErrorKind::NumericalFailure`
34 /// when the matrix is not positive definite; or a typed backend source
35 /// when the provider cannot execute the factorization.
36 fn cholesky(&mut self, input: &Tensor) -> tenferro_tensor::Result<Tensor>;
37
38 /// Solve a triangular linear system with explicit side, triangle,
39 /// transpose, and unit-diagonal flags.
40 ///
41 /// # Errors
42 ///
43 /// Returns `Error::Validation` for incompatible matrix/rhs shapes, rank,
44 /// or dtype; `Error::Extension` with `ErrorKind::NumericalFailure` for a
45 /// singular or zero-diagonal system; or a typed backend source for a
46 /// provider failure.
47 fn triangular_solve(
48 &mut self,
49 a: &Tensor,
50 b: &Tensor,
51 left_side: bool,
52 lower: bool,
53 transpose_a: bool,
54 unit_diagonal: bool,
55 ) -> tenferro_tensor::Result<Tensor>;
56
57 // INVARIANT: the six flags are the public triangular-solve contract and mirror the owned hook.
58 #[allow(clippy::too_many_arguments)]
59 /// Solve a triangular linear system from tensor read targets.
60 ///
61 /// Backends may canonicalize the inputs inside the same placement family,
62 /// but must not silently transfer between CPU and GPU memory.
63 ///
64 /// # Examples
65 ///
66 /// ```rust
67 /// use tenferro_cpu::{with_cpu_exec_session, CpuBackend};
68 /// use tenferro_linalg::LinalgBackend;
69 /// use tenferro_tensor::{BackendSessionHost, Tensor, TensorRead};
70 ///
71 /// let a = Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 1.0, 3.0])?;
72 /// let b = Tensor::from_vec_col_major(vec![2, 1], vec![4.0_f64, 9.0])?;
73 /// let mut host = CpuBackend::new();
74 /// let x = host.with_backend_session(|session| {
75 /// with_cpu_exec_session(session, |backend| {
76 /// backend.triangular_solve_read(
77 /// TensorRead::from_tensor(&a),
78 /// TensorRead::from_tensor(&b),
79 /// true,
80 /// false,
81 /// false,
82 /// false,
83 /// )
84 /// })
85 /// .expect("CpuBackend must expose a CpuExecSession")
86 /// })?;
87 /// let Tensor::F64(x) = x else { unreachable!("F64 inputs return F64 output") };
88 /// assert_eq!(x.host_data()?, &[0.5, 3.0]);
89 /// # Ok::<(), tenferro_tensor::Error>(())
90 /// ```
91 ///
92 /// # Errors
93 ///
94 /// The default implementation returns `Error::Unsupported` because the
95 /// backend does not accept tensor read targets. Implementations may return
96 /// `Error::Validation` for incompatible shapes or dtypes,
97 /// `Error::RuntimeState` for invalid placement, `Error::Extension` for a
98 /// singular system, or a typed backend-source error.
99 fn triangular_solve_read(
100 &mut self,
101 _a: TensorRead<'_>,
102 _b: TensorRead<'_>,
103 _left_side: bool,
104 _lower: bool,
105 _transpose_a: bool,
106 _unit_diagonal: bool,
107 ) -> tenferro_tensor::Result<Tensor> {
108 Err(tenferro_tensor::Error::unsupported(
109 "triangular_solve",
110 "backend does not accept tensor reads at this execution boundary",
111 ))
112 }
113
114 /// Compute public LU outputs `(P, L, U, parity)`.
115 ///
116 /// # Errors
117 ///
118 /// Returns `Error::Validation` when the input is not a supported matrix or
119 /// dtype, and `Error::Extension` or a typed backend source when LU
120 /// execution or pivot storage fails.
121 fn lu(&mut self, input: &Tensor) -> tenferro_tensor::Result<Vec<Tensor>>;
122
123 #[doc(hidden)]
124 fn lu_factor(&mut self, _input: &Tensor) -> tenferro_tensor::Result<Vec<Tensor>> {
125 Err(tenferro_tensor::Error::unsupported(
126 "lu_factor",
127 format!(
128 "backend {} does not implement internal packed LU factorization",
129 std::any::type_name::<Self>()
130 ),
131 ))
132 }
133
134 /// Compute complete-pivot LU outputs `(P, L, U, Q, parity)`.
135 ///
136 /// The reconstruction convention is `A = P^T * L * U * Q`, equivalently
137 /// `P * A * Q^T = L * U`. `parity` is a scalar real tensor containing
138 /// `+1` or `-1`: `F32` for `F32`/`C32` inputs and `F64` for `F64`/`C64`
139 /// inputs.
140 ///
141 /// # Errors
142 ///
143 /// Returns `Error::Validation` for an invalid rank, square-shape
144 /// requirement, or dtype, and `Error::Extension` or a typed backend source
145 /// when complete-pivot factorization cannot be executed.
146 fn full_piv_lu(&mut self, input: &Tensor) -> tenferro_tensor::Result<Vec<Tensor>>;
147
148 /// Solve a linear system through the complete-pivot LU path.
149 ///
150 /// With `transpose_a = false`, this solves `A * x = b`. With
151 /// `transpose_a = true`, this solves `A^T * x = b`.
152 ///
153 /// # Errors
154 ///
155 /// Returns `Error::Validation` for incompatible coefficient/rhs shapes or
156 /// dtypes, `Error::Extension` with `ErrorKind::NumericalFailure` for a
157 /// singular system, or a typed backend source for provider failure.
158 fn full_piv_lu_solve(
159 &mut self,
160 a: &Tensor,
161 b: &Tensor,
162 transpose_a: bool,
163 ) -> tenferro_tensor::Result<Tensor>;
164
165 /// Compute public SVD outputs `(U, S, Vt)`.
166 ///
167 /// # Errors
168 ///
169 /// Returns `Error::Validation` for an unsupported rank or dtype and a
170 /// typed `Error::Extension` or backend source when the solver fails.
171 fn svd(&mut self, input: &Tensor) -> tenferro_tensor::Result<Vec<Tensor>>;
172
173 /// Compute public SVD outputs `(U, S, Vt)` with explicit options.
174 ///
175 /// `derivative_eps` is validated for API consistency, but concrete backend
176 /// execution does not perform AD. `gauge` controls optional singular-vector
177 /// post-processing.
178 ///
179 /// # Examples
180 ///
181 /// ```rust
182 /// use tenferro_cpu::{with_cpu_exec_session, CpuBackend};
183 /// use tenferro_linalg::{LinalgBackend, SvdGauge, SvdOptions};
184 /// use tenferro_tensor::{BackendSessionHost, Tensor};
185 ///
186 /// let input = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 2.0])?;
187 /// let mut host = CpuBackend::new();
188 /// let outputs = host.with_backend_session(|session| {
189 /// with_cpu_exec_session(session, |backend| {
190 /// backend.svd_with_options(
191 /// &input,
192 /// SvdOptions::default().gauge(SvdGauge::CanonicalPivot),
193 /// )
194 /// })
195 /// .expect("CpuBackend must expose a CpuExecSession")
196 /// })?;
197 /// assert_eq!(outputs[1].shape(), &[2]);
198 /// # Ok::<(), tenferro_tensor::Error>(())
199 /// ```
200 ///
201 /// # Errors
202 ///
203 /// Returns [`tenferro_tensor::Error::Validation`] containing
204 /// [`tenferro_tensor::ValidationError::InvalidArgument`] when
205 /// `derivative_eps` is non-finite or non-positive, or when canonical gauge
206 /// output metadata is malformed. It can return
207 /// [`tenferro_tensor::Error::Validation`] with
208 /// [`tenferro_tensor::ValidationError::RankMismatch`],
209 /// [`tenferro_tensor::ValidationError::ShapeMismatch`], or
210 /// [`tenferro_tensor::ValidationError::DTypeMismatch`] for the input
211 /// or generated outputs, [`tenferro_tensor::Error::Extension`] with the
212 /// typed `tenferro_linalg::Error::UnsupportedDType` or
213 /// `NonConvergence` source, [`tenferro_tensor::Error::BackendSource`] for
214 /// provider calls, and [`tenferro_tensor::Error::RuntimeState`] for
215 /// placement failures. A CPU provider that was not compiled is reported
216 /// as [`tenferro_tensor::ValidationError::InvalidArgument`] on the
217 /// provider configuration.
218 fn svd_with_options(
219 &mut self,
220 input: &Tensor,
221 options: SvdOptions,
222 ) -> tenferro_tensor::Result<Vec<Tensor>> {
223 validate_derivative_eps("svd_with_options", options.derivative_eps)?;
224 let mut outputs = self.svd(input)?;
225 apply_svd_gauge(options.gauge, &mut outputs)?;
226 Ok(outputs)
227 }
228
229 /// Compute public full-matrices SVD outputs `(U, S, Vt)` with `U` shaped
230 /// `m x m` and `Vt` shaped `n x n`, so the trailing `Vt` rows span the
231 /// input's right nullspace.
232 ///
233 /// # Errors
234 ///
235 /// The default implementation returns `Error::Unsupported`: a backend that
236 /// does not implement the full variant reports it explicitly rather than
237 /// silently falling back to the thin decomposition. Implementing backends
238 /// may additionally return `Error::Validation` for an unsupported rank or
239 /// dtype and a typed backend source when the solver fails.
240 fn svd_full(&mut self, _input: &Tensor) -> tenferro_tensor::Result<Vec<Tensor>> {
241 Err(tenferro_tensor::Error::unsupported(
242 "svd_full",
243 format!(
244 "backend {} does not implement full-matrices SVD",
245 std::any::type_name::<Self>()
246 ),
247 ))
248 }
249
250 #[doc(hidden)]
251 fn svd_values(&mut self, _input: &Tensor) -> tenferro_tensor::Result<Tensor> {
252 Err(tenferro_tensor::Error::unsupported(
253 "svd_values",
254 format!(
255 "backend {} does not implement internal singular-values-only decomposition",
256 std::any::type_name::<Self>()
257 ),
258 ))
259 }
260
261 /// Compute a singular value decomposition from a tensor read target.
262 ///
263 /// Backends may canonicalize the input inside the same placement family, but
264 /// must not silently transfer between CPU and GPU memory.
265 ///
266 /// # Examples
267 ///
268 /// ```rust
269 /// use tenferro_linalg::LinalgBackend;
270 /// use tenferro_cpu::{with_cpu_exec_session, CpuBackend};
271 /// use tenferro_tensor::{BackendSessionHost, TensorRead, TensorView, TypedTensor};
272 ///
273 /// let input = TypedTensor::<f64>::from_vec_col_major(
274 /// vec![2, 2],
275 /// vec![1.0, 0.0, 0.0, 2.0],
276 /// )?;
277 /// let mut host = CpuBackend::new();
278 /// let outputs = host.with_backend_session(|session| {
279 /// with_cpu_exec_session(session, |backend| {
280 /// backend.svd_read(TensorRead::from_view(TensorView::F64(input.as_view())))
281 /// })
282 /// .expect("CpuBackend must expose a CpuExecSession")
283 /// })?;
284 /// assert_eq!(outputs[1].shape(), &[2]);
285 /// # Ok::<(), tenferro_tensor::Error>(())
286 /// ```
287 ///
288 /// # Errors
289 ///
290 /// The default implementation returns `Error::Unsupported` because the
291 /// backend does not accept tensor read targets; an implementation may instead
292 /// return validation or typed backend-source errors after canonicalizing
293 /// the view.
294 fn svd_read(&mut self, _input: TensorRead<'_>) -> tenferro_tensor::Result<Vec<Tensor>> {
295 Err(tenferro_tensor::Error::unsupported(
296 "svd",
297 "backend does not accept tensor reads at this execution boundary",
298 ))
299 }
300
301 /// Compute public QR outputs `(Q, R)`.
302 ///
303 /// QR is thin: for an `m x n` input, `Q` has shape `m x min(m, n)` and
304 /// `R` has shape `min(m, n) x n`.
305 ///
306 /// # Errors
307 ///
308 /// Returns `Error::Validation` for an unsupported rank, shape, or dtype,
309 /// and a typed `Error::Extension` or backend source when QR execution
310 /// fails.
311 fn qr(&mut self, input: &Tensor) -> tenferro_tensor::Result<Vec<Tensor>>;
312
313 /// Compute public QR outputs `(Q, R)` with explicit options.
314 ///
315 /// `gauge` controls optional sign or phase post-processing.
316 ///
317 /// # Examples
318 ///
319 /// ```rust
320 /// use tenferro_cpu::{with_cpu_exec_session, CpuBackend};
321 /// use tenferro_linalg::{LinalgBackend, QrGauge, QrOptions};
322 /// use tenferro_tensor::{BackendSessionHost, Tensor};
323 ///
324 /// let input = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 2.0])?;
325 /// let mut host = CpuBackend::new();
326 /// let outputs = host.with_backend_session(|session| {
327 /// with_cpu_exec_session(session, |backend| {
328 /// backend.qr_with_options(
329 /// &input,
330 /// QrOptions::default().gauge(QrGauge::PositiveDiagonal),
331 /// )
332 /// })
333 /// .expect("CpuBackend must expose a CpuExecSession")
334 /// })?;
335 /// assert_eq!(outputs[0].shape(), &[2, 2]);
336 /// # Ok::<(), tenferro_tensor::Error>(())
337 /// ```
338 ///
339 /// # Errors
340 ///
341 /// Returns [`tenferro_tensor::Error::Validation`] containing
342 /// [`tenferro_tensor::ValidationError::RankMismatch`] or
343 /// [`tenferro_tensor::ValidationError::ShapeMismatch`] for an invalid
344 /// matrix input, or [`tenferro_tensor::ValidationError::InvalidArgument`]
345 /// for malformed gauge output metadata, checked size arithmetic, or an
346 /// unavailable compiled provider. A mismatched generated `Q`/`R` dtype is reported as
347 /// [`tenferro_tensor::ValidationError::DTypeMismatch`]. Provider
348 /// unsupported dtype or numerical rejection is
349 /// [`tenferro_tensor::Error::Extension`] with a typed linalg source, while
350 /// provider failures use [`tenferro_tensor::Error::BackendSource`] and a
351 /// backend-resident input uses [`tenferro_tensor::Error::RuntimeState`].
352 fn qr_with_options(
353 &mut self,
354 input: &Tensor,
355 options: QrOptions,
356 ) -> tenferro_tensor::Result<Vec<Tensor>> {
357 let mut outputs = self.qr(input)?;
358 apply_qr_gauge(options.gauge, &mut outputs)?;
359 Ok(outputs)
360 }
361
362 /// Compute public QR outputs `(Q, R)` from a tensor read target.
363 ///
364 /// Backends may canonicalize the input inside the same placement family, but
365 /// must not silently transfer between CPU and GPU memory.
366 ///
367 /// # Examples
368 ///
369 /// ```rust
370 /// use tenferro_linalg::LinalgBackend;
371 /// use tenferro_cpu::{with_cpu_exec_session, CpuBackend};
372 /// use tenferro_tensor::{BackendSessionHost, TensorRead, TensorView, TypedTensor};
373 ///
374 /// let input = TypedTensor::<f64>::from_vec_col_major(
375 /// vec![2, 2],
376 /// vec![1.0, 0.0, 0.0, 2.0],
377 /// )?;
378 /// let mut host = CpuBackend::new();
379 /// let outputs = host.with_backend_session(|session| {
380 /// with_cpu_exec_session(session, |backend| {
381 /// backend.qr_read(TensorRead::from_view(TensorView::F64(input.as_view())))
382 /// })
383 /// .expect("CpuBackend must expose a CpuExecSession")
384 /// })?;
385 /// assert_eq!(outputs[0].shape(), &[2, 2]);
386 /// assert_eq!(outputs[1].shape(), &[2, 2]);
387 /// # Ok::<(), tenferro_tensor::Error>(())
388 /// ```
389 ///
390 /// # Errors
391 ///
392 /// The default implementation returns `Error::Unsupported` because the
393 /// backend does not accept tensor read targets; implementations may return
394 /// validation or typed backend-source errors.
395 fn qr_read(&mut self, _input: TensorRead<'_>) -> tenferro_tensor::Result<Vec<Tensor>> {
396 Err(tenferro_tensor::Error::unsupported(
397 "qr",
398 "backend does not accept tensor reads at this execution boundary",
399 ))
400 }
401
402 /// Compute public Hermitian eigendecomposition outputs `(values, vectors)`.
403 ///
404 /// The returned vector order is `[values, vectors]`, where `values` has
405 /// shape `[n]` and `vectors` has shape `[n, n]`.
406 ///
407 /// # Errors
408 ///
409 /// Returns `Error::Validation` for a non-square or unsupported-dtype input
410 /// and a typed `Error::Extension` or backend source when eigendecomposition
411 /// fails.
412 fn eigh(&mut self, input: &Tensor) -> tenferro_tensor::Result<Vec<Tensor>>;
413
414 /// Compute public Hermitian eigendecomposition outputs with explicit options.
415 ///
416 /// `derivative_eps` is validated for API consistency, but concrete backend
417 /// execution does not perform AD. `gauge` controls optional eigenvector
418 /// post-processing.
419 ///
420 /// # Examples
421 ///
422 /// ```rust
423 /// use tenferro_cpu::{with_cpu_exec_session, CpuBackend};
424 /// use tenferro_linalg::{EighGauge, EighOptions, LinalgBackend};
425 /// use tenferro_tensor::{BackendSessionHost, Tensor};
426 ///
427 /// let input = Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 2.0])?;
428 /// let mut host = CpuBackend::new();
429 /// let outputs = host.with_backend_session(|session| {
430 /// with_cpu_exec_session(session, |backend| {
431 /// backend.eigh_with_options(
432 /// &input,
433 /// EighOptions::default()
434 /// .gauge(EighGauge::CanonicalPivot)
435 /// .derivative_eps(1.0e-10),
436 /// )
437 /// })
438 /// .expect("CpuBackend must expose a CpuExecSession")
439 /// })?;
440 /// assert_eq!(outputs[0].shape(), &[2]);
441 /// # Ok::<(), tenferro_tensor::Error>(())
442 /// ```
443 ///
444 /// # Errors
445 ///
446 /// Returns [`tenferro_tensor::Error::Validation`] containing
447 /// [`tenferro_tensor::ValidationError::InvalidArgument`] when
448 /// `derivative_eps` is non-finite or non-positive, when canonical gauge
449 /// output metadata is malformed, or when checked output-size arithmetic
450 /// overflows. It can return [`tenferro_tensor::Error::Validation`] with
451 /// [`tenferro_tensor::ValidationError::RankMismatch`] or
452 /// [`tenferro_tensor::ValidationError::ShapeMismatch`] for the
453 /// matrix input, or [`tenferro_tensor::ValidationError::DTypeMismatch`]
454 /// for generated outputs. It can also return
455 /// [`tenferro_tensor::Error::Extension`] with typed
456 /// `tenferro_linalg::Error::UnsupportedDType` or `NonConvergence`, and
457 /// [`tenferro_tensor::Error::BackendSource`] or
458 /// [`tenferro_tensor::Error::RuntimeState`] for provider and placement
459 /// failures.
460 fn eigh_with_options(
461 &mut self,
462 input: &Tensor,
463 options: EighOptions,
464 ) -> tenferro_tensor::Result<Vec<Tensor>> {
465 validate_derivative_eps("eigh_with_options", options.derivative_eps)?;
466 let mut outputs = self.eigh(input)?;
467 apply_eigh_gauge(options.gauge, &mut outputs)?;
468 Ok(outputs)
469 }
470
471 /// Compute public Hermitian eigendecomposition outputs from a tensor read target.
472 ///
473 /// Backends may canonicalize the input inside the same placement family, but
474 /// must not silently transfer between CPU and GPU memory.
475 ///
476 /// # Examples
477 ///
478 /// ```rust
479 /// use tenferro_linalg::LinalgBackend;
480 /// use tenferro_cpu::{with_cpu_exec_session, CpuBackend};
481 /// use tenferro_tensor::{BackendSessionHost, TensorRead, TensorView, TypedTensor};
482 ///
483 /// let input = TypedTensor::<f64>::from_vec_col_major(
484 /// vec![2, 2],
485 /// vec![1.0, 0.0, 0.0, 2.0],
486 /// )?;
487 /// let mut host = CpuBackend::new();
488 /// let outputs = host.with_backend_session(|session| {
489 /// with_cpu_exec_session(session, |backend| {
490 /// backend.eigh_read(TensorRead::from_view(TensorView::F64(input.as_view())))
491 /// })
492 /// .expect("CpuBackend must expose a CpuExecSession")
493 /// })?;
494 /// assert_eq!(outputs[0].shape(), &[2]);
495 /// assert_eq!(outputs[1].shape(), &[2, 2]);
496 /// # Ok::<(), tenferro_tensor::Error>(())
497 /// ```
498 ///
499 /// # Errors
500 ///
501 /// The default implementation returns `Error::Unsupported` because the
502 /// backend does not accept tensor read targets; implementations may return
503 /// validation or typed backend-source errors.
504 fn eigh_read(&mut self, _input: TensorRead<'_>) -> tenferro_tensor::Result<Vec<Tensor>> {
505 Err(tenferro_tensor::Error::unsupported(
506 "eigh",
507 "backend does not accept tensor reads at this execution boundary",
508 ))
509 }
510
511 /// Compute Cholesky factorization from a tensor read target.
512 ///
513 /// Backends may canonicalize the input inside the same placement family, but
514 /// must not silently transfer between CPU and GPU memory.
515 ///
516 /// # Examples
517 ///
518 /// ```rust
519 /// use tenferro_linalg::LinalgBackend;
520 /// use tenferro_cpu::{with_cpu_exec_session, CpuBackend};
521 /// use tenferro_tensor::{BackendSessionHost, TensorRead, TensorView, TypedTensor};
522 ///
523 /// let input = TypedTensor::<f64>::from_vec_col_major(
524 /// vec![2, 2],
525 /// vec![4.0, 2.0, 2.0, 3.0],
526 /// )?;
527 /// let mut host = CpuBackend::new();
528 /// let output = host.with_backend_session(|session| {
529 /// with_cpu_exec_session(session, |backend| {
530 /// backend.cholesky_read(TensorRead::from_view(TensorView::F64(input.as_view())))
531 /// })
532 /// .expect("CpuBackend must expose a CpuExecSession")
533 /// })?;
534 /// assert_eq!(output.shape(), &[2, 2]);
535 /// # Ok::<(), tenferro_tensor::Error>(())
536 /// ```
537 ///
538 /// # Errors
539 ///
540 /// The default implementation returns `Error::Unsupported` because the
541 /// backend does not accept tensor read targets; implementations may return
542 /// validation or typed backend-source errors.
543 fn cholesky_read(&mut self, _input: TensorRead<'_>) -> tenferro_tensor::Result<Tensor> {
544 Err(tenferro_tensor::Error::unsupported(
545 "cholesky",
546 "backend does not accept tensor reads at this execution boundary",
547 ))
548 }
549
550 /// Compute public LU outputs from a tensor read target.
551 ///
552 /// Backends may canonicalize the input inside the same placement family, but
553 /// must not silently transfer between CPU and GPU memory.
554 ///
555 /// # Examples
556 ///
557 /// ```rust
558 /// use tenferro_linalg::LinalgBackend;
559 /// use tenferro_cpu::{with_cpu_exec_session, CpuBackend};
560 /// use tenferro_tensor::{BackendSessionHost, TensorRead, TensorView, TypedTensor};
561 ///
562 /// let input = TypedTensor::<f64>::from_vec_col_major(
563 /// vec![2, 2],
564 /// vec![1.0, 3.0, 2.0, 4.0],
565 /// )?;
566 /// let mut host = CpuBackend::new();
567 /// let outputs = host.with_backend_session(|session| {
568 /// with_cpu_exec_session(session, |backend| {
569 /// backend.lu_read(TensorRead::from_view(TensorView::F64(input.as_view())))
570 /// })
571 /// .expect("CpuBackend must expose a CpuExecSession")
572 /// })?;
573 /// assert_eq!(outputs.len(), 4);
574 /// # Ok::<(), tenferro_tensor::Error>(())
575 /// ```
576 ///
577 /// # Errors
578 ///
579 /// The default implementation returns `Error::Unsupported` because the
580 /// backend does not accept tensor read targets; implementations may return
581 /// validation or typed backend-source errors.
582 fn lu_read(&mut self, _input: TensorRead<'_>) -> tenferro_tensor::Result<Vec<Tensor>> {
583 Err(tenferro_tensor::Error::unsupported(
584 "lu",
585 "backend does not accept tensor reads at this execution boundary",
586 ))
587 }
588
589 /// Compute public full-pivoting LU outputs from a tensor read target.
590 ///
591 /// Backends may canonicalize the input inside the same placement family, but
592 /// must not silently transfer between CPU and GPU memory.
593 ///
594 /// # Examples
595 ///
596 /// ```rust
597 /// use tenferro_linalg::LinalgBackend;
598 /// use tenferro_cpu::{with_cpu_exec_session, CpuBackend};
599 /// use tenferro_tensor::{BackendSessionHost, TensorRead, TensorView, TypedTensor};
600 ///
601 /// let input = TypedTensor::<f64>::from_vec_col_major(
602 /// vec![2, 2],
603 /// vec![1.0, 3.0, 2.0, 4.0],
604 /// )?;
605 /// let mut host = CpuBackend::new();
606 /// let outputs = host.with_backend_session(|session| {
607 /// with_cpu_exec_session(session, |backend| {
608 /// backend.full_piv_lu_read(TensorRead::from_view(TensorView::F64(input.as_view())))
609 /// })
610 /// .expect("CpuBackend must expose a CpuExecSession")
611 /// })?;
612 /// assert_eq!(outputs.len(), 5);
613 /// # Ok::<(), tenferro_tensor::Error>(())
614 /// ```
615 ///
616 /// # Errors
617 ///
618 /// The default implementation returns `Error::Unsupported` because the
619 /// backend does not accept tensor read targets; implementations may return
620 /// validation or typed backend-source errors.
621 fn full_piv_lu_read(&mut self, _input: TensorRead<'_>) -> tenferro_tensor::Result<Vec<Tensor>> {
622 Err(tenferro_tensor::Error::unsupported(
623 "full_piv_lu",
624 "backend does not accept tensor reads at this execution boundary",
625 ))
626 }
627
628 /// Compute general eigendecomposition outputs from a tensor read target.
629 ///
630 /// Backends may canonicalize the input inside the same placement family, but
631 /// must not silently transfer between CPU and GPU memory.
632 ///
633 /// # Examples
634 ///
635 /// ```rust
636 /// use tenferro_linalg::LinalgBackend;
637 /// use tenferro_cpu::{with_cpu_exec_session, CpuBackend};
638 /// use tenferro_tensor::{BackendSessionHost, TensorRead, TensorView, TypedTensor};
639 ///
640 /// let input = TypedTensor::<f64>::from_vec_col_major(
641 /// vec![2, 2],
642 /// vec![2.0, 0.0, 0.0, 3.0],
643 /// )?;
644 /// let mut host = CpuBackend::new();
645 /// let outputs = host.with_backend_session(|session| {
646 /// with_cpu_exec_session(session, |backend| {
647 /// backend.eig_read(TensorRead::from_view(TensorView::F64(input.as_view())))
648 /// })
649 /// .expect("CpuBackend must expose a CpuExecSession")
650 /// })?;
651 /// assert_eq!(outputs.len(), 2);
652 /// # Ok::<(), tenferro_tensor::Error>(())
653 /// ```
654 ///
655 /// # Errors
656 ///
657 /// The default implementation returns `Error::Unsupported` because the
658 /// backend does not accept tensor read targets; implementations may return
659 /// validation or typed backend-source errors.
660 fn eig_read(&mut self, _input: TensorRead<'_>) -> tenferro_tensor::Result<Vec<Tensor>> {
661 Err(tenferro_tensor::Error::unsupported(
662 "eig",
663 "backend does not accept tensor reads at this execution boundary",
664 ))
665 }
666
667 #[doc(hidden)]
668 fn eigh_values(&mut self, _input: &Tensor) -> tenferro_tensor::Result<Tensor> {
669 Err(tenferro_tensor::Error::unsupported(
670 "eigh_values",
671 format!(
672 "backend {} does not implement internal Hermitian eigenvalues-only decomposition",
673 std::any::type_name::<Self>()
674 ),
675 ))
676 }
677
678 /// Compute public general eigendecomposition outputs `(values, vectors)`.
679 ///
680 /// # Errors
681 ///
682 /// Returns `Error::Validation` for a non-square, rank, or dtype mismatch,
683 /// and a typed `Error::Extension` or backend source when the eigensolver
684 /// fails.
685 fn eig(&mut self, input: &Tensor) -> tenferro_tensor::Result<Vec<Tensor>>;
686
687 #[doc(hidden)]
688 fn eig_values(&mut self, _input: &Tensor) -> tenferro_tensor::Result<Tensor> {
689 Err(tenferro_tensor::Error::unsupported(
690 "eig_values",
691 format!(
692 "backend {} does not implement internal general eigenvalues-only decomposition",
693 std::any::type_name::<Self>()
694 ),
695 ))
696 }
697
698 /// Solve a dense linear system.
699 ///
700 /// # Errors
701 ///
702 /// Returns `Error::Validation` for incompatible matrix/rhs shapes, rank,
703 /// or dtype; `Error::Extension` with `ErrorKind::NumericalFailure` for a
704 /// singular system; or a typed backend source for provider failure.
705 fn solve(&mut self, a: &Tensor, b: &Tensor) -> tenferro_tensor::Result<Tensor>;
706
707 /// Solve a linear system from tensor read targets.
708 ///
709 /// Backends may canonicalize the inputs inside the same placement family,
710 /// but must not silently transfer between CPU and GPU memory.
711 ///
712 /// # Examples
713 ///
714 /// ```rust
715 /// use tenferro_cpu::{with_cpu_exec_session, CpuBackend};
716 /// use tenferro_linalg::LinalgBackend;
717 /// use tenferro_tensor::{BackendSessionHost, Tensor, TensorRead};
718 ///
719 /// let a = Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 0.0, 3.0])?;
720 /// let b = Tensor::from_vec_col_major(vec![2, 1], vec![4.0_f64, 9.0])?;
721 /// let mut host = CpuBackend::new();
722 /// let x = host.with_backend_session(|session| {
723 /// with_cpu_exec_session(session, |backend| {
724 /// backend.solve_read(
725 /// TensorRead::from_tensor(&a),
726 /// TensorRead::from_tensor(&b),
727 /// )
728 /// })
729 /// .expect("CpuBackend must expose a CpuExecSession")
730 /// })?;
731 /// let Tensor::F64(x) = x else { unreachable!("F64 inputs return F64 output") };
732 /// assert_eq!(x.host_data()?, &[2.0, 3.0]);
733 /// # Ok::<(), tenferro_tensor::Error>(())
734 /// ```
735 ///
736 /// # Errors
737 ///
738 /// The default implementation returns `Error::Unsupported` because the
739 /// backend does not accept tensor read targets. Implementations may return
740 /// `Error::Validation` for incompatible shapes or dtypes,
741 /// `Error::RuntimeState` for invalid placement, `Error::Extension` for a
742 /// singular system, or a typed backend-source error.
743 fn solve_read(
744 &mut self,
745 _a: TensorRead<'_>,
746 _b: TensorRead<'_>,
747 ) -> tenferro_tensor::Result<Tensor> {
748 Err(tenferro_tensor::Error::unsupported(
749 "solve",
750 "backend does not accept tensor reads at this execution boundary",
751 ))
752 }
753
754 /// Solve into a caller-owned destination.
755 ///
756 /// The default preserves the ordinary read path and copies its result into
757 /// `out`. Backends with a native destination path may override this method,
758 /// but must validate the destination before the first write and preserve
759 /// the same shape, dtype, placement, aliasing, and error contracts.
760 ///
761 /// # Errors
762 ///
763 /// Returns `tenferro_tensor_core::ShapeMismatch` or
764 /// `tenferro_tensor_core::ValidationError::DTypeMismatch` for incompatible
765 /// destination metadata, `tenferro_tensor_core::ValidationError::InvalidArgument`
766 /// for aliasing or placement violations, `Error::Unsupported` when the
767 /// provider is unavailable, and `Error::Singular` for a singular system.
768 ///
769 /// # Examples
770 ///
771 /// ```rust
772 /// use tenferro_cpu::{with_cpu_exec_session, CpuBackend};
773 /// use tenferro_linalg::LinalgBackend;
774 /// use tenferro_tensor::{BackendSessionHost, Tensor, TensorRead, TensorWrite};
775 ///
776 /// let a = Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 0.0, 4.0])?;
777 /// let b = Tensor::from_vec_col_major(vec![2, 1], vec![4.0_f64, 8.0])?;
778 /// let mut out = Tensor::from_vec_col_major(vec![2, 1], vec![0.0_f64; 2])?;
779 /// let mut host = CpuBackend::new();
780 /// host.with_backend_session(|session| {
781 /// with_cpu_exec_session(session, |backend| {
782 /// backend.solve_read_into(
783 /// TensorRead::from_tensor(&a),
784 /// TensorRead::from_tensor(&b),
785 /// TensorWrite::from_tensor(&mut out),
786 /// )
787 /// })
788 /// .expect("CpuBackend must expose a CpuExecSession")
789 /// })?;
790 /// assert_eq!(out.as_slice::<f64>()?, &[2.0, 2.0]);
791 /// # Ok::<(), tenferro_tensor::Error>(())
792 /// ```
793 fn solve_read_into(
794 &mut self,
795 a: TensorRead<'_>,
796 b: TensorRead<'_>,
797 out: TensorWrite<'_>,
798 ) -> tenferro_tensor::Result<()> {
799 solve_read_into_default(self, a, b, out)
800 }
801
802 #[doc(hidden)]
803 fn lu_solve_prepared(
804 &mut self,
805 _a: &Tensor,
806 _packed_lu: &Tensor,
807 _pivots: &Tensor,
808 _b: &Tensor,
809 _transpose_a: bool,
810 _conjugate_a: bool,
811 ) -> tenferro_tensor::Result<Tensor> {
812 Err(tenferro_tensor::Error::unsupported(
813 "lu_solve_prepared",
814 format!(
815 "backend {} does not implement internal prepared LU solve",
816 std::any::type_name::<Self>()
817 ),
818 ))
819 }
820}
821
822pub(crate) fn solve_read_into_default<B: LinalgBackend + ?Sized>(
823 backend: &mut B,
824 a: TensorRead<'_>,
825 b: TensorRead<'_>,
826 out: TensorWrite<'_>,
827) -> tenferro_tensor::Result<()> {
828 validate_solve_read_into(&a, &b, &out)?;
829 let result = backend.solve_read(a, b)?;
830 backend.copy_read_into(TensorRead::from_tensor(&result), out)
831}
832
833pub(crate) fn validate_solve_read_into(
834 _a: &TensorRead<'_>,
835 b: &TensorRead<'_>,
836 out: &TensorWrite<'_>,
837) -> tenferro_tensor::Result<()> {
838 if b.shape() != out.shape() {
839 return Err(tenferro_tensor::Error::shape_mismatch(
840 "solve_read_into",
841 b.shape().to_vec(),
842 out.shape().to_vec(),
843 ));
844 }
845 if b.dtype() != out.dtype() {
846 return Err(tenferro_tensor::Error::dtype_mismatch(
847 "solve_read_into",
848 b.dtype(),
849 out.dtype(),
850 ));
851 }
852 if b.placement() != out.as_read().placement() {
853 return Err(tenferro_tensor::Error::invalid_argument(
854 "solve_read_into",
855 "out",
856 format!(
857 "destination placement {:?} does not match rhs placement {:?}",
858 out.as_read().placement(),
859 b.placement()
860 ),
861 ));
862 }
863 let inputs = [_a.clone(), b.clone()];
864 tenferro_tensor::backend::validate_read_into_destination("solve_read_into", &inputs, out)
865}