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
25macro_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 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 #[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 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 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 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
1074fn 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
1087fn 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
1101type TypedOperandPair<'a, T> = (
1103 &'a TypedTensor<T, tenferro_tensor::DynRank>,
1104 &'a TypedTensor<T, tenferro_tensor::DynRank>,
1105);
1106
1107fn 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
1124type 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
1131fn 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 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 DType::External(_) => unreachable!("linalg validates its input dtypes first"),
1446 },
1447 TensorWrite::View(view) => view,
1448 }
1449}
1450
1451fn 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 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 _ => 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#[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
3242fn 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 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;