Skip to main content

tenferro_linalg/cpu/
backend.rs

1use crate::backend::{unsupported_dtype, CompactQrResult, LinalgBackend};
2#[derive(Clone, Copy, Debug)]
3struct TriangularSolveOptions {
4    left_side: bool,
5    lower: bool,
6    transpose_a: bool,
7    unit_diagonal: bool,
8}
9use crate::extension::apply_qr_gauge;
10use crate::rank_revealing_qr::validate_rank_revealing_qr_options;
11
12use super::linalg;
13use tenferro_cpu::same_variant_pair;
14
15use num_complex::{Complex32, Complex64};
16use tcontract_exec::Exec;
17use tenferro_cpu::linalg_interop::BufferPool;
18use tenferro_cpu::CpuEngine;
19use tenferro_tensor::{
20    validate::validate_nonsingular_u, AllocationDomainId, DType, Error, HostAccessError,
21    MemoryKind, SharedTensorAllocationDomain, Tensor, TensorRead, TensorScalar, TensorStructural,
22    TensorView, TensorViewMut, TensorWrite, TypedTensor,
23};
24
25/// The Rust scalar type behind a preset variant name a macro received.
26macro_rules! preset_scalar {
27    (F32) => {
28        f32
29    };
30    (F64) => {
31        f64
32    };
33    (I32) => {
34        i32
35    };
36    (I64) => {
37        i64
38    };
39    (Bool) => {
40        bool
41    };
42    (C32) => {
43        num_complex::Complex32
44    };
45    (C64) => {
46        num_complex::Complex64
47    };
48}
49trait CpuBackendLinalgAffinityExt {
50    /// Every BLAS linalg entry passes here, including the direct caller-output
51    /// path that does not allocate a fresh result.
52    fn with_linalg_pool_checked<R>(
53        &mut self,
54        op: impl FnOnce(&mut Exec<'_>, &mut BufferPool) -> tenferro_tensor::Result<R>,
55    ) -> tenferro_tensor::Result<R>;
56}
57
58impl CpuBackendLinalgAffinityExt for CpuEngine {
59    fn with_linalg_pool_checked<R>(
60        &mut self,
61        op: impl FnOnce(&mut Exec<'_>, &mut BufferPool) -> tenferro_tensor::Result<R>,
62    ) -> tenferro_tensor::Result<R> {
63        self.with_linalg_pool(move |exec, buffers| {
64            #[cfg(feature = "blas")]
65            super::tlinalg_blas::reject_forced_batch_strategy(exec)?;
66            op(exec, buffers)
67        })
68    }
69}
70
71impl LinalgBackend for CpuEngine {
72    fn cholesky(&mut self, input: &Tensor) -> tenferro_tensor::Result<Tensor> {
73        let domain = self.shared_allocation_domain().cloned();
74        self.with_linalg_pool_checked(move |exec, buffers| {
75            if tensor_uses_backend_storage(input)
76                && let Some(domain) = domain.as_deref()
77            {
78                return managed_cholesky(exec, buffers, TensorRead::from_tensor(input), domain);
79            }
80            ensure_host_tensor("cholesky", input)?;
81            cholesky_entered(exec, buffers, input)
82        })
83    }
84
85    fn triangular_solve(
86        &mut self,
87        a: &Tensor,
88        b: &Tensor,
89        left_side: bool,
90        lower: bool,
91        transpose_a: bool,
92        unit_diagonal: bool,
93    ) -> tenferro_tensor::Result<Tensor> {
94        ensure_host_tensor("triangular_solve", a)?;
95        ensure_host_tensor("triangular_solve", b)?;
96        let options = TriangularSolveOptions {
97            left_side,
98            lower,
99            transpose_a,
100            unit_diagonal,
101        };
102        self.with_linalg_pool_checked(|exec, buffers| {
103            triangular_solve_entered(exec, buffers, a, b, options)
104        })
105    }
106
107    fn triangular_solve_read(
108        &mut self,
109        a: TensorRead<'_>,
110        b: TensorRead<'_>,
111        left_side: bool,
112        lower: bool,
113        transpose_a: bool,
114        unit_diagonal: bool,
115    ) -> tenferro_tensor::Result<Tensor> {
116        ensure_host_tensor_read("triangular_solve", &a)?;
117        ensure_host_tensor_read("triangular_solve", &b)?;
118        ensure_supported_linalg_dtypes("triangular_solve", a.dtype(), b.dtype())?;
119        let options = TriangularSolveOptions {
120            left_side,
121            lower,
122            transpose_a,
123            unit_diagonal,
124        };
125        self.with_linalg_pool_checked(move |exec, buffers| {
126            // Both operands must be faer-eligible for the direct path: `a`
127            // reaches faer as a strided `MatRef` and `b` is gathered straight
128            // into the destructible right-hand side. If either is ineligible,
129            // the scoped materializer below packs only what it must.
130            #[cfg(feature = "native")]
131            if faer_strided_read_ok(&a) && faer_rhs_read_ok(&b) {
132                return triangular_solve_faer_view_entered(
133                    exec,
134                    buffers,
135                    a.tensor_view(),
136                    b.tensor_view(),
137                    options,
138                );
139            }
140            tenferro_cpu::with_materialized_tensor_read(
141                exec,
142                buffers,
143                "triangular_solve",
144                a,
145                |a, buffers, exec| {
146                    tenferro_cpu::with_materialized_tensor_read(
147                        exec,
148                        buffers,
149                        "triangular_solve",
150                        b,
151                        |b, buffers, exec| triangular_solve_entered(exec, buffers, a, b, options),
152                    )
153                },
154            )
155        })
156    }
157
158    fn lu(&mut self, input: &Tensor) -> tenferro_tensor::Result<Vec<Tensor>> {
159        ensure_host_tensor("lu", input)?;
160        self.with_linalg_pool_checked(|exec, buffers| lu_entered(exec, buffers, input))
161    }
162
163    fn lu_factor(&mut self, input: &Tensor) -> tenferro_tensor::Result<Vec<Tensor>> {
164        ensure_host_tensor("lu_factor", input)?;
165        {
166            #[cfg(feature = "native")]
167            {
168                #[cfg(feature = "native")]
169                {
170                    self.with_linalg_pool_checked(|exec, buffers| match input.dtype() {
171                        DType::F32 => {
172                            let t = input
173                                .as_typed::<f32>()
174                                .ok_or_else(|| unsupported_dtype("lu_factor", input.dtype()))?;
175                            linalg::faer::lu_factor(exec, buffers, t).map(|(lu, pivots, parity)| {
176                                vec![
177                                    Tensor::from_typed::<f32>(lu),
178                                    Tensor::from_typed::<i32>(pivots),
179                                    Tensor::from_typed::<f32>(parity),
180                                ]
181                            })
182                        }
183                        DType::F64 => {
184                            let t = input
185                                .as_typed::<f64>()
186                                .ok_or_else(|| unsupported_dtype("lu_factor", input.dtype()))?;
187                            linalg::faer::lu_factor(exec, buffers, t).map(|(lu, pivots, parity)| {
188                                vec![
189                                    Tensor::from_typed::<f64>(lu),
190                                    Tensor::from_typed::<i32>(pivots),
191                                    Tensor::from_typed::<f64>(parity),
192                                ]
193                            })
194                        }
195                        DType::C32 => {
196                            let t = input
197                                .as_typed::<Complex32>()
198                                .ok_or_else(|| unsupported_dtype("lu_factor", input.dtype()))?;
199                            linalg::faer::lu_factor(exec, buffers, t).map(|(lu, pivots, parity)| {
200                                vec![
201                                    Tensor::from_typed::<Complex32>(lu),
202                                    Tensor::from_typed::<i32>(pivots),
203                                    Tensor::from_typed::<Complex32>(parity),
204                                ]
205                            })
206                        }
207                        DType::C64 => {
208                            let t = input
209                                .as_typed::<Complex64>()
210                                .ok_or_else(|| unsupported_dtype("lu_factor", input.dtype()))?;
211                            linalg::faer::lu_factor(exec, buffers, t).map(|(lu, pivots, parity)| {
212                                vec![
213                                    Tensor::from_typed::<Complex64>(lu),
214                                    Tensor::from_typed::<i32>(pivots),
215                                    Tensor::from_typed::<Complex64>(parity),
216                                ]
217                            })
218                        }
219                        _ => Err(unsupported_dtype("lu_factor", input.dtype())),
220                    })
221                }
222            }
223            #[cfg(feature = "blas")]
224            {
225                #[cfg(feature = "blas")]
226                {
227                    self.with_linalg_pool_checked(|exec, buffers| match input.dtype() {
228                        DType::F32 => {
229                            let t = input
230                                .as_typed::<f32>()
231                                .ok_or_else(|| unsupported_dtype("lu_factor", input.dtype()))?;
232                            linalg::blas::lu_factor(exec, buffers, t).map(|(lu, pivots, parity)| {
233                                vec![
234                                    Tensor::from_typed::<f32>(lu),
235                                    Tensor::from_typed::<i32>(pivots),
236                                    Tensor::from_typed::<f32>(parity),
237                                ]
238                            })
239                        }
240                        DType::F64 => {
241                            let t = input
242                                .as_typed::<f64>()
243                                .ok_or_else(|| unsupported_dtype("lu_factor", input.dtype()))?;
244                            linalg::blas::lu_factor(exec, buffers, t).map(|(lu, pivots, parity)| {
245                                vec![
246                                    Tensor::from_typed::<f64>(lu),
247                                    Tensor::from_typed::<i32>(pivots),
248                                    Tensor::from_typed::<f64>(parity),
249                                ]
250                            })
251                        }
252                        DType::C32 => {
253                            let t = input
254                                .as_typed::<Complex32>()
255                                .ok_or_else(|| unsupported_dtype("lu_factor", input.dtype()))?;
256                            linalg::blas::lu_factor(exec, buffers, t).map(|(lu, pivots, parity)| {
257                                vec![
258                                    Tensor::from_typed::<Complex32>(lu),
259                                    Tensor::from_typed::<i32>(pivots),
260                                    Tensor::from_typed::<Complex32>(parity),
261                                ]
262                            })
263                        }
264                        DType::C64 => {
265                            let t = input
266                                .as_typed::<Complex64>()
267                                .ok_or_else(|| unsupported_dtype("lu_factor", input.dtype()))?;
268                            linalg::blas::lu_factor(exec, buffers, t).map(|(lu, pivots, parity)| {
269                                vec![
270                                    Tensor::from_typed::<Complex64>(lu),
271                                    Tensor::from_typed::<i32>(pivots),
272                                    Tensor::from_typed::<Complex64>(parity),
273                                ]
274                            })
275                        }
276                        _ => Err(unsupported_dtype("lu_factor", input.dtype())),
277                    })
278                }
279            }
280        }
281    }
282
283    fn full_piv_lu(&mut self, input: &Tensor) -> tenferro_tensor::Result<Vec<Tensor>> {
284        ensure_host_tensor("full_piv_lu", input)?;
285        self.with_linalg_pool_checked(|exec, buffers| full_piv_lu_entered(exec, buffers, input))
286    }
287
288    fn full_piv_lu_solve(
289        &mut self,
290        a: &Tensor,
291        b: &Tensor,
292        transpose_a: bool,
293    ) -> tenferro_tensor::Result<Tensor> {
294        ensure_host_tensor("full_piv_lu_solve", a)?;
295        ensure_host_tensor("full_piv_lu_solve", b)?;
296        ensure_supported_linalg_pair("full_piv_lu_solve", a, b)?;
297        if has_zero_dim(a.shape()) || has_zero_dim(b.shape()) {
298            return self.with_linalg_pool_checked(|_, _| zeros_like_tensor(b));
299        }
300
301        let (rhs, restore_shape) = if let Some(matrix_rhs_shape) = batched_vector_rhs_shape(a, b) {
302            (
303                self.reshape_read(TensorRead::from_tensor(b), &matrix_rhs_shape)?,
304                Some(b.shape().to_vec()),
305            )
306        } else {
307            (b.duplicate()?, None)
308        };
309
310        let result = {
311            #[cfg(feature = "native")]
312            {
313                #[cfg(feature = "native")]
314                {
315                    self.with_linalg_pool_checked(|exec, buffers| {
316                        same_variant_pair!(
317                            a,
318                            &rhs,
319                            |a, b| {
320                                linalg::faer::full_piv_lu_solve(exec, buffers, a, b, transpose_a)
321                            },
322                            unsupported_pair("full_piv_lu_solve", a, &rhs)
323                        )
324                    })
325                }
326            }
327            #[cfg(feature = "blas")]
328            {
329                #[cfg(feature = "blas")]
330                {
331                    self.with_linalg_pool_checked(|_, buffers| {
332                        same_variant_pair!(
333                            a,
334                            &rhs,
335                            |a, b| { linalg::blas::full_piv_lu_solve(buffers, a, b, transpose_a) },
336                            unsupported_pair("full_piv_lu_solve", a, &rhs)
337                        )
338                    })
339                }
340            }
341        }?;
342
343        if let Some(shape) = restore_shape {
344            self.reshape_read(TensorRead::from_tensor(&result), &shape)
345        } else {
346            Ok(result)
347        }
348    }
349
350    fn svd(&mut self, input: &Tensor) -> tenferro_tensor::Result<Vec<Tensor>> {
351        ensure_host_tensor("svd", input)?;
352        self.with_linalg_pool_checked(|exec, buffers| svd_entered(exec, buffers, input))
353    }
354
355    fn svd_full(&mut self, input: &Tensor) -> tenferro_tensor::Result<Vec<Tensor>> {
356        ensure_host_tensor("svd_full", input)?;
357        self.with_linalg_pool_checked(|exec, buffers| svd_full_entered(exec, buffers, input))
358    }
359
360    fn svd_full_read(&mut self, input: TensorRead<'_>) -> tenferro_tensor::Result<Vec<Tensor>> {
361        ensure_host_tensor_read("svd_full", &input)?;
362        ensure_supported_linalg_dtype("svd_full", input.dtype())?;
363        self.with_linalg_pool_checked(move |exec, buffers| {
364            #[cfg(feature = "native")]
365            if faer_strided_read_ok(&input) {
366                return svd_full_faer_view_entered(exec, buffers, input.tensor_view());
367            }
368            tenferro_cpu::with_materialized_tensor_read(
369                exec,
370                buffers,
371                "svd_full",
372                input,
373                |input, buffers, exec| svd_full_entered(exec, buffers, input),
374            )
375        })
376    }
377
378    fn svd_values(&mut self, input: &Tensor) -> tenferro_tensor::Result<Tensor> {
379        ensure_host_tensor("svd_values", input)?;
380        self.with_linalg_pool_checked(|exec, buffers| svd_values_entered(exec, buffers, input))
381    }
382
383    fn svd_read(&mut self, input: TensorRead<'_>) -> tenferro_tensor::Result<Vec<Tensor>> {
384        ensure_host_tensor_read("svd", &input)?;
385        ensure_supported_linalg_dtype("svd", input.dtype())?;
386        self.with_linalg_pool_checked(move |exec, buffers| {
387            #[cfg(feature = "native")]
388            if faer_strided_read_ok(&input) {
389                return svd_faer_view_entered(exec, buffers, input.tensor_view());
390            }
391            tenferro_cpu::with_materialized_tensor_read(
392                exec,
393                buffers,
394                "svd",
395                input,
396                |input, buffers, exec| svd_entered(exec, buffers, input),
397            )
398        })
399    }
400
401    fn svd_values_read(&mut self, input: TensorRead<'_>) -> tenferro_tensor::Result<Tensor> {
402        ensure_host_tensor_read("svd_values", &input)?;
403        ensure_supported_linalg_dtype("svd_values", input.dtype())?;
404        self.with_linalg_pool_checked(move |exec, buffers| {
405            #[cfg(feature = "native")]
406            if faer_strided_read_ok(&input) {
407                return svd_values_faer_view_entered(exec, buffers, input.tensor_view());
408            }
409            tenferro_cpu::with_materialized_tensor_read(
410                exec,
411                buffers,
412                "svd_values",
413                input,
414                |input, buffers, exec| svd_values_entered(exec, buffers, input),
415            )
416        })
417    }
418
419    fn qr(&mut self, input: &Tensor) -> tenferro_tensor::Result<Vec<Tensor>> {
420        ensure_host_tensor("qr", input)?;
421        self.with_linalg_pool_checked(|exec, buffers| qr_entered(exec, buffers, input))
422    }
423
424    fn qr_read(&mut self, input: TensorRead<'_>) -> tenferro_tensor::Result<Vec<Tensor>> {
425        ensure_host_tensor_read("qr", &input)?;
426        ensure_supported_linalg_dtype("qr", input.dtype())?;
427        self.with_linalg_pool_checked(move |exec, buffers| {
428            #[cfg(feature = "native")]
429            if faer_strided_read_ok(&input) {
430                return qr_faer_view_entered(exec, buffers, input.tensor_view());
431            }
432            tenferro_cpu::with_materialized_tensor_read(
433                exec,
434                buffers,
435                "qr",
436                input,
437                |input, buffers, exec| qr_entered(exec, buffers, input),
438            )
439        })
440    }
441
442    fn rank_revealing_qr(
443        &mut self,
444        input: &Tensor,
445        options: crate::RankRevealingQrOptions,
446    ) -> tenferro_tensor::Result<Vec<Tensor>> {
447        validate_rank_revealing_qr_options("rank_revealing_qr", options)?;
448        ensure_host_tensor("rank_revealing_qr", input)?;
449        ensure_supported_linalg_dtype("rank_revealing_qr", input.dtype())?;
450        self.with_linalg_pool_checked(|exec, buffers| {
451            rank_revealing_qr_entered(exec, buffers, input, options)
452        })
453    }
454
455    fn rank_revealing_qr_read(
456        &mut self,
457        input: TensorRead<'_>,
458        options: crate::RankRevealingQrOptions,
459    ) -> tenferro_tensor::Result<Vec<Tensor>> {
460        validate_rank_revealing_qr_options("rank_revealing_qr", options)?;
461        ensure_host_tensor_read("rank_revealing_qr", &input)?;
462        ensure_supported_linalg_dtype("rank_revealing_qr", input.dtype())?;
463        self.with_linalg_pool_checked(move |exec, buffers| {
464            #[cfg(feature = "native")]
465            if faer_strided_read_ok(&input) {
466                return rank_revealing_qr_faer_view_entered(
467                    exec,
468                    buffers,
469                    input.tensor_view(),
470                    options,
471                );
472            }
473            tenferro_cpu::with_materialized_tensor_read(
474                exec,
475                buffers,
476                "rank_revealing_qr",
477                input,
478                |input, buffers, exec| rank_revealing_qr_entered(exec, buffers, input, options),
479            )
480        })
481    }
482
483    fn householder_qr(&mut self, input: &Tensor) -> tenferro_tensor::Result<CompactQrResult> {
484        ensure_host_tensor("householder_qr", input)?;
485        self.with_linalg_pool_checked(|exec, buffers| householder_qr_entered(exec, buffers, input))
486    }
487
488    fn householder_qr_from_factors(
489        &mut self,
490        q: &Tensor,
491        r: &Tensor,
492    ) -> tenferro_tensor::Result<CompactQrResult> {
493        ensure_host_tensor("householder_qr_from_factors", q)?;
494        ensure_host_tensor("householder_qr_from_factors", r)?;
495        self.with_linalg_pool_checked(|exec, buffers| {
496            householder_qr_from_factors_entered(exec, buffers, q, r)
497        })
498    }
499
500    fn householder_qr_append(
501        &mut self,
502        packed: &Tensor,
503        coeff: &Tensor,
504        block: &Tensor,
505    ) -> tenferro_tensor::Result<CompactQrResult> {
506        ensure_host_tensor("householder_qr_append", packed)?;
507        ensure_host_tensor("householder_qr_append", coeff)?;
508        ensure_host_tensor("householder_qr_append", block)?;
509        self.with_linalg_pool_checked(|exec, buffers| {
510            householder_qr_append_entered(exec, buffers, packed, coeff, block)
511        })
512    }
513
514    fn householder_qr_r(
515        &mut self,
516        packed: &Tensor,
517        coeff: &Tensor,
518        options: crate::QrOptions,
519    ) -> tenferro_tensor::Result<Tensor> {
520        ensure_host_tensor("householder_qr_r", packed)?;
521        ensure_host_tensor("householder_qr_r", coeff)?;
522        self.with_linalg_pool_checked(|exec, buffers| {
523            householder_qr_r_entered(exec, buffers, packed, coeff, options)
524        })
525    }
526
527    fn householder_qr_q_columns(
528        &mut self,
529        packed: &Tensor,
530        coeff: &Tensor,
531        columns: std::ops::Range<usize>,
532        options: crate::QrOptions,
533    ) -> tenferro_tensor::Result<Tensor> {
534        ensure_host_tensor("householder_qr_q_columns", packed)?;
535        ensure_host_tensor("householder_qr_q_columns", coeff)?;
536        self.with_linalg_pool_checked(|exec, buffers| {
537            householder_qr_q_columns_entered(exec, buffers, packed, coeff, columns, options)
538        })
539    }
540
541    fn eigh(&mut self, input: &Tensor) -> tenferro_tensor::Result<Vec<Tensor>> {
542        ensure_host_tensor("eigh", input)?;
543        self.with_linalg_pool_checked(|exec, buffers| eigh_entered(exec, buffers, input))
544    }
545
546    fn eigh_read(&mut self, input: TensorRead<'_>) -> tenferro_tensor::Result<Vec<Tensor>> {
547        ensure_host_tensor_read("eigh", &input)?;
548        ensure_supported_linalg_dtype("eigh", input.dtype())?;
549        self.with_linalg_pool_checked(move |exec, buffers| {
550            #[cfg(feature = "native")]
551            if faer_strided_read_ok(&input) {
552                return eigh_faer_view_entered(exec, buffers, input.tensor_view());
553            }
554            tenferro_cpu::with_materialized_tensor_read(
555                exec,
556                buffers,
557                "eigh",
558                input,
559                |input, buffers, exec| eigh_entered(exec, buffers, input),
560            )
561        })
562    }
563
564    fn eigh_values_read(&mut self, input: TensorRead<'_>) -> tenferro_tensor::Result<Tensor> {
565        ensure_host_tensor_read("eigh_values", &input)?;
566        ensure_supported_linalg_dtype("eigh_values", input.dtype())?;
567        self.with_linalg_pool_checked(move |exec, buffers| {
568            #[cfg(feature = "native")]
569            if faer_strided_read_ok(&input) {
570                return eigh_values_faer_view_entered(exec, buffers, input.tensor_view());
571            }
572            tenferro_cpu::with_materialized_tensor_read(
573                exec,
574                buffers,
575                "eigh_values",
576                input,
577                |input, buffers, exec| eigh_values_entered(exec, buffers, input),
578            )
579        })
580    }
581
582    fn cholesky_read(&mut self, input: TensorRead<'_>) -> tenferro_tensor::Result<Tensor> {
583        let domain = self.shared_allocation_domain().cloned();
584        self.with_linalg_pool_checked(move |exec, buffers| {
585            if let Some(domain) = domain.as_deref()
586                && input.backend_family().is_some()
587            {
588                return managed_cholesky(exec, buffers, input, domain);
589            }
590            ensure_host_tensor_read("cholesky", &input)?;
591            ensure_supported_linalg_dtype("cholesky", input.dtype())?;
592            #[cfg(feature = "native")]
593            if faer_strided_read_ok(&input) {
594                return cholesky_faer_view_entered(exec, buffers, input.tensor_view());
595            }
596            tenferro_cpu::with_materialized_tensor_read(
597                exec,
598                buffers,
599                "cholesky",
600                input,
601                |input, buffers, exec| cholesky_entered(exec, buffers, input),
602            )
603        })
604    }
605
606    fn lu_read(&mut self, input: TensorRead<'_>) -> tenferro_tensor::Result<Vec<Tensor>> {
607        ensure_host_tensor_read("lu", &input)?;
608        ensure_supported_linalg_dtype("lu", input.dtype())?;
609        self.with_linalg_pool_checked(move |exec, buffers| {
610            #[cfg(feature = "native")]
611            if faer_strided_read_ok(&input) {
612                return lu_faer_view_entered(exec, buffers, input.tensor_view());
613            }
614            tenferro_cpu::with_materialized_tensor_read(
615                exec,
616                buffers,
617                "lu",
618                input,
619                |input, buffers, exec| lu_entered(exec, buffers, input),
620            )
621        })
622    }
623
624    fn full_piv_lu_read(&mut self, input: TensorRead<'_>) -> tenferro_tensor::Result<Vec<Tensor>> {
625        ensure_host_tensor_read("full_piv_lu", &input)?;
626        ensure_supported_linalg_dtype("full_piv_lu", input.dtype())?;
627        self.with_linalg_pool_checked(move |exec, buffers| {
628            #[cfg(feature = "native")]
629            if faer_strided_read_ok(&input) {
630                return full_piv_lu_faer_view_entered(exec, buffers, input.tensor_view());
631            }
632            tenferro_cpu::with_materialized_tensor_read(
633                exec,
634                buffers,
635                "full_piv_lu",
636                input,
637                |input, buffers, exec| full_piv_lu_entered(exec, buffers, input),
638            )
639        })
640    }
641
642    fn eig_read(&mut self, input: TensorRead<'_>) -> tenferro_tensor::Result<Vec<Tensor>> {
643        ensure_host_tensor_read("eig", &input)?;
644        ensure_supported_linalg_dtype("eig", input.dtype())?;
645        self.with_linalg_pool_checked(move |exec, buffers| {
646            #[cfg(feature = "native")]
647            if faer_strided_read_ok(&input) {
648                return linalg::faer::eig_view(exec, buffers, input.tensor_view());
649            }
650            tenferro_cpu::with_materialized_tensor_read(
651                exec,
652                buffers,
653                "eig",
654                input,
655                |input, buffers, exec| eig_entered(exec, buffers, input),
656            )
657        })
658    }
659
660    fn eig_values_read(&mut self, input: TensorRead<'_>) -> tenferro_tensor::Result<Tensor> {
661        ensure_host_tensor_read("eig_values", &input)?;
662        ensure_supported_linalg_dtype("eig_values", input.dtype())?;
663        self.with_linalg_pool_checked(move |exec, buffers| {
664            #[cfg(feature = "native")]
665            if faer_strided_read_ok(&input) {
666                return linalg::faer::eig_values_view(exec, buffers, input.tensor_view());
667            }
668            tenferro_cpu::with_materialized_tensor_read(
669                exec,
670                buffers,
671                "eig_values",
672                input,
673                |input, buffers, exec| eig_values_entered(exec, buffers, input),
674            )
675        })
676    }
677
678    fn eigh_values(&mut self, input: &Tensor) -> tenferro_tensor::Result<Tensor> {
679        ensure_host_tensor("eigh_values", input)?;
680        self.with_linalg_pool_checked(|exec, buffers| eigh_values_entered(exec, buffers, input))
681    }
682
683    fn eig(&mut self, input: &Tensor) -> tenferro_tensor::Result<Vec<Tensor>> {
684        ensure_host_tensor("eig", input)?;
685        ensure_supported_linalg_dtype("eig", input.dtype())?;
686        self.with_linalg_pool_checked(|exec, buffers| eig_entered(exec, buffers, input))
687    }
688
689    fn eig_values(&mut self, input: &Tensor) -> tenferro_tensor::Result<Tensor> {
690        ensure_host_tensor("eig_values", input)?;
691        ensure_supported_linalg_dtype("eig_values", input.dtype())?;
692        self.with_linalg_pool_checked(|exec, buffers| eig_values_entered(exec, buffers, input))
693    }
694
695    fn lu_solve_prepared(
696        &mut self,
697        a: &Tensor,
698        packed_lu: &Tensor,
699        pivots: &Tensor,
700        b: &Tensor,
701        transpose_a: bool,
702        conjugate_a: bool,
703    ) -> tenferro_tensor::Result<Tensor> {
704        const OP: &str = "lu_solve_prepared";
705
706        ensure_host_tensor(OP, a)?;
707        ensure_host_tensor(OP, packed_lu)?;
708        ensure_host_tensor(OP, pivots)?;
709        ensure_host_tensor(OP, b)?;
710        ensure_supported_linalg_pair(OP, a, b)?;
711        ensure_supported_linalg_pair(OP, a, packed_lu)?;
712        if !matches!(pivots.dtype(), DType::I32) {
713            return Err(Error::dtype_mismatch(OP, DType::I32, pivots.dtype()));
714        }
715        if has_zero_dim(a.shape()) || has_zero_dim(b.shape()) {
716            return self.with_linalg_pool_checked(|_, _| zeros_like_tensor(b));
717        }
718
719        validate_lu_solve_prepared_shapes(
720            packed_lu.shape(),
721            pivots.shape(),
722            &packed_lu::rhs_matrix_shape(a.shape(), b.shape()),
723        )?;
724        validate_nonsingular_u(packed_lu)?;
725        // One pooled RHS copy becomes the output; the provider kernel applies
726        // the stored pivots and both triangular solves per matrix in place.
727        self.with_linalg_pool_checked(|exec, buffers| {
728            packed_lu::lu_solve_prepared_entered(
729                exec,
730                buffers,
731                a,
732                packed_lu,
733                pivots,
734                b,
735                transpose_a,
736                conjugate_a,
737            )
738        })
739    }
740
741    fn lu_factor_solve(&mut self, a: &Tensor, b: &Tensor) -> tenferro_tensor::Result<Vec<Tensor>> {
742        const OP: &str = "lu_factor_solve";
743
744        ensure_host_tensor(OP, a)?;
745        ensure_host_tensor(OP, b)?;
746        ensure_supported_linalg_pair(OP, a, b)?;
747        self.with_linalg_pool_checked(|exec, buffers| {
748            packed_lu::lu_factor_solve_entered(exec, buffers, a, b)
749        })
750    }
751
752    fn solve(&mut self, a: &Tensor, b: &Tensor) -> tenferro_tensor::Result<Tensor> {
753        ensure_host_tensor("solve", a)?;
754        ensure_host_tensor("solve", b)?;
755        ensure_supported_linalg_pair("solve", a, b)?;
756        self.with_linalg_pool_checked(|exec, buffers| solve_entered(exec, buffers, a, b))
757    }
758
759    fn solve_read(
760        &mut self,
761        a: TensorRead<'_>,
762        b: TensorRead<'_>,
763    ) -> tenferro_tensor::Result<Tensor> {
764        ensure_host_tensor_read("solve", &a)?;
765        ensure_host_tensor_read("solve", &b)?;
766        ensure_supported_linalg_dtypes("solve", a.dtype(), b.dtype())?;
767        let direct = !has_zero_dim(a.shape())
768            && !has_zero_dim(b.shape())
769            && solve_shape_direct_eligible(a.shape(), b.shape());
770        self.with_linalg_pool_checked(move |exec, buffers| {
771            if direct {
772                solve_from_views_entered(exec, buffers, a.tensor_view(), b.tensor_view())
773            } else {
774                tenferro_cpu::with_materialized_tensor_read(
775                    exec,
776                    buffers,
777                    "solve",
778                    a,
779                    |a, buffers, exec| {
780                        tenferro_cpu::with_materialized_tensor_read(
781                            exec,
782                            buffers,
783                            "solve",
784                            b,
785                            |b, buffers, exec| solve_entered(exec, buffers, a, b),
786                        )
787                    },
788                )
789            }
790        })
791    }
792
793    fn solve_read_into(
794        &mut self,
795        a: TensorRead<'_>,
796        b: TensorRead<'_>,
797        out: TensorWrite<'_>,
798    ) -> tenferro_tensor::Result<()> {
799        crate::backend::validate_solve_read_into(&a, &b, &out)?;
800        ensure_host_tensor_read("solve_read_into", &a)?;
801        ensure_host_tensor_read("solve_read_into", &b)?;
802        ensure_host_tensor_read("solve_read_into", &out.as_read())?;
803        ensure_supported_linalg_dtypes("solve_read_into", a.dtype(), b.dtype())?;
804
805        if has_zero_dim(a.shape())
806            || has_zero_dim(b.shape())
807            || !solve_read_into_direct_eligible(&a, &b, &out)
808        {
809            return crate::backend::solve_read_into_default(self, a, b, out);
810        }
811
812        let a = a.tensor_view();
813        let b = b.tensor_view();
814        self.with_linalg_pool_checked(move |exec, buffers| {
815            solve_read_into_entered(exec, buffers, a, b, out)
816        })
817    }
818}
819
820fn solve_read_into_direct_eligible(
821    a: &TensorRead<'_>,
822    b: &TensorRead<'_>,
823    out: &TensorWrite<'_>,
824) -> bool {
825    if !solve_shape_direct_eligible(a.shape(), b.shape()) {
826        return false;
827    }
828    let out = out.as_read();
829    if out.backend_family().is_some() || out.shape() != b.shape() {
830        return false;
831    }
832    let Ok(strides) = out.strides() else {
833        return false;
834    };
835    match out.shape() {
836        [_] => strides == [1],
837        [rows, cols] => {
838            strides.first().copied() == Some(1)
839                && strides.get(1).copied().is_some_and(|stride| {
840                    stride >= isize::try_from(*rows).unwrap_or(isize::MAX)
841                        && (*cols <= 1 || stride > 0)
842                })
843        }
844        _ => false,
845    }
846}
847
848fn solve_shape_direct_eligible(a_shape: &[usize], b_shape: &[usize]) -> bool {
849    a_shape.len() == 2 && matches!(b_shape.len(), 1 | 2)
850}
851
852fn tensor_uses_backend_storage(input: &Tensor) -> bool {
853    input.is_backend_buffer()
854}
855
856fn managed_cholesky(
857    exec: &mut Exec<'_>,
858    buffers: &mut BufferPool,
859    input: TensorRead<'_>,
860    domain: &dyn SharedTensorAllocationDomain,
861) -> tenferro_tensor::Result<Tensor> {
862    let dtype = input.dtype();
863    ensure_supported_linalg_dtype("cholesky", dtype)?;
864    match input.tensor_view() {
865        TensorView::F32(view) => managed_cholesky_typed(exec, buffers, &view, domain),
866        TensorView::F64(view) => managed_cholesky_typed(exec, buffers, &view, domain),
867        TensorView::C32(view) => managed_cholesky_typed(exec, buffers, &view, domain),
868        TensorView::C64(view) => managed_cholesky_typed(exec, buffers, &view, domain),
869        _ => Err(unsupported_dtype("cholesky", dtype)),
870    }
871}
872
873trait ManagedCholeskyScalar: Copy + Send + Sync + TensorScalar + 'static {
874    const DTYPE: DType;
875
876    /// The factor as a pooled tensor: the staging copy into the managed domain borrows it, and its
877    /// storage returns to the session pool when the copy is done.
878    fn factor(
879        exec: &mut Exec<'_>,
880        buffers: &mut BufferPool,
881        data: &[Self],
882        n: usize,
883    ) -> tenferro_tensor::Result<TypedTensor<Self>>;
884
885    fn take_output(output: Tensor) -> tenferro_tensor::Result<TypedTensor<Self>>;
886    fn wrap(output: TypedTensor<Self>) -> Tensor;
887}
888
889macro_rules! impl_managed_cholesky_scalar {
890    ($scalar:ty, $dtype:ident, $variant:ident) => {
891        impl ManagedCholeskyScalar for $scalar {
892            const DTYPE: DType = DType::$dtype;
893
894            fn factor(
895                exec: &mut Exec<'_>,
896                buffers: &mut BufferPool,
897                data: &[Self],
898                n: usize,
899            ) -> tenferro_tensor::Result<TypedTensor<Self>> {
900                {
901                    #[cfg(feature = "native")]
902                    {
903                        #[cfg(feature = "native")]
904                        {
905                            linalg::faer::cholesky_compact_data(exec, buffers, data, n)
906                        }
907                    }
908                    #[cfg(feature = "blas")]
909                    {
910                        #[cfg(feature = "blas")]
911                        {
912                            let _ = &exec;
913                            linalg::blas::cholesky_compact_data(buffers, data, n)
914                        }
915                    }
916                }
917            }
918
919            fn take_output(output: Tensor) -> tenferro_tensor::Result<TypedTensor<Self>> {
920                let Ok(output) = output.into_typed::<preset_scalar!($variant)>() else {
921                    return Err(tenferro_tensor::Error::runtime_state(
922                        "cholesky",
923                        concat!(
924                            "shared allocation owner returned a non-",
925                            stringify!($variant),
926                            " output"
927                        ),
928                    ));
929                };
930                Ok(output)
931            }
932
933            fn wrap(output: TypedTensor<Self>) -> Tensor {
934                Tensor::from_typed::<preset_scalar!($variant)>(output)
935            }
936        }
937    };
938}
939
940impl_managed_cholesky_scalar!(f32, F32, F32);
941impl_managed_cholesky_scalar!(f64, F64, F64);
942impl_managed_cholesky_scalar!(Complex32, C32, C32);
943impl_managed_cholesky_scalar!(Complex64, C64, C64);
944
945fn managed_cholesky_typed<T>(
946    exec: &mut Exec<'_>,
947    buffers: &mut BufferPool,
948    input: &tenferro_tensor::TypedTensorView<'_, T>,
949    domain: &dyn SharedTensorAllocationDomain,
950) -> tenferro_tensor::Result<Tensor>
951where
952    T: ManagedCholeskyScalar,
953{
954    let n = validate_managed_cholesky_input(input, domain.id())?;
955    // The factor keeps its pool recycler: the copy below is its only use, and the staging storage
956    // returns to the session pool immediately afterwards.
957    let values = if n == 0 {
958        None
959    } else {
960        Some(input.with_host_read(|read| T::factor(exec, buffers, read, n))??)
961    };
962    let mut typed = T::take_output(domain.allocate(T::DTYPE, &[n, n])?)?;
963    let staged: &[T] = match &values {
964        Some(values) => values.host_data()?,
965        None => &[],
966    };
967    write_managed_cholesky_output(&mut typed, domain.id(), staged)?;
968    Ok(T::wrap(typed))
969}
970
971fn validate_managed_cholesky_input<T: Copy + Send + Sync + TensorScalar + 'static>(
972    input: &tenferro_tensor::TypedTensorView<'_, T>,
973    expected_domain: AllocationDomainId,
974) -> tenferro_tensor::Result<usize> {
975    if input.rank() != 2 {
976        return Err(tenferro_tensor::Error::rank_mismatch(
977            "cholesky",
978            2,
979            input.rank(),
980        ));
981    }
982    let rows = input.shape()[0];
983    let cols = input.shape()[1];
984    if rows != cols {
985        return Err(tenferro_tensor::Error::shape_mismatch(
986            "cholesky",
987            vec![rows],
988            vec![cols],
989        ));
990    }
991    if !input.is_col_major_contiguous()? {
992        return Err(tenferro_tensor::Error::invalid_argument(
993            "cholesky",
994            "input layout",
995            "managed rank-2 Cholesky requires a compact column-major descriptor",
996        ));
997    }
998    rows.checked_mul(cols).ok_or_else(|| {
999        tenferro_tensor::Error::invalid_argument(
1000            "cholesky",
1001            "input shape",
1002            "matrix element count overflows usize",
1003        )
1004    })?;
1005    if input.placement().memory_kind != MemoryKind::Managed {
1006        return Err(tenferro_tensor::Error::host_access(
1007            "cholesky",
1008            HostAccessError::Unsupported {
1009                backend: if matches!(
1010                    input.placement().memory_kind,
1011                    MemoryKind::PinnedHost | MemoryKind::UnpinnedHost
1012                ) {
1013                    "host"
1014                } else {
1015                    "backend"
1016                },
1017            },
1018        ));
1019    }
1020    match input.allocation_domain() {
1021        Some(actual) if actual == expected_domain => {}
1022        Some(actual) => {
1023            return Err(tenferro_tensor::Error::host_access(
1024                "cholesky",
1025                HostAccessError::ForeignDomain {
1026                    expected: expected_domain,
1027                    actual,
1028                },
1029            ));
1030        }
1031        None => {
1032            return Err(tenferro_tensor::Error::host_access(
1033                "cholesky",
1034                HostAccessError::Unsupported { backend: "backend" },
1035            ));
1036        }
1037    }
1038    Ok(rows)
1039}
1040
1041fn write_managed_cholesky_output<T: TensorScalar + Copy + Send + Sync + 'static>(
1042    output: &mut TypedTensor<T>,
1043    expected_domain: AllocationDomainId,
1044    values: &[T],
1045) -> tenferro_tensor::Result<()> {
1046    if output.allocation_domain() != Some(expected_domain)
1047        || output.placement().memory_kind != MemoryKind::Managed
1048    {
1049        return Err(tenferro_tensor::Error::runtime_state(
1050            "cholesky",
1051            "shared allocation owner returned an output outside its managed domain",
1052        ));
1053    }
1054    if let Some(buffer) = output.backend_buffer_mut() {
1055        let mut write = buffer
1056            .map_write()
1057            .map_err(|source| tenferro_tensor::Error::host_access("cholesky", source))?;
1058        return write
1059            .copy_from_slice(values)
1060            .map_err(|source| tenferro_tensor::Error::host_access("cholesky", source));
1061    }
1062    output.with_host_write(|write| {
1063        if write.len() != values.len() {
1064            return Err(tenferro_tensor::Error::runtime_state(
1065                "cholesky",
1066                "shared allocation owner returned an output with the wrong length",
1067            ));
1068        }
1069        write.copy_from_slice(values);
1070        Ok(())
1071    })?
1072}
1073
1074/// The typed host tensor behind `input`, or this file's standard refusal.
1075///
1076/// Callers reach this from a match on `input.dtype()`, so `None` here means the tag
1077/// table and the runtime dtype disagree rather than a caller mistake.
1078fn typed_host<'a, T: TensorScalar>(
1079    input: &'a Tensor,
1080    op: &'static str,
1081) -> tenferro_tensor::Result<&'a TypedTensor<T>> {
1082    input
1083        .as_typed::<T>()
1084        .ok_or_else(|| unsupported_dtype(op, input.dtype()))
1085}
1086
1087/// The typed operands behind a same-dtype pair, or this entry point's refusal.
1088fn q_columns_operands<'a, T: TensorScalar>(
1089    packed: &'a Tensor,
1090    coeff: &'a Tensor,
1091) -> Result<TypedOperandPair<'a, T>, tenferro_tensor::Error> {
1092    let p = packed.as_typed::<T>().ok_or_else(|| {
1093        Error::dtype_mismatch("householder_qr_q_columns", packed.dtype(), coeff.dtype())
1094    })?;
1095    let c = coeff.as_typed::<T>().ok_or_else(|| {
1096        Error::dtype_mismatch("householder_qr_q_columns", packed.dtype(), coeff.dtype())
1097    })?;
1098    Ok((p, c))
1099}
1100
1101/// A pair of borrowed typed operands that share one scalar.
1102type TypedOperandPair<'a, T> = (
1103    &'a TypedTensor<T, tenferro_tensor::DynRank>,
1104    &'a TypedTensor<T, tenferro_tensor::DynRank>,
1105);
1106
1107/// The typed operands behind a same-dtype pair, or the refusal this entry point reports.
1108///
1109/// The result type is spelled out because this module's imports resolve `TypedTensor` to its
1110/// two-parameter form and `Result` to the standard one at file scope.
1111fn qr_import_operands<'a, T: TensorScalar>(
1112    q: &'a Tensor,
1113    r: &'a Tensor,
1114) -> Result<TypedOperandPair<'a, T>, tenferro_tensor::Error> {
1115    let q_t = q
1116        .as_typed::<T>()
1117        .ok_or_else(|| unsupported_dtype("householder_qr_from_factors", q.dtype()))?;
1118    let r_t = r
1119        .as_typed::<T>()
1120        .ok_or_else(|| unsupported_dtype("householder_qr_from_factors", r.dtype()))?;
1121    Ok((q_t, r_t))
1122}
1123
1124/// A triple of borrowed typed operands that share one scalar.
1125type TypedOperandTriple<'a, T> = (
1126    &'a TypedTensor<T, tenferro_tensor::DynRank>,
1127    &'a TypedTensor<T, tenferro_tensor::DynRank>,
1128    &'a TypedTensor<T, tenferro_tensor::DynRank>,
1129);
1130
1131/// The typed operands behind a same-dtype triple, or the refusal this entry point reports.
1132fn append_operands<'a, T: TensorScalar>(
1133    packed: &'a Tensor,
1134    coeff: &'a Tensor,
1135    block: &'a Tensor,
1136) -> Result<TypedOperandTriple<'a, T>, tenferro_tensor::Error> {
1137    let mismatch = || unsupported_dtype("householder_qr_append", packed.dtype());
1138    let packed_t = packed.as_typed::<T>().ok_or_else(mismatch)?;
1139    let coeff_t = coeff.as_typed::<T>().ok_or_else(mismatch)?;
1140    let block_t = block.as_typed::<T>().ok_or_else(mismatch)?;
1141    Ok((packed_t, coeff_t, block_t))
1142}
1143
1144fn ensure_host_tensor(op: &'static str, input: &Tensor) -> tenferro_tensor::Result<()> {
1145    match input.dtype() {
1146        // A caller-owned payload is not a linalg operand.
1147        DType::External(type_id) => Err(unsupported_dtype(op, DType::External(type_id))),
1148        DType::F32 => ensure_host_typed_tensor(op, typed_host::<f32>(input, op)?),
1149        DType::F64 => ensure_host_typed_tensor(op, typed_host::<f64>(input, op)?),
1150        DType::I32 => ensure_host_typed_tensor(op, typed_host::<i32>(input, op)?),
1151        DType::I64 => ensure_host_typed_tensor(op, typed_host::<i64>(input, op)?),
1152        DType::Bool => ensure_host_typed_tensor(op, typed_host::<bool>(input, op)?),
1153        DType::C32 => ensure_host_typed_tensor(op, typed_host::<Complex32>(input, op)?),
1154        DType::C64 => ensure_host_typed_tensor(op, typed_host::<Complex64>(input, op)?),
1155    }
1156}
1157
1158fn ensure_host_tensor_read(
1159    op: &'static str,
1160    input: &TensorRead<'_>,
1161) -> tenferro_tensor::Result<()> {
1162    match input {
1163        TensorRead::Tensor(tensor) => ensure_host_tensor(op, tensor),
1164        TensorRead::View(view) => ensure_host_tensor_view(op, view),
1165    }
1166}
1167
1168fn ensure_host_tensor_view(
1169    op: &'static str,
1170    input: &TensorView<'_>,
1171) -> tenferro_tensor::Result<()> {
1172    let is_backend_buffer = match input {
1173        TensorView::F32(view) => view.backend_buffer().is_some(),
1174        TensorView::F64(view) => view.backend_buffer().is_some(),
1175        TensorView::I32(view) => view.backend_buffer().is_some(),
1176        TensorView::I64(view) => view.backend_buffer().is_some(),
1177        TensorView::Bool(view) => view.backend_buffer().is_some(),
1178        TensorView::C32(view) => view.backend_buffer().is_some(),
1179        TensorView::C64(view) => view.backend_buffer().is_some(),
1180    };
1181    if is_backend_buffer {
1182        return Err(Error::runtime_state(
1183            op,
1184            "CPU linalg backend received a backend buffer; download the tensor to host before CPU execution",
1185        ));
1186    }
1187    Ok(())
1188}
1189
1190fn triangular_solve_entered(
1191    exec: &mut Exec<'_>,
1192    buffers: &mut BufferPool,
1193    a: &Tensor,
1194    b: &Tensor,
1195    options: TriangularSolveOptions,
1196) -> tenferro_tensor::Result<Tensor> {
1197    {
1198        #[cfg(feature = "native")]
1199        {
1200            #[cfg(feature = "native")]
1201            {
1202                same_variant_pair!(
1203                    a,
1204                    b,
1205                    |a, b| {
1206                        linalg::faer::triangular_solve(
1207                            exec,
1208                            buffers,
1209                            a,
1210                            b,
1211                            options.left_side,
1212                            options.lower,
1213                            options.transpose_a,
1214                            options.unit_diagonal,
1215                        )
1216                    },
1217                    unsupported_pair("triangular_solve", a, b)
1218                )
1219            }
1220        }
1221        #[cfg(feature = "blas")]
1222        {
1223            #[cfg(feature = "blas")]
1224            {
1225                let _ = &exec;
1226                same_variant_pair!(
1227                    a,
1228                    b,
1229                    |a, b| {
1230                        linalg::blas::triangular_solve(
1231                            buffers,
1232                            a,
1233                            b,
1234                            options.left_side,
1235                            options.lower,
1236                            options.transpose_a,
1237                            options.unit_diagonal,
1238                        )
1239                    },
1240                    unsupported_pair("triangular_solve", a, b)
1241                )
1242            }
1243        }
1244    }
1245}
1246
1247fn solve_entered(
1248    exec: &mut Exec<'_>,
1249    buffers: &mut BufferPool,
1250    a: &Tensor,
1251    b: &Tensor,
1252) -> tenferro_tensor::Result<Tensor> {
1253    if has_zero_dim(a.shape()) || has_zero_dim(b.shape()) {
1254        return zeros_like_tensor(b);
1255    }
1256
1257    let (rhs, restore_shape) = if let Some(matrix_rhs_shape) = batched_vector_rhs_shape(a, b) {
1258        (
1259            tenferro_cpu::reshape_tensor(exec, buffers, b, &matrix_rhs_shape)?,
1260            Some(b.shape().to_vec()),
1261        )
1262    } else {
1263        (b.duplicate()?, None)
1264    };
1265
1266    let result = {
1267        #[cfg(feature = "native")]
1268        {
1269            #[cfg(feature = "native")]
1270            {
1271                same_variant_pair!(
1272                    a,
1273                    &rhs,
1274                    |a, b| { linalg::faer::solve(exec, buffers, a, b, false) },
1275                    unsupported_pair("solve", a, &rhs)
1276                )
1277            }
1278        }
1279        #[cfg(feature = "blas")]
1280        {
1281            #[cfg(feature = "blas")]
1282            {
1283                let _ = &exec;
1284                same_variant_pair!(
1285                    a,
1286                    &rhs,
1287                    |a, b| { linalg::blas::solve(buffers, a, b, false) },
1288                    unsupported_pair("solve", a, &rhs)
1289                )
1290            }
1291        }
1292    }?;
1293
1294    if let Some(shape) = restore_shape {
1295        tenferro_cpu::reshape_tensor(exec, buffers, &result, &shape)
1296    } else {
1297        Ok(result)
1298    }
1299}
1300
1301fn solve_from_views_entered(
1302    exec: &mut Exec<'_>,
1303    buffers: &mut BufferPool,
1304    a: TensorView<'_>,
1305    b: TensorView<'_>,
1306) -> tenferro_tensor::Result<Tensor> {
1307    {
1308        #[cfg(feature = "native")]
1309        {
1310            #[cfg(feature = "native")]
1311            {
1312                match (a, b) {
1313                    (TensorView::F32(a), TensorView::F32(b)) => {
1314                        linalg::faer::solve_from_views(exec, buffers, a, b, false)
1315                            .map(Tensor::from_typed::<f32>)
1316                    }
1317                    (TensorView::F64(a), TensorView::F64(b)) => {
1318                        linalg::faer::solve_from_views(exec, buffers, a, b, false)
1319                            .map(Tensor::from_typed::<f64>)
1320                    }
1321                    (TensorView::C32(a), TensorView::C32(b)) => {
1322                        linalg::faer::solve_from_views(exec, buffers, a, b, false)
1323                            .map(Tensor::from_typed::<Complex32>)
1324                    }
1325                    (TensorView::C64(a), TensorView::C64(b)) => {
1326                        linalg::faer::solve_from_views(exec, buffers, a, b, false)
1327                            .map(Tensor::from_typed::<Complex64>)
1328                    }
1329                    _ => Err(Error::invalid_argument(
1330                        "solve",
1331                        "inputs",
1332                        "solve inputs must have the same dtype",
1333                    )),
1334                }
1335            }
1336        }
1337        #[cfg(feature = "blas")]
1338        {
1339            #[cfg(feature = "blas")]
1340            {
1341                let _ = &exec;
1342                match (a, b) {
1343                    (TensorView::F32(a), TensorView::F32(b)) => {
1344                        linalg::blas::solve_from_views(buffers, a, b, false)
1345                            .map(Tensor::from_typed::<f32>)
1346                    }
1347                    (TensorView::F64(a), TensorView::F64(b)) => {
1348                        linalg::blas::solve_from_views(buffers, a, b, false)
1349                            .map(Tensor::from_typed::<f64>)
1350                    }
1351                    (TensorView::C32(a), TensorView::C32(b)) => {
1352                        linalg::blas::solve_from_views(buffers, a, b, false)
1353                            .map(Tensor::from_typed::<Complex32>)
1354                    }
1355                    (TensorView::C64(a), TensorView::C64(b)) => {
1356                        linalg::blas::solve_from_views(buffers, a, b, false)
1357                            .map(Tensor::from_typed::<Complex64>)
1358                    }
1359                    _ => Err(Error::invalid_argument(
1360                        "solve",
1361                        "inputs",
1362                        "solve inputs must have the same dtype",
1363                    )),
1364                }
1365            }
1366        }
1367    }
1368}
1369
1370fn solve_read_into_entered(
1371    exec: &mut Exec<'_>,
1372    buffers: &mut BufferPool,
1373    a: TensorView<'_>,
1374    b: TensorView<'_>,
1375    out: TensorWrite<'_>,
1376) -> tenferro_tensor::Result<()> {
1377    let out = tensor_write_view(out);
1378    {
1379        #[cfg(feature = "native")]
1380        {
1381            #[cfg(feature = "native")]
1382            {
1383                match (a, b, out) {
1384                    (TensorView::F32(a), TensorView::F32(b), TensorViewMut::F32(mut out)) => {
1385                        linalg::faer::solve_into(exec, buffers, a, b, &mut out, false)
1386                    }
1387                    (TensorView::F64(a), TensorView::F64(b), TensorViewMut::F64(mut out)) => {
1388                        linalg::faer::solve_into(exec, buffers, a, b, &mut out, false)
1389                    }
1390                    (TensorView::C32(a), TensorView::C32(b), TensorViewMut::C32(mut out)) => {
1391                        linalg::faer::solve_into(exec, buffers, a, b, &mut out, false)
1392                    }
1393                    (TensorView::C64(a), TensorView::C64(b), TensorViewMut::C64(mut out)) => {
1394                        linalg::faer::solve_into(exec, buffers, a, b, &mut out, false)
1395                    }
1396                    _ => Err(Error::invalid_argument(
1397                        "solve_read_into",
1398                        "out",
1399                        "destination dtype does not match the solve inputs",
1400                    )),
1401                }
1402            }
1403        }
1404        #[cfg(feature = "blas")]
1405        {
1406            #[cfg(feature = "blas")]
1407            {
1408                let _ = &exec;
1409                match (a, b, out) {
1410                    (TensorView::F32(a), TensorView::F32(b), TensorViewMut::F32(mut out)) => {
1411                        linalg::blas::solve_into(buffers, a, b, &mut out, false)
1412                    }
1413                    (TensorView::F64(a), TensorView::F64(b), TensorViewMut::F64(mut out)) => {
1414                        linalg::blas::solve_into(buffers, a, b, &mut out, false)
1415                    }
1416                    (TensorView::C32(a), TensorView::C32(b), TensorViewMut::C32(mut out)) => {
1417                        linalg::blas::solve_into(buffers, a, b, &mut out, false)
1418                    }
1419                    (TensorView::C64(a), TensorView::C64(b), TensorViewMut::C64(mut out)) => {
1420                        linalg::blas::solve_into(buffers, a, b, &mut out, false)
1421                    }
1422                    _ => Err(Error::invalid_argument(
1423                        "solve_read_into",
1424                        "out",
1425                        "destination dtype does not match the solve inputs",
1426                    )),
1427                }
1428            }
1429        }
1430    }
1431}
1432
1433fn tensor_write_view(out: TensorWrite<'_>) -> TensorViewMut<'_> {
1434    match out {
1435        TensorWrite::Tensor(tensor) => match tensor.dtype() {
1436            DType::F32 => TensorViewMut::F32(write_view_operand::<f32>(tensor).as_view_mut()),
1437            DType::F64 => TensorViewMut::F64(write_view_operand::<f64>(tensor).as_view_mut()),
1438            DType::I32 => TensorViewMut::I32(write_view_operand::<i32>(tensor).as_view_mut()),
1439            DType::I64 => TensorViewMut::I64(write_view_operand::<i64>(tensor).as_view_mut()),
1440            DType::Bool => TensorViewMut::Bool(write_view_operand::<bool>(tensor).as_view_mut()),
1441            DType::C32 => TensorViewMut::C32(write_view_operand::<Complex32>(tensor).as_view_mut()),
1442            DType::C64 => TensorViewMut::C64(write_view_operand::<Complex64>(tensor).as_view_mut()),
1443            // INVARIANT: linalg rejects an externally defined dtype before adapting a
1444            // write target, and `TensorViewMut` has no externally defined variant.
1445            DType::External(_) => unreachable!("linalg validates its input dtypes first"),
1446        },
1447        TensorWrite::View(view) => view,
1448    }
1449}
1450
1451/// The typed write target behind a tensor, or the unwind site this module documents.
1452///
1453/// INVARIANT: linalg rejects a dtype it cannot adapt before it borrows a write target, and
1454/// `TensorViewMut` has no externally defined variant, so this cannot be reached.
1455fn write_view_operand<T: TensorScalar>(tensor: &mut Tensor) -> &mut TypedTensor<T> {
1456    tensor
1457        .as_typed_mut::<T>()
1458        .unwrap_or_else(|| unreachable!("linalg validates its input dtypes first"))
1459}
1460
1461fn cholesky_entered(
1462    exec: &mut Exec<'_>,
1463    buffers: &mut BufferPool,
1464    input: &Tensor,
1465) -> tenferro_tensor::Result<Tensor> {
1466    {
1467        #[cfg(feature = "native")]
1468        {
1469            #[cfg(feature = "native")]
1470            {
1471                match input.dtype() {
1472                    DType::F32 => linalg::faer::cholesky(
1473                        exec,
1474                        buffers,
1475                        input
1476                            .as_typed::<f32>()
1477                            .ok_or_else(|| unsupported_dtype("cholesky", input.dtype()))?,
1478                    )
1479                    .map(Tensor::from_typed::<f32>),
1480                    DType::F64 => linalg::faer::cholesky(
1481                        exec,
1482                        buffers,
1483                        input
1484                            .as_typed::<f64>()
1485                            .ok_or_else(|| unsupported_dtype("cholesky", input.dtype()))?,
1486                    )
1487                    .map(Tensor::from_typed::<f64>),
1488                    DType::C32 => linalg::faer::cholesky(
1489                        exec,
1490                        buffers,
1491                        input
1492                            .as_typed::<Complex32>()
1493                            .ok_or_else(|| unsupported_dtype("cholesky", input.dtype()))?,
1494                    )
1495                    .map(Tensor::from_typed::<Complex32>),
1496                    DType::C64 => linalg::faer::cholesky(
1497                        exec,
1498                        buffers,
1499                        input
1500                            .as_typed::<Complex64>()
1501                            .ok_or_else(|| unsupported_dtype("cholesky", input.dtype()))?,
1502                    )
1503                    .map(Tensor::from_typed::<Complex64>),
1504                    _ => Err(unsupported_dtype("cholesky", input.dtype())),
1505                }
1506            }
1507        }
1508        #[cfg(feature = "blas")]
1509        {
1510            #[cfg(feature = "blas")]
1511            {
1512                let _ = &exec;
1513                match input.dtype() {
1514                    DType::F32 => linalg::blas::cholesky(
1515                        buffers,
1516                        input
1517                            .as_typed::<f32>()
1518                            .ok_or_else(|| unsupported_dtype("cholesky", input.dtype()))?,
1519                    )
1520                    .map(Tensor::from_typed::<f32>),
1521                    DType::F64 => linalg::blas::cholesky(
1522                        buffers,
1523                        input
1524                            .as_typed::<f64>()
1525                            .ok_or_else(|| unsupported_dtype("cholesky", input.dtype()))?,
1526                    )
1527                    .map(Tensor::from_typed::<f64>),
1528                    DType::C32 => linalg::blas::cholesky(
1529                        buffers,
1530                        input
1531                            .as_typed::<Complex32>()
1532                            .ok_or_else(|| unsupported_dtype("cholesky", input.dtype()))?,
1533                    )
1534                    .map(Tensor::from_typed::<Complex32>),
1535                    DType::C64 => linalg::blas::cholesky(
1536                        buffers,
1537                        input
1538                            .as_typed::<Complex64>()
1539                            .ok_or_else(|| unsupported_dtype("cholesky", input.dtype()))?,
1540                    )
1541                    .map(Tensor::from_typed::<Complex64>),
1542                    _ => Err(unsupported_dtype("cholesky", input.dtype())),
1543                }
1544            }
1545        }
1546    }
1547}
1548
1549fn lu_entered(
1550    exec: &mut Exec<'_>,
1551    buffers: &mut BufferPool,
1552    input: &Tensor,
1553) -> tenferro_tensor::Result<Vec<Tensor>> {
1554    {
1555        #[cfg(feature = "native")]
1556        {
1557            #[cfg(feature = "native")]
1558            {
1559                match input.dtype() {
1560                    DType::F32 => linalg::faer::lu(
1561                        exec,
1562                        buffers,
1563                        input
1564                            .as_typed::<f32>()
1565                            .ok_or_else(|| unsupported_dtype("lu", input.dtype()))?,
1566                    )
1567                    .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f32>).collect()),
1568                    DType::F64 => linalg::faer::lu(
1569                        exec,
1570                        buffers,
1571                        input
1572                            .as_typed::<f64>()
1573                            .ok_or_else(|| unsupported_dtype("lu", input.dtype()))?,
1574                    )
1575                    .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f64>).collect()),
1576                    DType::C32 => linalg::faer::lu(
1577                        exec,
1578                        buffers,
1579                        input
1580                            .as_typed::<Complex32>()
1581                            .ok_or_else(|| unsupported_dtype("lu", input.dtype()))?,
1582                    )
1583                    .map(|outputs| {
1584                        outputs
1585                            .into_iter()
1586                            .map(Tensor::from_typed::<Complex32>)
1587                            .collect()
1588                    }),
1589                    DType::C64 => linalg::faer::lu(
1590                        exec,
1591                        buffers,
1592                        input
1593                            .as_typed::<Complex64>()
1594                            .ok_or_else(|| unsupported_dtype("lu", input.dtype()))?,
1595                    )
1596                    .map(|outputs| {
1597                        outputs
1598                            .into_iter()
1599                            .map(Tensor::from_typed::<Complex64>)
1600                            .collect()
1601                    }),
1602                    _ => Err(unsupported_dtype("lu", input.dtype())),
1603                }
1604            }
1605        }
1606        #[cfg(feature = "blas")]
1607        {
1608            #[cfg(feature = "blas")]
1609            {
1610                let _ = &exec;
1611                match input.dtype() {
1612                    DType::F32 => linalg::blas::lu(
1613                        buffers,
1614                        input
1615                            .as_typed::<f32>()
1616                            .ok_or_else(|| unsupported_dtype("lu", input.dtype()))?,
1617                    )
1618                    .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f32>).collect()),
1619                    DType::F64 => linalg::blas::lu(
1620                        buffers,
1621                        input
1622                            .as_typed::<f64>()
1623                            .ok_or_else(|| unsupported_dtype("lu", input.dtype()))?,
1624                    )
1625                    .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f64>).collect()),
1626                    DType::C32 => linalg::blas::lu(
1627                        buffers,
1628                        input
1629                            .as_typed::<Complex32>()
1630                            .ok_or_else(|| unsupported_dtype("lu", input.dtype()))?,
1631                    )
1632                    .map(|outputs| {
1633                        outputs
1634                            .into_iter()
1635                            .map(Tensor::from_typed::<Complex32>)
1636                            .collect()
1637                    }),
1638                    DType::C64 => linalg::blas::lu(
1639                        buffers,
1640                        input
1641                            .as_typed::<Complex64>()
1642                            .ok_or_else(|| unsupported_dtype("lu", input.dtype()))?,
1643                    )
1644                    .map(|outputs| {
1645                        outputs
1646                            .into_iter()
1647                            .map(Tensor::from_typed::<Complex64>)
1648                            .collect()
1649                    }),
1650                    _ => Err(unsupported_dtype("lu", input.dtype())),
1651                }
1652            }
1653        }
1654    }
1655}
1656
1657fn full_piv_lu_entered(
1658    exec: &mut Exec<'_>,
1659    buffers: &mut BufferPool,
1660    input: &Tensor,
1661) -> tenferro_tensor::Result<Vec<Tensor>> {
1662    {
1663        #[cfg(feature = "native")]
1664        {
1665            #[cfg(feature = "native")]
1666            {
1667                match input.dtype() {
1668                    DType::F32 => linalg::faer::full_piv_lu(
1669                        exec,
1670                        buffers,
1671                        input
1672                            .as_typed::<f32>()
1673                            .ok_or_else(|| unsupported_dtype("full_piv_lu", input.dtype()))?,
1674                    )
1675                    .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f32>).collect()),
1676                    DType::F64 => linalg::faer::full_piv_lu(
1677                        exec,
1678                        buffers,
1679                        input
1680                            .as_typed::<f64>()
1681                            .ok_or_else(|| unsupported_dtype("full_piv_lu", input.dtype()))?,
1682                    )
1683                    .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f64>).collect()),
1684                    DType::C32 => linalg::faer::full_piv_lu(
1685                        exec,
1686                        buffers,
1687                        input
1688                            .as_typed::<Complex32>()
1689                            .ok_or_else(|| unsupported_dtype("full_piv_lu", input.dtype()))?,
1690                    )
1691                    .and_then(full_piv_lu_c32_outputs_to_public_tensors),
1692                    DType::C64 => linalg::faer::full_piv_lu(
1693                        exec,
1694                        buffers,
1695                        input
1696                            .as_typed::<Complex64>()
1697                            .ok_or_else(|| unsupported_dtype("full_piv_lu", input.dtype()))?,
1698                    )
1699                    .and_then(full_piv_lu_c64_outputs_to_public_tensors),
1700                    _ => Err(unsupported_dtype("full_piv_lu", input.dtype())),
1701                }
1702            }
1703        }
1704        #[cfg(feature = "blas")]
1705        {
1706            #[cfg(feature = "blas")]
1707            {
1708                let _ = &exec;
1709                match input.dtype() {
1710                    DType::F32 => linalg::blas::full_piv_lu(
1711                        buffers,
1712                        input
1713                            .as_typed::<f32>()
1714                            .ok_or_else(|| unsupported_dtype("full_piv_lu", input.dtype()))?,
1715                    )
1716                    .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f32>).collect()),
1717                    DType::F64 => linalg::blas::full_piv_lu(
1718                        buffers,
1719                        input
1720                            .as_typed::<f64>()
1721                            .ok_or_else(|| unsupported_dtype("full_piv_lu", input.dtype()))?,
1722                    )
1723                    .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f64>).collect()),
1724                    DType::C32 => linalg::blas::full_piv_lu(
1725                        buffers,
1726                        input
1727                            .as_typed::<Complex32>()
1728                            .ok_or_else(|| unsupported_dtype("full_piv_lu", input.dtype()))?,
1729                    )
1730                    .and_then(full_piv_lu_c32_outputs_to_public_tensors),
1731                    DType::C64 => linalg::blas::full_piv_lu(
1732                        buffers,
1733                        input
1734                            .as_typed::<Complex64>()
1735                            .ok_or_else(|| unsupported_dtype("full_piv_lu", input.dtype()))?,
1736                    )
1737                    .and_then(full_piv_lu_c64_outputs_to_public_tensors),
1738                    _ => Err(unsupported_dtype("full_piv_lu", input.dtype())),
1739                }
1740            }
1741        }
1742    }
1743}
1744
1745fn svd_entered(
1746    exec: &mut Exec<'_>,
1747    buffers: &mut BufferPool,
1748    input: &Tensor,
1749) -> tenferro_tensor::Result<Vec<Tensor>> {
1750    {
1751        #[cfg(feature = "native")]
1752        {
1753            #[cfg(feature = "native")]
1754            {
1755                match input.dtype() {
1756                    DType::F32 => linalg::faer::svd(
1757                        exec,
1758                        buffers,
1759                        input
1760                            .as_typed::<f32>()
1761                            .ok_or_else(|| unsupported_dtype("svd", input.dtype()))?,
1762                    )
1763                    .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f32>).collect()),
1764                    DType::F64 => linalg::faer::svd(
1765                        exec,
1766                        buffers,
1767                        input
1768                            .as_typed::<f64>()
1769                            .ok_or_else(|| unsupported_dtype("svd", input.dtype()))?,
1770                    )
1771                    .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f64>).collect()),
1772                    DType::C32 => linalg::faer::svd(
1773                        exec,
1774                        buffers,
1775                        input
1776                            .as_typed::<Complex32>()
1777                            .ok_or_else(|| unsupported_dtype("svd", input.dtype()))?,
1778                    )
1779                    .and_then(svd_c32_outputs_to_public_tensors),
1780                    DType::C64 => linalg::faer::svd(
1781                        exec,
1782                        buffers,
1783                        input
1784                            .as_typed::<Complex64>()
1785                            .ok_or_else(|| unsupported_dtype("svd", input.dtype()))?,
1786                    )
1787                    .and_then(svd_c64_outputs_to_public_tensors),
1788                    _ => Err(unsupported_dtype("svd", input.dtype())),
1789                }
1790            }
1791        }
1792        #[cfg(feature = "blas")]
1793        {
1794            #[cfg(feature = "blas")]
1795            {
1796                let _ = &exec;
1797                match input.dtype() {
1798                    DType::F32 => linalg::blas::svd(
1799                        buffers,
1800                        input
1801                            .as_typed::<f32>()
1802                            .ok_or_else(|| unsupported_dtype("svd", input.dtype()))?,
1803                    )
1804                    .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f32>).collect()),
1805                    DType::F64 => linalg::blas::svd(
1806                        buffers,
1807                        input
1808                            .as_typed::<f64>()
1809                            .ok_or_else(|| unsupported_dtype("svd", input.dtype()))?,
1810                    )
1811                    .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f64>).collect()),
1812                    DType::C32 => {
1813                        let t = input
1814                            .as_typed::<Complex32>()
1815                            .ok_or_else(|| unsupported_dtype("svd", input.dtype()))?;
1816                        linalg::blas::svd(buffers, t).and_then(svd_c32_outputs_to_public_tensors)
1817                    }
1818                    DType::C64 => {
1819                        let t = input
1820                            .as_typed::<Complex64>()
1821                            .ok_or_else(|| unsupported_dtype("svd", input.dtype()))?;
1822                        linalg::blas::svd(buffers, t).and_then(svd_c64_outputs_to_public_tensors)
1823                    }
1824                    _ => Err(unsupported_dtype("svd", input.dtype())),
1825                }
1826            }
1827        }
1828    }
1829}
1830
1831fn svd_full_entered(
1832    exec: &mut Exec<'_>,
1833    buffers: &mut BufferPool,
1834    input: &Tensor,
1835) -> tenferro_tensor::Result<Vec<Tensor>> {
1836    {
1837        #[cfg(feature = "native")]
1838        {
1839            #[cfg(feature = "native")]
1840            {
1841                match input.dtype() {
1842                    DType::F32 => linalg::faer::svd_full(
1843                        exec,
1844                        buffers,
1845                        input
1846                            .as_typed::<f32>()
1847                            .ok_or_else(|| unsupported_dtype("svd_full", input.dtype()))?,
1848                    )
1849                    .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f32>).collect()),
1850                    DType::F64 => linalg::faer::svd_full(
1851                        exec,
1852                        buffers,
1853                        input
1854                            .as_typed::<f64>()
1855                            .ok_or_else(|| unsupported_dtype("svd_full", input.dtype()))?,
1856                    )
1857                    .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f64>).collect()),
1858                    DType::C32 => linalg::faer::svd_full(
1859                        exec,
1860                        buffers,
1861                        input
1862                            .as_typed::<Complex32>()
1863                            .ok_or_else(|| unsupported_dtype("svd_full", input.dtype()))?,
1864                    )
1865                    .and_then(svd_c32_outputs_to_public_tensors),
1866                    DType::C64 => linalg::faer::svd_full(
1867                        exec,
1868                        buffers,
1869                        input
1870                            .as_typed::<Complex64>()
1871                            .ok_or_else(|| unsupported_dtype("svd_full", input.dtype()))?,
1872                    )
1873                    .and_then(svd_c64_outputs_to_public_tensors),
1874                    _ => Err(unsupported_dtype("svd_full", input.dtype())),
1875                }
1876            }
1877        }
1878        #[cfg(feature = "blas")]
1879        {
1880            #[cfg(feature = "blas")]
1881            {
1882                let _ = &exec;
1883                match input.dtype() {
1884                    DType::F32 => linalg::blas::svd_full(
1885                        buffers,
1886                        input
1887                            .as_typed::<f32>()
1888                            .ok_or_else(|| unsupported_dtype("svd_full", input.dtype()))?,
1889                    )
1890                    .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f32>).collect()),
1891                    DType::F64 => linalg::blas::svd_full(
1892                        buffers,
1893                        input
1894                            .as_typed::<f64>()
1895                            .ok_or_else(|| unsupported_dtype("svd_full", input.dtype()))?,
1896                    )
1897                    .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f64>).collect()),
1898                    DType::C32 => {
1899                        let t = input
1900                            .as_typed::<Complex32>()
1901                            .ok_or_else(|| unsupported_dtype("svd_full", input.dtype()))?;
1902                        linalg::blas::svd_full(buffers, t)
1903                            .and_then(svd_c32_outputs_to_public_tensors)
1904                    }
1905                    DType::C64 => {
1906                        let t = input
1907                            .as_typed::<Complex64>()
1908                            .ok_or_else(|| unsupported_dtype("svd_full", input.dtype()))?;
1909                        linalg::blas::svd_full(buffers, t)
1910                            .and_then(svd_c64_outputs_to_public_tensors)
1911                    }
1912                    _ => Err(unsupported_dtype("svd_full", input.dtype())),
1913                }
1914            }
1915        }
1916    }
1917}
1918
1919fn svd_values_entered(
1920    exec: &mut Exec<'_>,
1921    buffers: &mut BufferPool,
1922    input: &Tensor,
1923) -> tenferro_tensor::Result<Tensor> {
1924    {
1925        #[cfg(feature = "native")]
1926        {
1927            #[cfg(feature = "native")]
1928            {
1929                match input.dtype() {
1930                    DType::F32 => {
1931                        let t = input
1932                            .as_typed::<f32>()
1933                            .ok_or_else(|| unsupported_dtype("svd_values", input.dtype()))?;
1934                        linalg::faer::svd_values(exec, buffers, t).map(Tensor::from_typed::<f32>)
1935                    }
1936                    DType::F64 => {
1937                        let t = input
1938                            .as_typed::<f64>()
1939                            .ok_or_else(|| unsupported_dtype("svd_values", input.dtype()))?;
1940                        linalg::faer::svd_values(exec, buffers, t).map(Tensor::from_typed::<f64>)
1941                    }
1942                    DType::C32 => {
1943                        let t = input
1944                            .as_typed::<Complex32>()
1945                            .ok_or_else(|| unsupported_dtype("svd_values", input.dtype()))?;
1946                        linalg::faer::svd_values(exec, buffers, t).map(Tensor::from_typed::<f32>)
1947                    }
1948                    DType::C64 => {
1949                        let t = input
1950                            .as_typed::<Complex64>()
1951                            .ok_or_else(|| unsupported_dtype("svd_values", input.dtype()))?;
1952                        linalg::faer::svd_values(exec, buffers, t).map(Tensor::from_typed::<f64>)
1953                    }
1954                    _ => Err(unsupported_dtype("svd_values", input.dtype())),
1955                }
1956            }
1957        }
1958        #[cfg(feature = "blas")]
1959        {
1960            #[cfg(feature = "blas")]
1961            {
1962                let _ = &exec;
1963                match input.dtype() {
1964                    DType::F32 => linalg::blas::svd_values(
1965                        buffers,
1966                        input
1967                            .as_typed::<f32>()
1968                            .ok_or_else(|| unsupported_dtype("svd_values", input.dtype()))?,
1969                    )
1970                    .map(Tensor::from_typed::<f32>),
1971                    DType::F64 => linalg::blas::svd_values(
1972                        buffers,
1973                        input
1974                            .as_typed::<f64>()
1975                            .ok_or_else(|| unsupported_dtype("svd_values", input.dtype()))?,
1976                    )
1977                    .map(Tensor::from_typed::<f64>),
1978                    DType::C32 => linalg::blas::svd_values(
1979                        buffers,
1980                        input
1981                            .as_typed::<Complex32>()
1982                            .ok_or_else(|| unsupported_dtype("svd_values", input.dtype()))?,
1983                    )
1984                    .map(Tensor::from_typed::<f32>),
1985                    DType::C64 => linalg::blas::svd_values(
1986                        buffers,
1987                        input
1988                            .as_typed::<Complex64>()
1989                            .ok_or_else(|| unsupported_dtype("svd_values", input.dtype()))?,
1990                    )
1991                    .map(Tensor::from_typed::<f64>),
1992                    _ => Err(unsupported_dtype("svd_values", input.dtype())),
1993                }
1994            }
1995        }
1996    }
1997}
1998
1999fn qr_entered(
2000    exec: &mut Exec<'_>,
2001    buffers: &mut BufferPool,
2002    input: &Tensor,
2003) -> tenferro_tensor::Result<Vec<Tensor>> {
2004    {
2005        #[cfg(feature = "native")]
2006        {
2007            #[cfg(feature = "native")]
2008            {
2009                match input.dtype() {
2010                    DType::F32 => linalg::faer::qr(
2011                        exec,
2012                        buffers,
2013                        input
2014                            .as_typed::<f32>()
2015                            .ok_or_else(|| unsupported_dtype("qr", input.dtype()))?,
2016                    )
2017                    .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f32>).collect()),
2018                    DType::F64 => linalg::faer::qr(
2019                        exec,
2020                        buffers,
2021                        input
2022                            .as_typed::<f64>()
2023                            .ok_or_else(|| unsupported_dtype("qr", input.dtype()))?,
2024                    )
2025                    .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f64>).collect()),
2026                    DType::C32 => linalg::faer::qr(
2027                        exec,
2028                        buffers,
2029                        input
2030                            .as_typed::<Complex32>()
2031                            .ok_or_else(|| unsupported_dtype("qr", input.dtype()))?,
2032                    )
2033                    .map(|outputs| {
2034                        outputs
2035                            .into_iter()
2036                            .map(Tensor::from_typed::<Complex32>)
2037                            .collect()
2038                    }),
2039                    DType::C64 => linalg::faer::qr(
2040                        exec,
2041                        buffers,
2042                        input
2043                            .as_typed::<Complex64>()
2044                            .ok_or_else(|| unsupported_dtype("qr", input.dtype()))?,
2045                    )
2046                    .map(|outputs| {
2047                        outputs
2048                            .into_iter()
2049                            .map(Tensor::from_typed::<Complex64>)
2050                            .collect()
2051                    }),
2052                    _ => Err(unsupported_dtype("qr", input.dtype())),
2053                }
2054            }
2055        }
2056        #[cfg(feature = "blas")]
2057        {
2058            #[cfg(feature = "blas")]
2059            {
2060                let _ = &exec;
2061                match input.dtype() {
2062                    DType::F32 => linalg::blas::qr(
2063                        buffers,
2064                        input
2065                            .as_typed::<f32>()
2066                            .ok_or_else(|| unsupported_dtype("qr", input.dtype()))?,
2067                    )
2068                    .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f32>).collect()),
2069                    DType::F64 => linalg::blas::qr(
2070                        buffers,
2071                        input
2072                            .as_typed::<f64>()
2073                            .ok_or_else(|| unsupported_dtype("qr", input.dtype()))?,
2074                    )
2075                    .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f64>).collect()),
2076                    DType::C32 => linalg::blas::qr(
2077                        buffers,
2078                        input
2079                            .as_typed::<Complex32>()
2080                            .ok_or_else(|| unsupported_dtype("qr", input.dtype()))?,
2081                    )
2082                    .map(|outputs| {
2083                        outputs
2084                            .into_iter()
2085                            .map(Tensor::from_typed::<Complex32>)
2086                            .collect()
2087                    }),
2088                    DType::C64 => linalg::blas::qr(
2089                        buffers,
2090                        input
2091                            .as_typed::<Complex64>()
2092                            .ok_or_else(|| unsupported_dtype("qr", input.dtype()))?,
2093                    )
2094                    .map(|outputs| {
2095                        outputs
2096                            .into_iter()
2097                            .map(Tensor::from_typed::<Complex64>)
2098                            .collect()
2099                    }),
2100                    _ => Err(unsupported_dtype("qr", input.dtype())),
2101                }
2102            }
2103        }
2104    }
2105}
2106
2107fn rank_revealing_qr_entered(
2108    exec: &mut Exec<'_>,
2109    buffers: &mut BufferPool,
2110    input: &Tensor,
2111    options: crate::RankRevealingQrOptions,
2112) -> tenferro_tensor::Result<Vec<Tensor>> {
2113    macro_rules! map_result {
2114        ($result:expr, $variant:ident) => {{
2115            $result.map(|result| {
2116                vec![
2117                    Tensor::from_typed::<preset_scalar!($variant)>(result.q),
2118                    Tensor::from_typed::<preset_scalar!($variant)>(result.r),
2119                    Tensor::from_typed::<i64>(result.column_permutation),
2120                    Tensor::from_typed::<i64>(result.rank),
2121                ]
2122            })
2123        }};
2124    }
2125    let mut outputs = {
2126        #[cfg(feature = "native")]
2127        {
2128            #[cfg(feature = "native")]
2129            {
2130                match input.dtype() {
2131                    DType::F32 => map_result!(
2132                        linalg::faer::rank_revealing_qr(
2133                            exec,
2134                            buffers,
2135                            input.as_typed::<f32>().ok_or_else(|| unsupported_dtype(
2136                                "rank_revealing_qr",
2137                                input.dtype()
2138                            ))?,
2139                            options
2140                        ),
2141                        F32
2142                    ),
2143                    DType::F64 => map_result!(
2144                        linalg::faer::rank_revealing_qr(
2145                            exec,
2146                            buffers,
2147                            input.as_typed::<f64>().ok_or_else(|| unsupported_dtype(
2148                                "rank_revealing_qr",
2149                                input.dtype()
2150                            ))?,
2151                            options
2152                        ),
2153                        F64
2154                    ),
2155                    DType::C32 => {
2156                        map_result!(
2157                            linalg::faer::rank_revealing_qr(
2158                                exec,
2159                                buffers,
2160                                input.as_typed::<Complex32>().ok_or_else(|| {
2161                                    unsupported_dtype("rank_revealing_qr", input.dtype())
2162                                })?,
2163                                options
2164                            ),
2165                            C32
2166                        )
2167                    }
2168                    DType::C64 => {
2169                        map_result!(
2170                            linalg::faer::rank_revealing_qr(
2171                                exec,
2172                                buffers,
2173                                input.as_typed::<Complex64>().ok_or_else(|| {
2174                                    unsupported_dtype("rank_revealing_qr", input.dtype())
2175                                })?,
2176                                options
2177                            ),
2178                            C64
2179                        )
2180                    }
2181                    _ => Err(unsupported_dtype("rank_revealing_qr", input.dtype())),
2182                }
2183            }
2184        }
2185        #[cfg(feature = "blas")]
2186        {
2187            #[cfg(feature = "blas")]
2188            {
2189                let _ = &exec;
2190                match input.dtype() {
2191                    DType::F32 => {
2192                        let t = input
2193                            .as_typed::<f32>()
2194                            .ok_or_else(|| unsupported_dtype("rank_revealing_qr", input.dtype()))?;
2195                        map_result!(linalg::blas::rank_revealing_qr(buffers, t, options), F32)
2196                    }
2197                    DType::F64 => {
2198                        let t = input
2199                            .as_typed::<f64>()
2200                            .ok_or_else(|| unsupported_dtype("rank_revealing_qr", input.dtype()))?;
2201                        map_result!(linalg::blas::rank_revealing_qr(buffers, t, options), F64)
2202                    }
2203                    DType::C32 => {
2204                        let t = input
2205                            .as_typed::<Complex32>()
2206                            .ok_or_else(|| unsupported_dtype("rank_revealing_qr", input.dtype()))?;
2207                        map_result!(linalg::blas::rank_revealing_qr(buffers, t, options), C32)
2208                    }
2209                    DType::C64 => {
2210                        let t = input
2211                            .as_typed::<Complex64>()
2212                            .ok_or_else(|| unsupported_dtype("rank_revealing_qr", input.dtype()))?;
2213                        map_result!(linalg::blas::rank_revealing_qr(buffers, t, options), C64)
2214                    }
2215                    _ => Err(unsupported_dtype("rank_revealing_qr", input.dtype())),
2216                }
2217            }
2218        }
2219    }?;
2220    apply_qr_gauge(options.gauge, &mut outputs[..2])?;
2221    Ok(outputs)
2222}
2223
2224fn householder_qr_entered(
2225    exec: &mut Exec<'_>,
2226    buffers: &mut BufferPool,
2227    input: &Tensor,
2228) -> tenferro_tensor::Result<CompactQrResult> {
2229    {
2230        #[cfg(feature = "native")]
2231        {
2232            #[cfg(feature = "native")]
2233            {
2234                macro_rules! factor {
2235                    ($tensor:expr, $variant:ident) => {{
2236                        let (packed, coeff) =
2237                            linalg::faer::compact_factor_2d(exec, buffers, $tensor)?;
2238                        Ok(CompactQrResult {
2239                            packed: Tensor::from_typed::<preset_scalar!($variant)>(packed),
2240                            coeff: Tensor::from_typed::<preset_scalar!($variant)>(coeff),
2241                        })
2242                    }};
2243                }
2244                match input.dtype() {
2245                    DType::F32 => factor!(
2246                        input
2247                            .as_typed::<f32>()
2248                            .ok_or_else(|| unsupported_dtype("householder_qr", input.dtype()))?,
2249                        F32
2250                    ),
2251                    DType::F64 => factor!(
2252                        input
2253                            .as_typed::<f64>()
2254                            .ok_or_else(|| unsupported_dtype("householder_qr", input.dtype()))?,
2255                        F64
2256                    ),
2257                    DType::C32 => factor!(
2258                        input
2259                            .as_typed::<Complex32>()
2260                            .ok_or_else(|| unsupported_dtype("householder_qr", input.dtype()))?,
2261                        C32
2262                    ),
2263                    DType::C64 => factor!(
2264                        input
2265                            .as_typed::<Complex64>()
2266                            .ok_or_else(|| unsupported_dtype("householder_qr", input.dtype()))?,
2267                        C64
2268                    ),
2269                    _ => Err(unsupported_dtype("householder_qr", input.dtype())),
2270                }
2271            }
2272        }
2273        #[cfg(feature = "blas")]
2274        {
2275            #[cfg(feature = "blas")]
2276            {
2277                let _ = &exec;
2278                macro_rules! factor {
2279                    ($tensor:expr, $variant:ident) => {{
2280                        let (packed, coeff) = linalg::blas::householder_qr(buffers, $tensor)?;
2281                        Ok(CompactQrResult {
2282                            packed: Tensor::from_typed::<preset_scalar!($variant)>(packed),
2283                            coeff: Tensor::from_typed::<preset_scalar!($variant)>(coeff),
2284                        })
2285                    }};
2286                }
2287                match input.dtype() {
2288                    DType::F32 => factor!(
2289                        input
2290                            .as_typed::<f32>()
2291                            .ok_or_else(|| unsupported_dtype("householder_qr", input.dtype()))?,
2292                        F32
2293                    ),
2294                    DType::F64 => factor!(
2295                        input
2296                            .as_typed::<f64>()
2297                            .ok_or_else(|| unsupported_dtype("householder_qr", input.dtype()))?,
2298                        F64
2299                    ),
2300                    DType::C32 => factor!(
2301                        input
2302                            .as_typed::<Complex32>()
2303                            .ok_or_else(|| unsupported_dtype("householder_qr", input.dtype()))?,
2304                        C32
2305                    ),
2306                    DType::C64 => factor!(
2307                        input
2308                            .as_typed::<Complex64>()
2309                            .ok_or_else(|| unsupported_dtype("householder_qr", input.dtype()))?,
2310                        C64
2311                    ),
2312                    _ => Err(unsupported_dtype("householder_qr", input.dtype())),
2313                }
2314            }
2315        }
2316    }
2317}
2318
2319fn householder_qr_from_factors_entered(
2320    exec: &mut Exec<'_>,
2321    buffers: &mut BufferPool,
2322    q: &Tensor,
2323    r: &Tensor,
2324) -> tenferro_tensor::Result<CompactQrResult> {
2325    if q.dtype() != r.dtype() {
2326        return Err(Error::dtype_mismatch(
2327            "householder_qr_from_factors",
2328            q.dtype(),
2329            r.dtype(),
2330        ));
2331    }
2332    #[cfg(feature = "native")]
2333    {
2334        #[cfg(feature = "native")]
2335        {
2336            macro_rules! import {
2337                ($q:expr, $r:expr, $variant:ident) => {{
2338                    let (packed, coeff) = linalg::faer::from_factors_2d(exec, buffers, $q, $r)?;
2339                    return Ok(CompactQrResult {
2340                        packed: Tensor::from_typed::<preset_scalar!($variant)>(packed),
2341                        coeff: Tensor::from_typed::<preset_scalar!($variant)>(coeff),
2342                    });
2343                }};
2344            }
2345            match (q.dtype(), r.dtype()) {
2346                (DType::F32, DType::F32) => {
2347                    let (q_t, r_t) = qr_import_operands::<f32>(q, r)?;
2348                    import!(q_t, r_t, F32)
2349                }
2350                (DType::F64, DType::F64) => {
2351                    let (q_t, r_t) = qr_import_operands::<f64>(q, r)?;
2352                    import!(q_t, r_t, F64)
2353                }
2354                (DType::C32, DType::C32) => {
2355                    let (q_t, r_t) = qr_import_operands::<Complex32>(q, r)?;
2356                    import!(q_t, r_t, C32)
2357                }
2358                (DType::C64, DType::C64) => {
2359                    let (q_t, r_t) = qr_import_operands::<Complex64>(q, r)?;
2360                    import!(q_t, r_t, C64)
2361                }
2362                _ => Err(unsupported_dtype("householder_qr_from_factors", q.dtype())),
2363            }
2364        }
2365    }
2366    #[cfg(feature = "blas")]
2367    {
2368        let _ = &exec;
2369        macro_rules! import {
2370            ($q:expr, $r:expr, $variant:ident) => {{
2371                let (packed, coeff) = linalg::blas::householder_qr_from_factors(buffers, $q, $r)?;
2372                Ok(CompactQrResult {
2373                    packed: Tensor::from_typed::<preset_scalar!($variant)>(packed),
2374                    coeff: Tensor::from_typed::<preset_scalar!($variant)>(coeff),
2375                })
2376            }};
2377        }
2378        match (q.dtype(), r.dtype()) {
2379            (DType::F32, DType::F32) => {
2380                let (q, r) = qr_import_operands::<f32>(q, r)?;
2381                import!(q, r, F32)
2382            }
2383            (DType::F64, DType::F64) => {
2384                let (q, r) = qr_import_operands::<f64>(q, r)?;
2385                import!(q, r, F64)
2386            }
2387            (DType::C32, DType::C32) => {
2388                let (q, r) = qr_import_operands::<Complex32>(q, r)?;
2389                import!(q, r, C32)
2390            }
2391            (DType::C64, DType::C64) => {
2392                let (q, r) = qr_import_operands::<Complex64>(q, r)?;
2393                import!(q, r, C64)
2394            }
2395            _ => Err(unsupported_dtype("householder_qr_from_factors", q.dtype())),
2396        }
2397    }
2398}
2399
2400fn householder_qr_append_entered(
2401    exec: &mut Exec<'_>,
2402    buffers: &mut BufferPool,
2403    packed: &Tensor,
2404    coeff: &Tensor,
2405    block: &Tensor,
2406) -> tenferro_tensor::Result<CompactQrResult> {
2407    if packed.dtype() != coeff.dtype() || packed.dtype() != block.dtype() {
2408        return Err(Error::dtype_mismatch(
2409            "householder_qr_append",
2410            packed.dtype(),
2411            block.dtype(),
2412        ));
2413    }
2414    #[cfg(feature = "native")]
2415    {
2416        #[cfg(feature = "native")]
2417        {
2418            macro_rules! append {
2419                ($packed:expr, $coeff:expr, $block:expr, $variant:ident) => {{
2420                    let (packed, coeff) =
2421                        linalg::faer::append_2d(exec, buffers, $packed, $coeff, $block)?;
2422                    return Ok(CompactQrResult {
2423                        packed: Tensor::from_typed::<preset_scalar!($variant)>(packed),
2424                        coeff: Tensor::from_typed::<preset_scalar!($variant)>(coeff),
2425                    });
2426                }};
2427            }
2428            match (packed.dtype(), coeff.dtype(), block.dtype()) {
2429                (DType::F32, DType::F32, DType::F32) => {
2430                    let (p, c, b) = append_operands::<f32>(packed, coeff, block)?;
2431
2432                    append!(p, c, b, F32)
2433                }
2434                (DType::F64, DType::F64, DType::F64) => {
2435                    let (p, c, b) = append_operands::<f64>(packed, coeff, block)?;
2436
2437                    append!(p, c, b, F64)
2438                }
2439                (DType::C32, DType::C32, DType::C32) => {
2440                    let (p, c, b) = append_operands::<Complex32>(packed, coeff, block)?;
2441
2442                    append!(p, c, b, C32)
2443                }
2444                (DType::C64, DType::C64, DType::C64) => {
2445                    let (p, c, b) = append_operands::<Complex64>(packed, coeff, block)?;
2446
2447                    append!(p, c, b, C64)
2448                }
2449                _ => Err(unsupported_dtype("householder_qr_append", packed.dtype())),
2450            }
2451        }
2452    }
2453    #[cfg(feature = "blas")]
2454    {
2455        let _ = &exec;
2456        macro_rules! append {
2457            ($packed:expr, $coeff:expr, $block:expr, $variant:ident) => {{
2458                let (packed, coeff) =
2459                    linalg::blas::householder_qr_append(buffers, $packed, $coeff, $block)?;
2460                Ok(CompactQrResult {
2461                    packed: Tensor::from_typed::<preset_scalar!($variant)>(packed),
2462                    coeff: Tensor::from_typed::<preset_scalar!($variant)>(coeff),
2463                })
2464            }};
2465        }
2466        match (packed.dtype(), coeff.dtype(), block.dtype()) {
2467            (DType::F32, DType::F32, DType::F32) => {
2468                let (p, c, b) = append_operands::<f32>(packed, coeff, block)?;
2469
2470                append!(p, c, b, F32)
2471            }
2472            (DType::F64, DType::F64, DType::F64) => {
2473                let (p, c, b) = append_operands::<f64>(packed, coeff, block)?;
2474
2475                append!(p, c, b, F64)
2476            }
2477            (DType::C32, DType::C32, DType::C32) => {
2478                let (p, c, b) = append_operands::<Complex32>(packed, coeff, block)?;
2479
2480                append!(p, c, b, C32)
2481            }
2482            (DType::C64, DType::C64, DType::C64) => {
2483                let (p, c, b) = append_operands::<Complex64>(packed, coeff, block)?;
2484
2485                append!(p, c, b, C64)
2486            }
2487            _ => Err(unsupported_dtype("householder_qr_append", packed.dtype())),
2488        }
2489    }
2490}
2491
2492fn householder_qr_r_entered(
2493    _context: &mut Exec<'_>,
2494    buffers: &mut BufferPool,
2495    packed: &Tensor,
2496    coeff: &Tensor,
2497    options: crate::QrOptions,
2498) -> tenferro_tensor::Result<Tensor> {
2499    let positive = options.gauge == crate::QrGauge::PositiveDiagonal;
2500    #[cfg(feature = "native")]
2501    {
2502        #[cfg(feature = "native")]
2503        return same_variant_pair!(
2504            packed,
2505            coeff,
2506            |p, c| linalg::faer::raw_r_2d(buffers, p, c, positive),
2507            Err(Error::dtype_mismatch(
2508                "householder_qr_r",
2509                packed.dtype(),
2510                coeff.dtype(),
2511            ))
2512        );
2513    }
2514    #[cfg(feature = "blas")]
2515    {
2516        // The LAPACK reflector state has no pooled R destination; its extraction builds the
2517        // response vector itself.
2518        let _ = buffers;
2519        same_variant_pair!(
2520            packed,
2521            coeff,
2522            |p, c| linalg::blas::householder_qr_r(p, c, positive),
2523            Err(Error::dtype_mismatch(
2524                "householder_qr_r",
2525                packed.dtype(),
2526                coeff.dtype(),
2527            ))
2528        )
2529    }
2530}
2531
2532fn householder_qr_q_columns_entered(
2533    _context: &mut Exec<'_>,
2534    buffers: &mut BufferPool,
2535    packed: &Tensor,
2536    coeff: &Tensor,
2537    columns: std::ops::Range<usize>,
2538    options: crate::QrOptions,
2539) -> tenferro_tensor::Result<Tensor> {
2540    let positive = options.gauge == crate::QrGauge::PositiveDiagonal;
2541    #[cfg(feature = "native")]
2542    {
2543        #[cfg(feature = "native")]
2544        return match (packed.dtype(), coeff.dtype()) {
2545            (DType::F32, DType::F32) => {
2546                let (p, c) = q_columns_operands::<f32>(packed, coeff)?;
2547                linalg::faer::q_columns_2d(
2548                    _context,
2549                    buffers,
2550                    p,
2551                    c,
2552                    columns.start,
2553                    columns.end,
2554                    positive,
2555                )
2556                .map(Tensor::from_typed::<f32>)
2557            }
2558            (DType::F64, DType::F64) => {
2559                let (p, c) = q_columns_operands::<f64>(packed, coeff)?;
2560                linalg::faer::q_columns_2d(
2561                    _context,
2562                    buffers,
2563                    p,
2564                    c,
2565                    columns.start,
2566                    columns.end,
2567                    positive,
2568                )
2569                .map(Tensor::from_typed::<f64>)
2570            }
2571            (DType::C32, DType::C32) => {
2572                let (p, c) = q_columns_operands::<Complex32>(packed, coeff)?;
2573                linalg::faer::q_columns_2d(
2574                    _context,
2575                    buffers,
2576                    p,
2577                    c,
2578                    columns.start,
2579                    columns.end,
2580                    positive,
2581                )
2582                .map(Tensor::from_typed::<Complex32>)
2583            }
2584            (DType::C64, DType::C64) => {
2585                let (p, c) = q_columns_operands::<Complex64>(packed, coeff)?;
2586                linalg::faer::q_columns_2d(
2587                    _context,
2588                    buffers,
2589                    p,
2590                    c,
2591                    columns.start,
2592                    columns.end,
2593                    positive,
2594                )
2595                .map(Tensor::from_typed::<Complex64>)
2596            }
2597            _ => Err(Error::dtype_mismatch(
2598                "householder_qr_q_columns",
2599                packed.dtype(),
2600                coeff.dtype(),
2601            )),
2602        };
2603    }
2604    #[cfg(feature = "blas")]
2605    {
2606        macro_rules! columns {
2607            ($packed:expr, $coeff:expr, $variant:ident) => {
2608                linalg::blas::householder_qr_q_columns(
2609                    buffers,
2610                    $packed,
2611                    $coeff,
2612                    columns.start,
2613                    columns.end,
2614                    positive,
2615                )
2616                .map(Tensor::from_typed::<preset_scalar!($variant)>)
2617            };
2618        }
2619        match (packed.dtype(), coeff.dtype()) {
2620            (DType::F32, DType::F32) => {
2621                let (p, c) = q_columns_operands::<f32>(packed, coeff)?;
2622                columns!(p, c, F32)
2623            }
2624            (DType::F64, DType::F64) => {
2625                let (p, c) = q_columns_operands::<f64>(packed, coeff)?;
2626                columns!(p, c, F64)
2627            }
2628            (DType::C32, DType::C32) => {
2629                let (p, c) = q_columns_operands::<Complex32>(packed, coeff)?;
2630                columns!(p, c, C32)
2631            }
2632            (DType::C64, DType::C64) => {
2633                let (p, c) = q_columns_operands::<Complex64>(packed, coeff)?;
2634                columns!(p, c, C64)
2635            }
2636            _ => Err(Error::dtype_mismatch(
2637                "householder_qr_q_columns",
2638                packed.dtype(),
2639                coeff.dtype(),
2640            )),
2641        }
2642    }
2643}
2644
2645fn eigh_entered(
2646    exec: &mut Exec<'_>,
2647    buffers: &mut BufferPool,
2648    input: &Tensor,
2649) -> tenferro_tensor::Result<Vec<Tensor>> {
2650    {
2651        #[cfg(feature = "native")]
2652        {
2653            #[cfg(feature = "native")]
2654            {
2655                match input.dtype() {
2656                    DType::F32 => linalg::faer::eigh(
2657                        exec,
2658                        buffers,
2659                        input
2660                            .as_typed::<f32>()
2661                            .ok_or_else(|| unsupported_dtype("eigh", input.dtype()))?,
2662                    )
2663                    .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f32>).collect()),
2664                    DType::F64 => linalg::faer::eigh(
2665                        exec,
2666                        buffers,
2667                        input
2668                            .as_typed::<f64>()
2669                            .ok_or_else(|| unsupported_dtype("eigh", input.dtype()))?,
2670                    )
2671                    .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f64>).collect()),
2672                    DType::C32 => linalg::faer::eigh(
2673                        exec,
2674                        buffers,
2675                        input
2676                            .as_typed::<Complex32>()
2677                            .ok_or_else(|| unsupported_dtype("eigh", input.dtype()))?,
2678                    )
2679                    .and_then(eigh_c32_outputs_to_public_tensors),
2680                    DType::C64 => linalg::faer::eigh(
2681                        exec,
2682                        buffers,
2683                        input
2684                            .as_typed::<Complex64>()
2685                            .ok_or_else(|| unsupported_dtype("eigh", input.dtype()))?,
2686                    )
2687                    .and_then(eigh_c64_outputs_to_public_tensors),
2688                    _ => Err(unsupported_dtype("eigh", input.dtype())),
2689                }
2690            }
2691        }
2692        #[cfg(feature = "blas")]
2693        {
2694            #[cfg(feature = "blas")]
2695            {
2696                let _ = &exec;
2697                match input.dtype() {
2698                    DType::F32 => linalg::blas::eigh(
2699                        buffers,
2700                        input
2701                            .as_typed::<f32>()
2702                            .ok_or_else(|| unsupported_dtype("eigh", input.dtype()))?,
2703                    )
2704                    .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f32>).collect()),
2705                    DType::F64 => linalg::blas::eigh(
2706                        buffers,
2707                        input
2708                            .as_typed::<f64>()
2709                            .ok_or_else(|| unsupported_dtype("eigh", input.dtype()))?,
2710                    )
2711                    .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f64>).collect()),
2712                    DType::C32 => {
2713                        let t = input
2714                            .as_typed::<Complex32>()
2715                            .ok_or_else(|| unsupported_dtype("eigh", input.dtype()))?;
2716                        linalg::blas::eigh(buffers, t).and_then(eigh_c32_outputs_to_public_tensors)
2717                    }
2718                    DType::C64 => {
2719                        let t = input
2720                            .as_typed::<Complex64>()
2721                            .ok_or_else(|| unsupported_dtype("eigh", input.dtype()))?;
2722                        linalg::blas::eigh(buffers, t).and_then(eigh_c64_outputs_to_public_tensors)
2723                    }
2724                    _ => Err(unsupported_dtype("eigh", input.dtype())),
2725                }
2726            }
2727        }
2728    }
2729}
2730
2731fn eigh_values_entered(
2732    exec: &mut Exec<'_>,
2733    buffers: &mut BufferPool,
2734    input: &Tensor,
2735) -> tenferro_tensor::Result<Tensor> {
2736    {
2737        #[cfg(feature = "native")]
2738        {
2739            #[cfg(feature = "native")]
2740            {
2741                match input.dtype() {
2742                    DType::F32 => {
2743                        let t = input
2744                            .as_typed::<f32>()
2745                            .ok_or_else(|| unsupported_dtype("eigh_values", input.dtype()))?;
2746                        linalg::faer::eigh_values(exec, buffers, t).map(Tensor::from_typed::<f32>)
2747                    }
2748                    DType::F64 => {
2749                        let t = input
2750                            .as_typed::<f64>()
2751                            .ok_or_else(|| unsupported_dtype("eigh_values", input.dtype()))?;
2752                        linalg::faer::eigh_values(exec, buffers, t).map(Tensor::from_typed::<f64>)
2753                    }
2754                    DType::C32 => {
2755                        let t = input
2756                            .as_typed::<Complex32>()
2757                            .ok_or_else(|| unsupported_dtype("eigh_values", input.dtype()))?;
2758                        linalg::faer::eigh_values(exec, buffers, t).map(Tensor::from_typed::<f32>)
2759                    }
2760                    DType::C64 => {
2761                        let t = input
2762                            .as_typed::<Complex64>()
2763                            .ok_or_else(|| unsupported_dtype("eigh_values", input.dtype()))?;
2764                        linalg::faer::eigh_values(exec, buffers, t).map(Tensor::from_typed::<f64>)
2765                    }
2766                    _ => Err(unsupported_dtype("eigh_values", input.dtype())),
2767                }
2768            }
2769        }
2770        #[cfg(feature = "blas")]
2771        {
2772            #[cfg(feature = "blas")]
2773            {
2774                let _ = &exec;
2775                match input.dtype() {
2776                    DType::F32 => linalg::blas::eigh_values(
2777                        buffers,
2778                        input
2779                            .as_typed::<f32>()
2780                            .ok_or_else(|| unsupported_dtype("eigh_values", input.dtype()))?,
2781                    )
2782                    .map(Tensor::from_typed::<f32>),
2783                    DType::F64 => linalg::blas::eigh_values(
2784                        buffers,
2785                        input
2786                            .as_typed::<f64>()
2787                            .ok_or_else(|| unsupported_dtype("eigh_values", input.dtype()))?,
2788                    )
2789                    .map(Tensor::from_typed::<f64>),
2790                    DType::C32 => linalg::blas::eigh_values(
2791                        buffers,
2792                        input
2793                            .as_typed::<Complex32>()
2794                            .ok_or_else(|| unsupported_dtype("eigh_values", input.dtype()))?,
2795                    )
2796                    .map(Tensor::from_typed::<f32>),
2797                    DType::C64 => linalg::blas::eigh_values(
2798                        buffers,
2799                        input
2800                            .as_typed::<Complex64>()
2801                            .ok_or_else(|| unsupported_dtype("eigh_values", input.dtype()))?,
2802                    )
2803                    .map(Tensor::from_typed::<f64>),
2804                    _ => Err(unsupported_dtype("eigh_values", input.dtype())),
2805                }
2806            }
2807        }
2808    }
2809}
2810
2811fn eig_values_entered(
2812    exec: &mut Exec<'_>,
2813    buffers: &mut BufferPool,
2814    input: &Tensor,
2815) -> tenferro_tensor::Result<Tensor> {
2816    {
2817        #[cfg(feature = "native")]
2818        {
2819            #[cfg(feature = "native")]
2820            {
2821                linalg::faer::eig_values(exec, buffers, input)
2822            }
2823        }
2824        #[cfg(feature = "blas")]
2825        {
2826            #[cfg(feature = "blas")]
2827            {
2828                let _ = &exec;
2829                linalg::blas::eig_values(buffers, input)
2830            }
2831        }
2832    }
2833}
2834
2835fn eig_entered(
2836    exec: &mut Exec<'_>,
2837    buffers: &mut BufferPool,
2838    input: &Tensor,
2839) -> tenferro_tensor::Result<Vec<Tensor>> {
2840    {
2841        #[cfg(feature = "native")]
2842        {
2843            #[cfg(feature = "native")]
2844            {
2845                linalg::faer::eig(exec, buffers, input)
2846            }
2847        }
2848        #[cfg(feature = "blas")]
2849        {
2850            #[cfg(feature = "blas")]
2851            {
2852                let _ = &exec;
2853                linalg::blas::eig(buffers, input)
2854            }
2855        }
2856    }
2857}
2858
2859#[cfg(feature = "native")]
2860fn faer_strided_read_ok(input: &TensorRead<'_>) -> bool {
2861    match input {
2862        TensorRead::Tensor(tensor) => match tensor.dtype() {
2863            DType::F32 => tensor
2864                .as_typed::<f32>()
2865                .is_some_and(|t| linalg::faer::faer_strided_ok(&t.as_view())),
2866            DType::F64 => tensor
2867                .as_typed::<f64>()
2868                .is_some_and(|t| linalg::faer::faer_strided_ok(&t.as_view())),
2869            DType::C32 => tensor
2870                .as_typed::<Complex32>()
2871                .is_some_and(|t| linalg::faer::faer_strided_ok(&t.as_view())),
2872            DType::C64 => tensor
2873                .as_typed::<Complex64>()
2874                .is_some_and(|t| linalg::faer::faer_strided_ok(&t.as_view())),
2875            // A caller-owned payload is not a faer operand, and neither are the
2876            // integer and boolean tags, which faer does not compute on here.
2877            _ => false,
2878        },
2879        TensorRead::View(TensorView::F32(view)) => linalg::faer::faer_strided_ok(view),
2880        TensorRead::View(TensorView::F64(view)) => linalg::faer::faer_strided_ok(view),
2881        TensorRead::View(TensorView::C32(view)) => linalg::faer::faer_strided_ok(view),
2882        TensorRead::View(TensorView::C64(view)) => linalg::faer::faer_strided_ok(view),
2883        TensorRead::View(TensorView::I32(_))
2884        | TensorRead::View(TensorView::I64(_))
2885        | TensorRead::View(TensorView::Bool(_)) => false,
2886    }
2887}
2888
2889/// Can this right-hand side be gathered directly by the faer view path?
2890///
2891/// The RHS is copied element by element, so strides may be arbitrary; only host
2892/// placement, the matrix rank every provider requires, and a supported dtype
2893/// matter.
2894#[cfg(feature = "native")]
2895fn faer_rhs_read_ok(input: &TensorRead<'_>) -> bool {
2896    if input.backend_family().is_some() {
2897        return false;
2898    }
2899    if input.shape().len() != 2 {
2900        return false;
2901    }
2902    matches!(
2903        input.dtype(),
2904        DType::F32 | DType::F64 | DType::C32 | DType::C64
2905    )
2906}
2907
2908#[cfg(feature = "native")]
2909fn rank_revealing_qr_faer_view_entered(
2910    exec: &mut Exec<'_>,
2911    buffers: &mut BufferPool,
2912    input: TensorView<'_>,
2913    options: crate::RankRevealingQrOptions,
2914) -> tenferro_tensor::Result<Vec<Tensor>> {
2915    macro_rules! factor {
2916        ($view:expr, $scalar:ty) => {
2917            linalg::faer::rank_revealing_qr_view(exec, buffers, $view, options).map(|result| {
2918                vec![
2919                    Tensor::from_typed::<$scalar>(result.q),
2920                    Tensor::from_typed::<$scalar>(result.r),
2921                    Tensor::from_typed::<i64>(result.column_permutation),
2922                    Tensor::from_typed::<i64>(result.rank),
2923                ]
2924            })
2925        };
2926    }
2927    let mut outputs = match input {
2928        TensorView::F32(view) => factor!(view, f32),
2929        TensorView::F64(view) => factor!(view, f64),
2930        TensorView::C32(view) => factor!(view, Complex32),
2931        TensorView::C64(view) => factor!(view, Complex64),
2932        unsupported => Err(unsupported_dtype("rank_revealing_qr", unsupported.dtype())),
2933    }?;
2934    apply_qr_gauge(options.gauge, &mut outputs[..2])?;
2935    Ok(outputs)
2936}
2937
2938#[cfg(feature = "native")]
2939fn triangular_solve_faer_view_entered(
2940    exec: &mut Exec<'_>,
2941    buffers: &mut BufferPool,
2942    a: TensorView<'_>,
2943    b: TensorView<'_>,
2944    options: TriangularSolveOptions,
2945) -> tenferro_tensor::Result<Tensor> {
2946    let TriangularSolveOptions {
2947        left_side,
2948        lower,
2949        transpose_a,
2950        unit_diagonal,
2951    } = options;
2952    macro_rules! solve {
2953        ($a:expr, $b:expr, $scalar:ty) => {
2954            linalg::faer::triangular_solve_view(
2955                exec,
2956                buffers,
2957                $a,
2958                $b,
2959                left_side,
2960                lower,
2961                transpose_a,
2962                unit_diagonal,
2963            )
2964            .map(Tensor::from_typed::<$scalar>)
2965        };
2966    }
2967    match (a, b) {
2968        (TensorView::F32(a), TensorView::F32(b)) => solve!(a, b, f32),
2969        (TensorView::F64(a), TensorView::F64(b)) => solve!(a, b, f64),
2970        (TensorView::C32(a), TensorView::C32(b)) => solve!(a, b, Complex32),
2971        (TensorView::C64(a), TensorView::C64(b)) => solve!(a, b, Complex64),
2972        (a, b) => Err(Error::dtype_mismatch(
2973            "triangular_solve",
2974            a.dtype(),
2975            b.dtype(),
2976        )),
2977    }
2978}
2979
2980#[cfg(feature = "native")]
2981fn svd_faer_view_entered(
2982    exec: &mut Exec<'_>,
2983    buffers: &mut BufferPool,
2984    input: TensorView<'_>,
2985) -> tenferro_tensor::Result<Vec<Tensor>> {
2986    match input {
2987        TensorView::F32(view) => linalg::faer::svd_view(exec, buffers, view)
2988            .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f32>).collect()),
2989        TensorView::F64(view) => linalg::faer::svd_view(exec, buffers, view)
2990            .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f64>).collect()),
2991        TensorView::C32(view) => {
2992            linalg::faer::svd_view(exec, buffers, view).and_then(svd_c32_outputs_to_public_tensors)
2993        }
2994        TensorView::C64(view) => {
2995            linalg::faer::svd_view(exec, buffers, view).and_then(svd_c64_outputs_to_public_tensors)
2996        }
2997        unsupported => Err(unsupported_dtype("svd", unsupported.dtype())),
2998    }
2999}
3000
3001#[cfg(feature = "native")]
3002fn svd_full_faer_view_entered(
3003    exec: &mut Exec<'_>,
3004    buffers: &mut BufferPool,
3005    input: TensorView<'_>,
3006) -> tenferro_tensor::Result<Vec<Tensor>> {
3007    match input {
3008        TensorView::F32(view) => linalg::faer::svd_full_view(exec, buffers, view)
3009            .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f32>).collect()),
3010        TensorView::F64(view) => linalg::faer::svd_full_view(exec, buffers, view)
3011            .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f64>).collect()),
3012        TensorView::C32(view) => linalg::faer::svd_full_view(exec, buffers, view)
3013            .and_then(svd_c32_outputs_to_public_tensors),
3014        TensorView::C64(view) => linalg::faer::svd_full_view(exec, buffers, view)
3015            .and_then(svd_c64_outputs_to_public_tensors),
3016        unsupported => Err(unsupported_dtype("svd_full", unsupported.dtype())),
3017    }
3018}
3019
3020#[cfg(feature = "native")]
3021fn svd_values_faer_view_entered(
3022    exec: &mut Exec<'_>,
3023    buffers: &mut BufferPool,
3024    input: TensorView<'_>,
3025) -> tenferro_tensor::Result<Tensor> {
3026    match input {
3027        TensorView::F32(view) => {
3028            linalg::faer::svd_values_view(exec, buffers, view).map(Tensor::from_typed::<f32>)
3029        }
3030        TensorView::F64(view) => {
3031            linalg::faer::svd_values_view(exec, buffers, view).map(Tensor::from_typed::<f64>)
3032        }
3033        TensorView::C32(view) => {
3034            linalg::faer::svd_values_view(exec, buffers, view).map(Tensor::from_typed::<f32>)
3035        }
3036        TensorView::C64(view) => {
3037            linalg::faer::svd_values_view(exec, buffers, view).map(Tensor::from_typed::<f64>)
3038        }
3039        unsupported => Err(unsupported_dtype("svd_values", unsupported.dtype())),
3040    }
3041}
3042
3043#[cfg(feature = "native")]
3044fn qr_faer_view_entered(
3045    exec: &mut Exec<'_>,
3046    buffers: &mut BufferPool,
3047    input: TensorView<'_>,
3048) -> tenferro_tensor::Result<Vec<Tensor>> {
3049    match input {
3050        TensorView::F32(view) => linalg::faer::qr_view(exec, buffers, view)
3051            .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f32>).collect()),
3052        TensorView::F64(view) => linalg::faer::qr_view(exec, buffers, view)
3053            .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f64>).collect()),
3054        TensorView::C32(view) => linalg::faer::qr_view(exec, buffers, view).map(|outputs| {
3055            outputs
3056                .into_iter()
3057                .map(Tensor::from_typed::<Complex32>)
3058                .collect()
3059        }),
3060        TensorView::C64(view) => linalg::faer::qr_view(exec, buffers, view).map(|outputs| {
3061            outputs
3062                .into_iter()
3063                .map(Tensor::from_typed::<Complex64>)
3064                .collect()
3065        }),
3066        unsupported => Err(unsupported_dtype("qr", unsupported.dtype())),
3067    }
3068}
3069
3070#[cfg(feature = "native")]
3071fn eigh_faer_view_entered(
3072    exec: &mut Exec<'_>,
3073    buffers: &mut BufferPool,
3074    input: TensorView<'_>,
3075) -> tenferro_tensor::Result<Vec<Tensor>> {
3076    match input {
3077        TensorView::F32(view) => linalg::faer::eigh_view(exec, buffers, view)
3078            .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f32>).collect()),
3079        TensorView::F64(view) => linalg::faer::eigh_view(exec, buffers, view)
3080            .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f64>).collect()),
3081        TensorView::C32(view) => linalg::faer::eigh_view(exec, buffers, view)
3082            .and_then(eigh_c32_outputs_to_public_tensors),
3083        TensorView::C64(view) => linalg::faer::eigh_view(exec, buffers, view)
3084            .and_then(eigh_c64_outputs_to_public_tensors),
3085        unsupported => Err(unsupported_dtype("eigh", unsupported.dtype())),
3086    }
3087}
3088
3089#[cfg(feature = "native")]
3090fn eigh_values_faer_view_entered(
3091    exec: &mut Exec<'_>,
3092    buffers: &mut BufferPool,
3093    input: TensorView<'_>,
3094) -> tenferro_tensor::Result<Tensor> {
3095    match input {
3096        TensorView::F32(view) => {
3097            linalg::faer::eigh_values_view(exec, buffers, view).map(Tensor::from_typed::<f32>)
3098        }
3099        TensorView::F64(view) => {
3100            linalg::faer::eigh_values_view(exec, buffers, view).map(Tensor::from_typed::<f64>)
3101        }
3102        TensorView::C32(view) => {
3103            linalg::faer::eigh_values_view(exec, buffers, view).map(Tensor::from_typed::<f32>)
3104        }
3105        TensorView::C64(view) => {
3106            linalg::faer::eigh_values_view(exec, buffers, view).map(Tensor::from_typed::<f64>)
3107        }
3108        unsupported => Err(unsupported_dtype("eigh_values", unsupported.dtype())),
3109    }
3110}
3111
3112#[cfg(feature = "native")]
3113fn cholesky_faer_view_entered(
3114    exec: &mut Exec<'_>,
3115    buffers: &mut BufferPool,
3116    input: TensorView<'_>,
3117) -> tenferro_tensor::Result<Tensor> {
3118    match input {
3119        TensorView::F32(view) => {
3120            linalg::faer::cholesky_view(exec, buffers, view).map(Tensor::from_typed::<f32>)
3121        }
3122        TensorView::F64(view) => {
3123            linalg::faer::cholesky_view(exec, buffers, view).map(Tensor::from_typed::<f64>)
3124        }
3125        TensorView::C32(view) => {
3126            linalg::faer::cholesky_view(exec, buffers, view).map(Tensor::from_typed::<Complex32>)
3127        }
3128        TensorView::C64(view) => {
3129            linalg::faer::cholesky_view(exec, buffers, view).map(Tensor::from_typed::<Complex64>)
3130        }
3131        unsupported => Err(unsupported_dtype("cholesky", unsupported.dtype())),
3132    }
3133}
3134
3135#[cfg(feature = "native")]
3136fn lu_faer_view_entered(
3137    exec: &mut Exec<'_>,
3138    buffers: &mut BufferPool,
3139    input: TensorView<'_>,
3140) -> tenferro_tensor::Result<Vec<Tensor>> {
3141    match input {
3142        TensorView::F32(view) => linalg::faer::lu_view(exec, buffers, view)
3143            .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f32>).collect()),
3144        TensorView::F64(view) => linalg::faer::lu_view(exec, buffers, view)
3145            .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f64>).collect()),
3146        TensorView::C32(view) => linalg::faer::lu_view(exec, buffers, view).map(|outputs| {
3147            outputs
3148                .into_iter()
3149                .map(Tensor::from_typed::<Complex32>)
3150                .collect()
3151        }),
3152        TensorView::C64(view) => linalg::faer::lu_view(exec, buffers, view).map(|outputs| {
3153            outputs
3154                .into_iter()
3155                .map(Tensor::from_typed::<Complex64>)
3156                .collect()
3157        }),
3158        unsupported => Err(unsupported_dtype("lu", unsupported.dtype())),
3159    }
3160}
3161
3162#[cfg(feature = "native")]
3163fn full_piv_lu_faer_view_entered(
3164    exec: &mut Exec<'_>,
3165    buffers: &mut BufferPool,
3166    input: TensorView<'_>,
3167) -> tenferro_tensor::Result<Vec<Tensor>> {
3168    match input {
3169        TensorView::F32(view) => linalg::faer::full_piv_lu_view(exec, buffers, view)
3170            .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f32>).collect()),
3171        TensorView::F64(view) => linalg::faer::full_piv_lu_view(exec, buffers, view)
3172            .map(|outputs| outputs.into_iter().map(Tensor::from_typed::<f64>).collect()),
3173        TensorView::C32(view) => {
3174            linalg::faer::full_piv_lu_view(exec, buffers, view).map(|outputs| {
3175                outputs
3176                    .into_iter()
3177                    .map(Tensor::from_typed::<Complex32>)
3178                    .collect()
3179            })
3180        }
3181        TensorView::C64(view) => {
3182            linalg::faer::full_piv_lu_view(exec, buffers, view).map(|outputs| {
3183                outputs
3184                    .into_iter()
3185                    .map(Tensor::from_typed::<Complex64>)
3186                    .collect()
3187            })
3188        }
3189        unsupported => Err(unsupported_dtype("full_piv_lu", unsupported.dtype())),
3190    }
3191}
3192
3193fn ensure_host_typed_tensor<T: TensorScalar>(
3194    op: &'static str,
3195    input: &TypedTensor<T>,
3196) -> tenferro_tensor::Result<()> {
3197    if input.as_view().backend_buffer().is_some() {
3198        return Err(Error::runtime_state(
3199            op,
3200            "CPU linalg backend received a backend buffer; download the tensor to host before CPU execution",
3201        ));
3202    }
3203    Ok(())
3204}
3205
3206fn ensure_supported_linalg_pair(
3207    op: &'static str,
3208    lhs: &Tensor,
3209    rhs: &Tensor,
3210) -> tenferro_tensor::Result<()> {
3211    ensure_supported_linalg_dtypes(op, lhs.dtype(), rhs.dtype())
3212}
3213
3214fn ensure_supported_linalg_dtypes(
3215    op: &'static str,
3216    lhs: DType,
3217    rhs: DType,
3218) -> tenferro_tensor::Result<()> {
3219    if lhs != rhs {
3220        return Err(Error::dtype_mismatch(op, lhs, rhs));
3221    }
3222    ensure_supported_linalg_dtype(op, lhs)
3223}
3224
3225fn ensure_supported_linalg_dtype(op: &'static str, dtype: DType) -> tenferro_tensor::Result<()> {
3226    match dtype {
3227        DType::F32 | DType::F64 | DType::C32 | DType::C64 => Ok(()),
3228        DType::I32 | DType::I64 | DType::Bool | DType::External(_) => {
3229            Err(unsupported_dtype(op, dtype))
3230        }
3231    }
3232}
3233
3234fn has_zero_dim(shape: &[usize]) -> bool {
3235    shape.contains(&0)
3236}
3237
3238fn batched_vector_rhs_shape(a: &Tensor, b: &Tensor) -> Option<Vec<usize>> {
3239    batched_vector_rhs_shape_of(a.shape(), b.shape())
3240}
3241
3242/// The matrix RHS shape `[n, 1, batch...]` a vector RHS `b` is solved as.
3243fn batched_vector_rhs_shape_of(a_shape: &[usize], b_shape: &[usize]) -> Option<Vec<usize>> {
3244    if b_shape.len() == 1 {
3245        return Some(vec![b_shape[0], 1]);
3246    }
3247
3248    let is_batched_vector_rhs = a_shape.len() == b_shape.len() + 1
3249        && !b_shape.is_empty()
3250        && b_shape[0] == a_shape[0]
3251        && b_shape[1..] == a_shape[2..];
3252    if !is_batched_vector_rhs {
3253        return None;
3254    }
3255
3256    let mut rhs_shape = vec![b_shape[0], 1];
3257    rhs_shape.extend_from_slice(&b_shape[1..]);
3258    Some(rhs_shape)
3259}
3260
3261fn zeros_like_tensor(input: &Tensor) -> tenferro_tensor::Result<Tensor> {
3262    Ok(match input.dtype() {
3263        // A caller-owned payload has no zero-like runtime tensor.
3264        DType::External(type_id) => {
3265            return Err(unsupported_dtype(
3266                "zeros_like_tensor",
3267                DType::External(type_id),
3268            ));
3269        }
3270        DType::F32 => Tensor::from_typed::<f32>(TypedTensor::zeros(input.shape().to_vec())?),
3271        DType::F64 => Tensor::from_typed::<f64>(TypedTensor::zeros(input.shape().to_vec())?),
3272        DType::I32 => Tensor::from_typed::<i32>(TypedTensor::zeros(input.shape().to_vec())?),
3273        DType::I64 => Tensor::from_typed::<i64>(TypedTensor::zeros(input.shape().to_vec())?),
3274        DType::Bool => {
3275            let t = typed_host::<bool>(input, "zeros_like_tensor")?;
3276            Tensor::from_typed::<bool>(TypedTensor::from_vec_col_major(
3277                t.shape().to_vec(),
3278                vec![false; t.n_elements()],
3279            )?)
3280        }
3281        DType::C32 => Tensor::from_typed::<Complex32>(TypedTensor::zeros(input.shape().to_vec())?),
3282        DType::C64 => Tensor::from_typed::<Complex64>(TypedTensor::zeros(input.shape().to_vec())?),
3283    })
3284}
3285
3286fn complex32_real_part_tensor(
3287    values: TypedTensor<Complex32>,
3288) -> tenferro_tensor::Result<TypedTensor<f32>> {
3289    let mut out = TypedTensor::from_vec_col_major(
3290        values.shape().to_vec(),
3291        values.host_data()?.iter().map(|value| value.re).collect(),
3292    )?;
3293    out.set_placement(values.placement().clone());
3294    Ok(out)
3295}
3296
3297fn complex64_real_part_tensor(
3298    values: TypedTensor<Complex64>,
3299) -> tenferro_tensor::Result<TypedTensor<f64>> {
3300    let mut out = TypedTensor::from_vec_col_major(
3301        values.shape().to_vec(),
3302        values.host_data()?.iter().map(|value| value.re).collect(),
3303    )?;
3304    out.set_placement(values.placement().clone());
3305    Ok(out)
3306}
3307
3308fn svd_output_count_error(count: usize) -> Error {
3309    Error::Internal(format!(
3310        "svd produced an invalid output count: expected 3, got {count}"
3311    ))
3312}
3313
3314fn full_piv_lu_output_count_error(count: usize) -> Error {
3315    Error::Internal(format!(
3316        "full_piv_lu produced an invalid output count: expected 5, got {count}"
3317    ))
3318}
3319
3320fn eigh_output_count_error(count: usize) -> Error {
3321    Error::Internal(format!(
3322        "eigh produced an invalid output count: expected 2, got {count}"
3323    ))
3324}
3325
3326fn full_piv_lu_c32_outputs_to_public_tensors(
3327    outputs: Vec<TypedTensor<Complex32>>,
3328) -> tenferro_tensor::Result<Vec<Tensor>> {
3329    let count = outputs.len();
3330    let mut outputs = outputs.into_iter();
3331    match (
3332        outputs.next(),
3333        outputs.next(),
3334        outputs.next(),
3335        outputs.next(),
3336        outputs.next(),
3337        outputs.next(),
3338    ) {
3339        (Some(p), Some(l), Some(u), Some(q), Some(parity), None) => Ok(vec![
3340            Tensor::from_typed::<Complex32>(p),
3341            Tensor::from_typed::<Complex32>(l),
3342            Tensor::from_typed::<Complex32>(u),
3343            Tensor::from_typed::<Complex32>(q),
3344            Tensor::from_typed::<f32>(complex32_real_part_tensor(parity)?),
3345        ]),
3346        _ => Err(full_piv_lu_output_count_error(count)),
3347    }
3348}
3349
3350fn full_piv_lu_c64_outputs_to_public_tensors(
3351    outputs: Vec<TypedTensor<Complex64>>,
3352) -> tenferro_tensor::Result<Vec<Tensor>> {
3353    let count = outputs.len();
3354    let mut outputs = outputs.into_iter();
3355    match (
3356        outputs.next(),
3357        outputs.next(),
3358        outputs.next(),
3359        outputs.next(),
3360        outputs.next(),
3361        outputs.next(),
3362    ) {
3363        (Some(p), Some(l), Some(u), Some(q), Some(parity), None) => Ok(vec![
3364            Tensor::from_typed::<Complex64>(p),
3365            Tensor::from_typed::<Complex64>(l),
3366            Tensor::from_typed::<Complex64>(u),
3367            Tensor::from_typed::<Complex64>(q),
3368            Tensor::from_typed::<f64>(complex64_real_part_tensor(parity)?),
3369        ]),
3370        _ => Err(full_piv_lu_output_count_error(count)),
3371    }
3372}
3373
3374fn svd_c32_outputs_to_public_tensors(
3375    outputs: Vec<TypedTensor<Complex32>>,
3376) -> tenferro_tensor::Result<Vec<Tensor>> {
3377    let count = outputs.len();
3378    let mut outputs = outputs.into_iter();
3379    match (
3380        outputs.next(),
3381        outputs.next(),
3382        outputs.next(),
3383        outputs.next(),
3384    ) {
3385        (Some(u), Some(values), Some(vt), None) => Ok(vec![
3386            Tensor::from_typed::<Complex32>(u),
3387            Tensor::from_typed::<f32>(complex32_real_part_tensor(values)?),
3388            Tensor::from_typed::<Complex32>(vt),
3389        ]),
3390        _ => Err(svd_output_count_error(count)),
3391    }
3392}
3393
3394fn svd_c64_outputs_to_public_tensors(
3395    outputs: Vec<TypedTensor<Complex64>>,
3396) -> tenferro_tensor::Result<Vec<Tensor>> {
3397    let count = outputs.len();
3398    let mut outputs = outputs.into_iter();
3399    match (
3400        outputs.next(),
3401        outputs.next(),
3402        outputs.next(),
3403        outputs.next(),
3404    ) {
3405        (Some(u), Some(values), Some(vt), None) => Ok(vec![
3406            Tensor::from_typed::<Complex64>(u),
3407            Tensor::from_typed::<f64>(complex64_real_part_tensor(values)?),
3408            Tensor::from_typed::<Complex64>(vt),
3409        ]),
3410        _ => Err(svd_output_count_error(count)),
3411    }
3412}
3413
3414fn eigh_c32_outputs_to_public_tensors(
3415    outputs: Vec<TypedTensor<Complex32>>,
3416) -> tenferro_tensor::Result<Vec<Tensor>> {
3417    let count = outputs.len();
3418    let mut outputs = outputs.into_iter();
3419    match (outputs.next(), outputs.next(), outputs.next()) {
3420        (Some(values), Some(vectors), None) => Ok(vec![
3421            Tensor::from_typed::<f32>(complex32_real_part_tensor(values)?),
3422            Tensor::from_typed::<Complex32>(vectors),
3423        ]),
3424        _ => Err(eigh_output_count_error(count)),
3425    }
3426}
3427
3428fn eigh_c64_outputs_to_public_tensors(
3429    outputs: Vec<TypedTensor<Complex64>>,
3430) -> tenferro_tensor::Result<Vec<Tensor>> {
3431    let count = outputs.len();
3432    let mut outputs = outputs.into_iter();
3433    match (outputs.next(), outputs.next(), outputs.next()) {
3434        (Some(values), Some(vectors), None) => Ok(vec![
3435            Tensor::from_typed::<f64>(complex64_real_part_tensor(values)?),
3436            Tensor::from_typed::<Complex64>(vectors),
3437        ]),
3438        _ => Err(eigh_output_count_error(count)),
3439    }
3440}
3441
3442fn validate_lu_solve_prepared_shapes(
3443    lu_shape: &[usize],
3444    pivots_shape: &[usize],
3445    b_shape: &[usize],
3446) -> tenferro_tensor::Result<()> {
3447    let n = square_matrix_dim("lu_solve_prepared", lu_shape)?;
3448    let (b_rows, _) = matrix_dims("lu_solve_prepared", b_shape)?;
3449    if b_rows != n {
3450        return Err(Error::invalid_argument(
3451            "lu_solve_prepared",
3452            "rhs rows",
3453            format!("expected {n}, got {b_rows}"),
3454        ));
3455    }
3456    if lu_shape[2..] != b_shape[2..] {
3457        return Err(Error::shape_mismatch(
3458            "lu_solve_prepared",
3459            lu_shape.to_vec(),
3460            b_shape.to_vec(),
3461        ));
3462    }
3463    let mut expected_pivots = vec![n];
3464    expected_pivots.extend_from_slice(&lu_shape[2..]);
3465    if pivots_shape != expected_pivots {
3466        return Err(Error::shape_mismatch(
3467            "lu_solve_prepared",
3468            expected_pivots,
3469            pivots_shape.to_vec(),
3470        ));
3471    }
3472    Ok(())
3473}
3474
3475fn matrix_dims(op: &'static str, shape: &[usize]) -> tenferro_tensor::Result<(usize, usize)> {
3476    if shape.len() < 2 {
3477        return Err(Error::rank_mismatch(op, 2, shape.len()));
3478    }
3479    Ok((shape[0], shape[1]))
3480}
3481
3482fn square_matrix_dim(op: &'static str, shape: &[usize]) -> tenferro_tensor::Result<usize> {
3483    let (rows, cols) = matrix_dims(op, shape)?;
3484    if rows != cols {
3485        return Err(Error::shape_mismatch(op, vec![rows], vec![cols]));
3486    }
3487    Ok(rows)
3488}
3489
3490fn unsupported_pair(
3491    op: &'static str,
3492    lhs: &Tensor,
3493    rhs: &Tensor,
3494) -> tenferro_tensor::Result<Tensor> {
3495    if lhs.dtype() != rhs.dtype() {
3496        Err(Error::dtype_mismatch(op, lhs.dtype(), rhs.dtype()))
3497    } else {
3498        Err(unsupported_dtype(op, lhs.dtype()))
3499    }
3500}
3501
3502mod packed_lu;
3503
3504#[cfg(test)]
3505mod tests;