Skip to main content

tenferro_linalg/
eager_ext.rs

1use std::sync::Arc;
2
3use tenferro_ad::error::{Error, Result};
4use tenferro_ad::extension::apply_eager;
5use tenferro_ad::EagerTensor;
6
7use crate::extension::{
8    validate_derivative_eps, EighOptions, LinalgExtensionOp, LinalgOp, QrOptions, SvdOptions,
9};
10use crate::register_runtime;
11
12/// Linear algebra extension methods for [`EagerTensor`].
13pub trait EagerTensorLinalgExt {
14    fn svd(&self) -> Result<(EagerTensor, EagerTensor, EagerTensor)>;
15    fn svd_with_options(
16        &self,
17        options: SvdOptions,
18    ) -> Result<(EagerTensor, EagerTensor, EagerTensor)>;
19    fn qr(&self) -> Result<(EagerTensor, EagerTensor)>;
20    fn qr_with_options(&self, options: QrOptions) -> Result<(EagerTensor, EagerTensor)>;
21    fn lu(&self) -> Result<(EagerTensor, EagerTensor, EagerTensor, EagerTensor)>;
22    fn full_piv_lu(
23        &self,
24    ) -> Result<(
25        EagerTensor,
26        EagerTensor,
27        EagerTensor,
28        EagerTensor,
29        EagerTensor,
30    )>;
31    fn full_piv_lu_solve(&self, b: &EagerTensor) -> Result<EagerTensor>;
32    fn solve(&self, b: &EagerTensor) -> Result<EagerTensor>;
33    fn cholesky(&self) -> Result<EagerTensor>;
34    fn eigh(&self) -> Result<(EagerTensor, EagerTensor)>;
35    fn eigh_with_options(&self, options: EighOptions) -> Result<(EagerTensor, EagerTensor)>;
36    fn eig(&self) -> Result<(EagerTensor, EagerTensor)>;
37    fn triangular_solve(
38        &self,
39        b: &EagerTensor,
40        left_side: bool,
41        lower: bool,
42        transpose_a: bool,
43        unit_diagonal: bool,
44    ) -> Result<EagerTensor>;
45}
46
47impl EagerTensorLinalgExt for EagerTensor {
48    fn svd(&self) -> Result<(EagerTensor, EagerTensor, EagerTensor)> {
49        svd(self)
50    }
51
52    fn svd_with_options(
53        &self,
54        options: SvdOptions,
55    ) -> Result<(EagerTensor, EagerTensor, EagerTensor)> {
56        svd_with_options(self, options)
57    }
58
59    fn qr(&self) -> Result<(EagerTensor, EagerTensor)> {
60        qr(self)
61    }
62
63    fn qr_with_options(&self, options: QrOptions) -> Result<(EagerTensor, EagerTensor)> {
64        qr_with_options(self, options)
65    }
66
67    fn lu(&self) -> Result<(EagerTensor, EagerTensor, EagerTensor, EagerTensor)> {
68        lu(self)
69    }
70
71    fn full_piv_lu(
72        &self,
73    ) -> Result<(
74        EagerTensor,
75        EagerTensor,
76        EagerTensor,
77        EagerTensor,
78        EagerTensor,
79    )> {
80        full_piv_lu(self)
81    }
82
83    fn full_piv_lu_solve(&self, b: &EagerTensor) -> Result<EagerTensor> {
84        full_piv_lu_solve(self, b)
85    }
86
87    fn solve(&self, b: &EagerTensor) -> Result<EagerTensor> {
88        solve(self, b)
89    }
90
91    fn cholesky(&self) -> Result<EagerTensor> {
92        cholesky(self)
93    }
94
95    fn eigh(&self) -> Result<(EagerTensor, EagerTensor)> {
96        eigh(self)
97    }
98
99    fn eigh_with_options(&self, options: EighOptions) -> Result<(EagerTensor, EagerTensor)> {
100        eigh_with_options(self, options)
101    }
102
103    fn eig(&self) -> Result<(EagerTensor, EagerTensor)> {
104        eig(self)
105    }
106
107    fn triangular_solve(
108        &self,
109        b: &EagerTensor,
110        left_side: bool,
111        lower: bool,
112        transpose_a: bool,
113        unit_diagonal: bool,
114    ) -> Result<EagerTensor> {
115        triangular_solve(self, b, left_side, lower, transpose_a, unit_diagonal)
116    }
117}
118
119fn apply_linalg_eager(op: LinalgOp, inputs: &[&EagerTensor]) -> Result<Vec<EagerTensor>> {
120    if let Some(first) = inputs.first() {
121        first
122            .runtime()
123            .register_extension(register_runtime)
124            .map_err(|err| Error::Internal(err.to_string()))?;
125    }
126    apply_eager(Arc::new(LinalgExtensionOp::new(op)), inputs)
127}
128
129/// Singular value decomposition for eager tensors.
130///
131/// # Examples
132///
133/// ```rust
134/// use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
135/// use tenferro_linalg::EagerTensorLinalgExt;
136///
137/// let ctx = EagerRuntime::new();
138/// let a = EagerTensor::from_tensor_in(
139///     Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 2.0]).unwrap(),
140///     ctx,
141/// ).unwrap();
142/// let (_u, s, _vt) = a.svd()?;
143/// assert_eq!(s.shape(), &[2]);
144/// # Ok::<(), tenferro_ad::Error>(())
145/// ```
146pub fn svd(a: &EagerTensor) -> Result<(EagerTensor, EagerTensor, EagerTensor)> {
147    svd_with_options(a, SvdOptions::default())
148}
149
150/// Singular value decomposition for eager tensors with explicit options.
151///
152/// `derivative_eps` regularizes decomposition derivative formulas. It is not a
153/// backend SVD solver tolerance.
154///
155/// # Examples
156///
157/// ```rust
158/// use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
159/// use tenferro_linalg::{EagerTensorLinalgExt, SvdGauge, SvdOptions};
160///
161/// let ctx = EagerRuntime::new();
162/// let a = EagerTensor::from_tensor_in(
163///     Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 2.0]).unwrap(),
164///     ctx,
165/// ).unwrap();
166/// let options = SvdOptions::default()
167///     .gauge(SvdGauge::CanonicalPivot)
168///     .derivative_eps(1.0e-10);
169/// let (_u, s, _vt) = a.svd_with_options(options)?;
170/// assert_eq!(s.shape(), &[2]);
171/// # Ok::<(), tenferro_ad::Error>(())
172/// ```
173pub fn svd_with_options(
174    a: &EagerTensor,
175    options: SvdOptions,
176) -> Result<(EagerTensor, EagerTensor, EagerTensor)> {
177    validate_derivative_eps("svd_with_options", options.derivative_eps)?;
178    let mut outputs = apply_linalg_eager(
179        LinalgOp::Svd {
180            derivative_eps: options.derivative_eps,
181            gauge: options.gauge,
182        },
183        &[a],
184    )?
185    .into_iter();
186    match (
187        outputs.next(),
188        outputs.next(),
189        outputs.next(),
190        outputs.next(),
191    ) {
192        (Some(u), Some(s), Some(vt), None) => Ok((u, s, vt)),
193        _ => Err(Error::Internal(
194            "svd eager op returned an unexpected number of outputs".to_string(),
195        )),
196    }
197}
198
199/// QR decomposition for eager tensors.
200///
201/// # Examples
202///
203/// ```rust
204/// use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
205/// use tenferro_linalg::EagerTensorLinalgExt;
206///
207/// let ctx = EagerRuntime::new();
208/// let a = EagerTensor::from_tensor_in(
209///     Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 1.0]).unwrap(),
210///     ctx,
211/// ).unwrap();
212/// let (q, r) = a.qr()?;
213/// assert_eq!(q.shape(), &[2, 2]);
214/// assert_eq!(r.shape(), &[2, 2]);
215/// # Ok::<(), tenferro_ad::Error>(())
216/// ```
217pub fn qr(a: &EagerTensor) -> Result<(EagerTensor, EagerTensor)> {
218    qr_with_options(a, QrOptions::default())
219}
220
221/// QR decomposition for eager tensors with explicit options.
222///
223/// `gauge` controls optional sign or phase post-processing.
224///
225/// # Examples
226///
227/// ```rust
228/// use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
229/// use tenferro_linalg::{EagerTensorLinalgExt, QrGauge, QrOptions};
230///
231/// let ctx = EagerRuntime::new();
232/// let a = EagerTensor::from_tensor_in(
233///     Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 1.0]).unwrap(),
234///     ctx,
235/// ).unwrap();
236/// let (q, r) = a.qr_with_options(QrOptions::default().gauge(QrGauge::PositiveDiagonal))?;
237/// assert_eq!(q.shape(), &[2, 2]);
238/// assert_eq!(r.shape(), &[2, 2]);
239/// # Ok::<(), tenferro_ad::Error>(())
240/// ```
241pub fn qr_with_options(a: &EagerTensor, options: QrOptions) -> Result<(EagerTensor, EagerTensor)> {
242    two_outputs(
243        apply_linalg_eager(
244            LinalgOp::Qr {
245                gauge: options.gauge,
246            },
247            &[a],
248        )?,
249        "qr",
250    )
251}
252
253/// LU factorization for eager tensors.
254///
255/// # Examples
256///
257/// ```rust
258/// use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
259/// use tenferro_linalg::EagerTensorLinalgExt;
260///
261/// let ctx = EagerRuntime::new();
262/// let a = EagerTensor::from_tensor_in(
263///     Tensor::from_vec_col_major(vec![2, 2], vec![0.0_f64, 1.0, 1.0, 0.0]).unwrap(),
264///     ctx,
265/// ).unwrap();
266/// let (_p, l, u, parity) = a.lu()?;
267/// assert_eq!(l.shape(), &[2, 2]);
268/// assert_eq!(u.shape(), &[2, 2]);
269/// assert_eq!(parity.shape(), &[] as &[usize]);
270/// # Ok::<(), tenferro_ad::Error>(())
271/// ```
272pub fn lu(a: &EagerTensor) -> Result<(EagerTensor, EagerTensor, EagerTensor, EagerTensor)> {
273    let mut outputs = apply_linalg_eager(LinalgOp::Lu, &[a])?.into_iter();
274    match (
275        outputs.next(),
276        outputs.next(),
277        outputs.next(),
278        outputs.next(),
279        outputs.next(),
280    ) {
281        (Some(p), Some(l), Some(u), Some(parity), None) => Ok((p, l, u, parity)),
282        _ => Err(Error::Internal(
283            "lu eager op returned an unexpected number of outputs".to_string(),
284        )),
285    }
286}
287
288/// Complete-pivot LU factorization for eager tensors.
289///
290/// Returns `(P, L, U, Q, parity)` with reconstruction convention
291/// `A = P^T * L * U * Q`, equivalently `P * A * Q^T = L * U`. `parity` is a
292/// scalar real tensor containing `+1` or `-1`: `F32` for `F32`/`C32` inputs and
293/// `F64` for `F64`/`C64` inputs.
294///
295/// # Examples
296///
297/// ```rust
298/// use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
299/// use tenferro_linalg::EagerTensorLinalgExt;
300///
301/// let ctx = EagerRuntime::new();
302/// let a = EagerTensor::from_tensor_in(
303///     Tensor::from_vec_col_major(vec![2, 2], vec![0.0_f64, 2.0, 1.0, 3.0]).unwrap(),
304///     ctx,
305/// ).unwrap();
306/// let (p, _l, _u, q, parity) = a.full_piv_lu()?;
307/// assert_eq!(p.shape(), &[2, 2]);
308/// assert_eq!(q.shape(), &[2, 2]);
309/// assert_eq!(parity.shape(), &[] as &[usize]);
310/// # Ok::<(), tenferro_ad::Error>(())
311/// ```
312pub fn full_piv_lu(
313    a: &EagerTensor,
314) -> Result<(
315    EagerTensor,
316    EagerTensor,
317    EagerTensor,
318    EagerTensor,
319    EagerTensor,
320)> {
321    let mut outputs = apply_linalg_eager(LinalgOp::FullPivLu, &[a])?.into_iter();
322    match (
323        outputs.next(),
324        outputs.next(),
325        outputs.next(),
326        outputs.next(),
327        outputs.next(),
328        outputs.next(),
329    ) {
330        (Some(p), Some(l), Some(u), Some(q), Some(parity), None) => Ok((p, l, u, q, parity)),
331        _ => Err(Error::Internal(
332            "full_piv_lu eager op returned an unexpected number of outputs".to_string(),
333        )),
334    }
335}
336
337/// Solve a linear system using complete-pivot LU behavior.
338///
339/// # Examples
340///
341/// ```rust
342/// use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
343/// use tenferro_linalg::EagerTensorLinalgExt;
344///
345/// let ctx = EagerRuntime::new();
346/// let a = EagerTensor::from_tensor_in(
347///     Tensor::from_vec_col_major(vec![2, 2], vec![0.0_f64, 2.0, 1.0, 3.0]).unwrap(),
348///     ctx.clone(),
349/// ).unwrap();
350/// let b = EagerTensor::from_tensor_in(
351///     Tensor::from_vec_col_major(vec![2, 1], vec![-1.0_f64, 5.0]).unwrap(),
352///     ctx,
353/// ).unwrap();
354/// let x = a.full_piv_lu_solve(&b)?;
355/// assert_eq!(x.shape(), &[2, 1]);
356/// # Ok::<(), tenferro_ad::Error>(())
357/// ```
358pub fn full_piv_lu_solve(a: &EagerTensor, b: &EagerTensor) -> Result<EagerTensor> {
359    one_output(
360        apply_linalg_eager(LinalgOp::FullPivLuSolve { transpose_a: false }, &[a, b])?,
361        "full_piv_lu_solve",
362    )
363}
364
365/// Solve a linear system for eager tensors.
366///
367/// # Examples
368///
369/// ```rust
370/// use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
371/// use tenferro_linalg::EagerTensorLinalgExt;
372///
373/// let ctx = EagerRuntime::new();
374/// let a = EagerTensor::from_tensor_in(
375///     Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 0.0, 4.0]).unwrap(),
376///     ctx.clone(),
377/// ).unwrap();
378/// let b = EagerTensor::from_tensor_in(
379///     Tensor::from_vec_col_major(vec![2, 1], vec![4.0_f64, 8.0]).unwrap(),
380///     ctx,
381/// ).unwrap();
382/// let x = a.solve(&b)?;
383/// assert_eq!(x.shape(), &[2, 1]);
384/// # Ok::<(), tenferro_ad::Error>(())
385/// ```
386pub fn solve(a: &EagerTensor, b: &EagerTensor) -> Result<EagerTensor> {
387    let mut factor_outputs = apply_linalg_eager(LinalgOp::LuFactor, &[a])?.into_iter();
388    let (packed_lu, pivots) = match (
389        factor_outputs.next(),
390        factor_outputs.next(),
391        factor_outputs.next(),
392        factor_outputs.next(),
393    ) {
394        (Some(packed_lu), Some(pivots), Some(_parity), None) => (packed_lu, pivots),
395        _ => {
396            return Err(Error::Internal(
397                "lu_factor eager op returned an unexpected number of outputs".to_string(),
398            ));
399        }
400    };
401    one_output(
402        apply_linalg_eager(
403            LinalgOp::LuSolvePrepared {
404                transpose_a: false,
405                conjugate_a: false,
406            },
407            &[a, &packed_lu, &pivots, b],
408        )?,
409        "solve",
410    )
411}
412
413/// Cholesky factorization for eager tensors.
414///
415/// # Examples
416///
417/// ```rust
418/// use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
419/// use tenferro_linalg::EagerTensorLinalgExt;
420///
421/// let ctx = EagerRuntime::new();
422/// let a = EagerTensor::from_tensor_in(
423///     Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 1.0]).unwrap(),
424///     ctx,
425/// ).unwrap();
426/// let l = a.cholesky()?;
427/// assert_eq!(l.shape(), &[2, 2]);
428/// # Ok::<(), tenferro_ad::Error>(())
429/// ```
430pub fn cholesky(a: &EagerTensor) -> Result<EagerTensor> {
431    one_output(apply_linalg_eager(LinalgOp::Cholesky, &[a])?, "cholesky")
432}
433
434/// Hermitian eigenvalue decomposition for eager tensors.
435///
436/// # Examples
437///
438/// ```rust
439/// use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
440/// use tenferro_linalg::EagerTensorLinalgExt;
441///
442/// let ctx = EagerRuntime::new();
443/// let a = EagerTensor::from_tensor_in(
444///     Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 3.0]).unwrap(),
445///     ctx,
446/// ).unwrap();
447/// let (values, vectors) = a.eigh()?;
448/// assert_eq!(values.shape(), &[2]);
449/// assert_eq!(vectors.shape(), &[2, 2]);
450/// # Ok::<(), tenferro_ad::Error>(())
451/// ```
452pub fn eigh(a: &EagerTensor) -> Result<(EagerTensor, EagerTensor)> {
453    eigh_with_options(a, EighOptions::default())
454}
455
456/// Hermitian eigenvalue decomposition for eager tensors with explicit options.
457///
458/// `derivative_eps` regularizes derivative formulas for repeated or nearly
459/// repeated eigenvalues. It is not a backend eigensolver tolerance.
460///
461/// # Examples
462///
463/// ```rust
464/// use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
465/// use tenferro_linalg::{EagerTensorLinalgExt, EighGauge, EighOptions};
466///
467/// let ctx = EagerRuntime::new();
468/// let a = EagerTensor::from_tensor_in(
469///     Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 3.0]).unwrap(),
470///     ctx,
471/// ).unwrap();
472/// let (values, vectors) = a
473///     .eigh_with_options(
474///         EighOptions::default()
475///             .gauge(EighGauge::CanonicalPivot)
476///             .derivative_eps(1.0e-10),
477///     )?;
478/// assert_eq!(values.shape(), &[2]);
479/// assert_eq!(vectors.shape(), &[2, 2]);
480/// # Ok::<(), tenferro_ad::Error>(())
481/// ```
482pub fn eigh_with_options(
483    a: &EagerTensor,
484    options: EighOptions,
485) -> Result<(EagerTensor, EagerTensor)> {
486    validate_derivative_eps("eigh_with_options", options.derivative_eps)?;
487    two_outputs(
488        apply_linalg_eager(
489            LinalgOp::Eigh {
490                derivative_eps: options.derivative_eps,
491                gauge: options.gauge,
492            },
493            &[a],
494        )?,
495        "eigh",
496    )
497}
498
499/// General eigenvalue decomposition for eager tensors.
500///
501/// # Examples
502///
503/// ```rust
504/// use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
505/// use tenferro_linalg::EagerTensorLinalgExt;
506///
507/// let ctx = EagerRuntime::new();
508/// let a = EagerTensor::from_tensor_in(
509///     Tensor::from_vec_col_major(vec![2, 2], vec![1.0_f64, 0.0, 0.0, 3.0]).unwrap(),
510///     ctx,
511/// ).unwrap();
512/// let (values, vectors) = a.eig()?;
513/// assert_eq!(values.shape(), &[2]);
514/// assert_eq!(vectors.shape(), &[2, 2]);
515/// # Ok::<(), tenferro_ad::Error>(())
516/// ```
517pub fn eig(a: &EagerTensor) -> Result<(EagerTensor, EagerTensor)> {
518    two_outputs(
519        apply_linalg_eager(
520            LinalgOp::Eig {
521                input_dtype: a.dtype(),
522            },
523            &[a],
524        )?,
525        "eig",
526    )
527}
528
529/// Triangular solve for eager tensors.
530///
531/// # Examples
532///
533/// ```rust
534/// use tenferro_ad::{EagerRuntime, EagerTensor, Tensor};
535/// use tenferro_linalg::EagerTensorLinalgExt;
536///
537/// let ctx = EagerRuntime::new();
538/// let a = EagerTensor::from_tensor_in(
539///     Tensor::from_vec_col_major(vec![2, 2], vec![2.0_f64, 0.0, 1.0, 3.0]).unwrap(),
540///     ctx.clone(),
541/// ).unwrap();
542/// let b = EagerTensor::from_tensor_in(
543///     Tensor::from_vec_col_major(vec![2, 1], vec![2.0_f64, 7.0]).unwrap(),
544///     ctx,
545/// ).unwrap();
546/// let x = a.triangular_solve(&b, true, true, false, false)?;
547/// assert_eq!(x.shape(), &[2, 1]);
548/// # Ok::<(), tenferro_ad::Error>(())
549/// ```
550pub fn triangular_solve(
551    a: &EagerTensor,
552    b: &EagerTensor,
553    left_side: bool,
554    lower: bool,
555    transpose_a: bool,
556    unit_diagonal: bool,
557) -> Result<EagerTensor> {
558    one_output(
559        apply_linalg_eager(
560            LinalgOp::TriangularSolve {
561                left_side,
562                lower,
563                transpose_a,
564                unit_diagonal,
565            },
566            &[a, b],
567        )?,
568        "triangular_solve",
569    )
570}
571
572fn one_output(outputs: Vec<EagerTensor>, name: &str) -> Result<EagerTensor> {
573    let mut outputs = outputs.into_iter();
574    match (outputs.next(), outputs.next()) {
575        (Some(output), None) => Ok(output),
576        _ => Err(Error::Internal(format!(
577            "{name} eager op returned an unexpected number of outputs"
578        ))),
579    }
580}
581
582fn two_outputs(outputs: Vec<EagerTensor>, name: &str) -> Result<(EagerTensor, EagerTensor)> {
583    let mut outputs = outputs.into_iter();
584    match (outputs.next(), outputs.next(), outputs.next()) {
585        (Some(lhs), Some(rhs), None) => Ok((lhs, rhs)),
586        _ => Err(Error::Internal(format!(
587            "{name} eager op returned an unexpected number of outputs"
588        ))),
589    }
590}