1use tenferro_tensor::{
4 DType, DotGeneralAccumulation, Error, Result, Tensor, TensorBackend, TensorRead, TensorScalar,
5 TensorWrite, TypedTensor, TypedTensorView,
6};
7
8use crate::eager::{
9 eager_einsum_exec, eager_einsum_exec_read, eager_einsum_exec_read_into,
10 eager_einsum_exec_read_into_accum, eager_einsum_read_subscripts, eager_einsum_subscripts,
11 plan_subscripts,
12};
13use crate::{ContractionTree, EinsumSubscripts, Subscripts};
14
15const TENSOR_EINSUM_OP: &str = "TensorEinsumExt::einsum";
16const TENSOR_EINSUM_INTO_OP: &str = "TensorEinsumIntoExt::einsum_into";
17const TENSOR_READ_EINSUM_OP: &str = "TensorReadEinsumExt::einsum_read";
18const TENSOR_READ_EINSUM_INTO_OP: &str = "TensorReadEinsumIntoExt::einsum_read_into";
19const TYPED_TENSOR_EINSUM_OP: &str = "TypedTensorEinsumExt::einsum";
20const TYPED_TENSOR_EINSUM_INTO_OP: &str = "TypedTensorEinsumIntoExt::einsum_into";
21const PLAN_PREPARE_OP: &str = "ConcreteEinsumPlan::prepare";
22const PLAN_EXECUTE_OP: &str = "ConcreteEinsumPlan::execute";
23
24pub trait TensorEinsumExt {
46 fn einsum<B: TensorBackend>(&self, subscripts: &str, backend: &mut B) -> Result<Tensor>;
48
49 fn einsum_subscripts<B: TensorBackend>(
51 &self,
52 subscripts: &EinsumSubscripts,
53 backend: &mut B,
54 ) -> Result<Tensor>;
55}
56
57impl TensorEinsumExt for [&Tensor] {
58 fn einsum<B: TensorBackend>(&self, subscripts: &str, backend: &mut B) -> Result<Tensor> {
59 let subscripts = parse_subscripts(subscripts, TENSOR_EINSUM_OP)?;
60 eager_einsum_subscripts(backend, self, &subscripts)
61 }
62
63 fn einsum_subscripts<B: TensorBackend>(
64 &self,
65 subscripts: &EinsumSubscripts,
66 backend: &mut B,
67 ) -> Result<Tensor> {
68 let subscripts = Subscripts::from(subscripts);
69 eager_einsum_subscripts(backend, self, &subscripts)
70 }
71}
72
73impl<const N: usize> TensorEinsumExt for [&Tensor; N] {
74 fn einsum<B: TensorBackend>(&self, subscripts: &str, backend: &mut B) -> Result<Tensor> {
75 self.as_slice().einsum(subscripts, backend)
76 }
77
78 fn einsum_subscripts<B: TensorBackend>(
79 &self,
80 subscripts: &EinsumSubscripts,
81 backend: &mut B,
82 ) -> Result<Tensor> {
83 self.as_slice().einsum_subscripts(subscripts, backend)
84 }
85}
86
87pub trait TensorEinsumIntoExt {
89 fn einsum_into<B: TensorBackend>(
91 &self,
92 subscripts: &str,
93 backend: &mut B,
94 out: TensorWrite<'_>,
95 ) -> Result<()>;
96
97 fn einsum_into_subscripts<B: TensorBackend>(
99 &self,
100 subscripts: &EinsumSubscripts,
101 backend: &mut B,
102 out: TensorWrite<'_>,
103 ) -> Result<()>;
104}
105
106impl TensorEinsumIntoExt for [&Tensor] {
107 fn einsum_into<B: TensorBackend>(
108 &self,
109 subscripts: &str,
110 backend: &mut B,
111 out: TensorWrite<'_>,
112 ) -> Result<()> {
113 let subscripts = parse_subscripts(subscripts, TENSOR_EINSUM_INTO_OP)?;
114 tensor_einsum_into_subscripts(backend, self, &subscripts, out, TENSOR_EINSUM_INTO_OP)
115 }
116
117 fn einsum_into_subscripts<B: TensorBackend>(
118 &self,
119 subscripts: &EinsumSubscripts,
120 backend: &mut B,
121 out: TensorWrite<'_>,
122 ) -> Result<()> {
123 let subscripts = Subscripts::from(subscripts);
124 tensor_einsum_into_subscripts(backend, self, &subscripts, out, TENSOR_EINSUM_INTO_OP)
125 }
126}
127
128impl<const N: usize> TensorEinsumIntoExt for [&Tensor; N] {
129 fn einsum_into<B: TensorBackend>(
130 &self,
131 subscripts: &str,
132 backend: &mut B,
133 out: TensorWrite<'_>,
134 ) -> Result<()> {
135 self.as_slice().einsum_into(subscripts, backend, out)
136 }
137
138 fn einsum_into_subscripts<B: TensorBackend>(
139 &self,
140 subscripts: &EinsumSubscripts,
141 backend: &mut B,
142 out: TensorWrite<'_>,
143 ) -> Result<()> {
144 self.as_slice()
145 .einsum_into_subscripts(subscripts, backend, out)
146 }
147}
148
149pub trait TypedTensorEinsumExt<T: TensorScalar> {
170 fn einsum<B: TensorBackend>(&self, subscripts: &str, backend: &mut B)
172 -> Result<TypedTensor<T>>;
173
174 fn einsum_subscripts<B: TensorBackend>(
176 &self,
177 subscripts: &EinsumSubscripts,
178 backend: &mut B,
179 ) -> Result<TypedTensor<T>>;
180}
181
182impl<T: TensorScalar> TypedTensorEinsumExt<T> for [&TypedTensor<T>] {
183 fn einsum<B: TensorBackend>(
184 &self,
185 subscripts: &str,
186 backend: &mut B,
187 ) -> Result<TypedTensor<T>> {
188 let subscripts = parse_subscripts(subscripts, TYPED_TENSOR_EINSUM_OP)?;
189 typed_einsum_subscripts(backend, self, &subscripts, TYPED_TENSOR_EINSUM_OP)
190 }
191
192 fn einsum_subscripts<B: TensorBackend>(
193 &self,
194 subscripts: &EinsumSubscripts,
195 backend: &mut B,
196 ) -> Result<TypedTensor<T>> {
197 let subscripts = Subscripts::from(subscripts);
198 typed_einsum_subscripts(backend, self, &subscripts, TYPED_TENSOR_EINSUM_OP)
199 }
200}
201
202impl<T: TensorScalar, const N: usize> TypedTensorEinsumExt<T> for [&TypedTensor<T>; N] {
203 fn einsum<B: TensorBackend>(
204 &self,
205 subscripts: &str,
206 backend: &mut B,
207 ) -> Result<TypedTensor<T>> {
208 self.as_slice().einsum(subscripts, backend)
209 }
210
211 fn einsum_subscripts<B: TensorBackend>(
212 &self,
213 subscripts: &EinsumSubscripts,
214 backend: &mut B,
215 ) -> Result<TypedTensor<T>> {
216 self.as_slice().einsum_subscripts(subscripts, backend)
217 }
218}
219
220impl<'a, T: TensorScalar> TypedTensorEinsumExt<T> for [TypedTensorView<'a, T>] {
221 fn einsum<B: TensorBackend>(
222 &self,
223 subscripts: &str,
224 backend: &mut B,
225 ) -> Result<TypedTensor<T>> {
226 let subscripts = parse_subscripts(subscripts, TYPED_TENSOR_EINSUM_OP)?;
227 typed_view_einsum_subscripts(backend, self, &subscripts, TYPED_TENSOR_EINSUM_OP)
228 }
229
230 fn einsum_subscripts<B: TensorBackend>(
231 &self,
232 subscripts: &EinsumSubscripts,
233 backend: &mut B,
234 ) -> Result<TypedTensor<T>> {
235 let subscripts = Subscripts::from(subscripts);
236 typed_view_einsum_subscripts(backend, self, &subscripts, TYPED_TENSOR_EINSUM_OP)
237 }
238}
239
240impl<'a, T: TensorScalar, const N: usize> TypedTensorEinsumExt<T> for [TypedTensorView<'a, T>; N] {
241 fn einsum<B: TensorBackend>(
242 &self,
243 subscripts: &str,
244 backend: &mut B,
245 ) -> Result<TypedTensor<T>> {
246 self.as_slice().einsum(subscripts, backend)
247 }
248
249 fn einsum_subscripts<B: TensorBackend>(
250 &self,
251 subscripts: &EinsumSubscripts,
252 backend: &mut B,
253 ) -> Result<TypedTensor<T>> {
254 self.as_slice().einsum_subscripts(subscripts, backend)
255 }
256}
257
258pub trait TypedTensorEinsumIntoExt<T: TensorScalar> {
260 fn einsum_into<B: TensorBackend>(
262 &self,
263 subscripts: &str,
264 backend: &mut B,
265 out: &mut TypedTensor<T>,
266 ) -> Result<()>;
267
268 fn einsum_into_subscripts<B: TensorBackend>(
270 &self,
271 subscripts: &EinsumSubscripts,
272 backend: &mut B,
273 out: &mut TypedTensor<T>,
274 ) -> Result<()>;
275}
276
277impl<T: TensorScalar> TypedTensorEinsumIntoExt<T> for [&TypedTensor<T>] {
278 fn einsum_into<B: TensorBackend>(
279 &self,
280 subscripts: &str,
281 backend: &mut B,
282 out: &mut TypedTensor<T>,
283 ) -> Result<()> {
284 let subscripts = parse_subscripts(subscripts, TYPED_TENSOR_EINSUM_INTO_OP)?;
285 typed_einsum_into_subscripts(backend, self, &subscripts, out, TYPED_TENSOR_EINSUM_INTO_OP)
286 }
287
288 fn einsum_into_subscripts<B: TensorBackend>(
289 &self,
290 subscripts: &EinsumSubscripts,
291 backend: &mut B,
292 out: &mut TypedTensor<T>,
293 ) -> Result<()> {
294 let subscripts = Subscripts::from(subscripts);
295 typed_einsum_into_subscripts(backend, self, &subscripts, out, TYPED_TENSOR_EINSUM_INTO_OP)
296 }
297}
298
299impl<T: TensorScalar, const N: usize> TypedTensorEinsumIntoExt<T> for [&TypedTensor<T>; N] {
300 fn einsum_into<B: TensorBackend>(
301 &self,
302 subscripts: &str,
303 backend: &mut B,
304 out: &mut TypedTensor<T>,
305 ) -> Result<()> {
306 self.as_slice().einsum_into(subscripts, backend, out)
307 }
308
309 fn einsum_into_subscripts<B: TensorBackend>(
310 &self,
311 subscripts: &EinsumSubscripts,
312 backend: &mut B,
313 out: &mut TypedTensor<T>,
314 ) -> Result<()> {
315 self.as_slice()
316 .einsum_into_subscripts(subscripts, backend, out)
317 }
318}
319
320impl<'a, T: TensorScalar> TypedTensorEinsumIntoExt<T> for [TypedTensorView<'a, T>] {
321 fn einsum_into<B: TensorBackend>(
322 &self,
323 subscripts: &str,
324 backend: &mut B,
325 out: &mut TypedTensor<T>,
326 ) -> Result<()> {
327 let subscripts = parse_subscripts(subscripts, TYPED_TENSOR_EINSUM_INTO_OP)?;
328 typed_view_einsum_into_subscripts(
329 backend,
330 self,
331 &subscripts,
332 out,
333 TYPED_TENSOR_EINSUM_INTO_OP,
334 )
335 }
336
337 fn einsum_into_subscripts<B: TensorBackend>(
338 &self,
339 subscripts: &EinsumSubscripts,
340 backend: &mut B,
341 out: &mut TypedTensor<T>,
342 ) -> Result<()> {
343 let subscripts = Subscripts::from(subscripts);
344 typed_view_einsum_into_subscripts(
345 backend,
346 self,
347 &subscripts,
348 out,
349 TYPED_TENSOR_EINSUM_INTO_OP,
350 )
351 }
352}
353
354impl<'a, T: TensorScalar, const N: usize> TypedTensorEinsumIntoExt<T>
355 for [TypedTensorView<'a, T>; N]
356{
357 fn einsum_into<B: TensorBackend>(
358 &self,
359 subscripts: &str,
360 backend: &mut B,
361 out: &mut TypedTensor<T>,
362 ) -> Result<()> {
363 self.as_slice().einsum_into(subscripts, backend, out)
364 }
365
366 fn einsum_into_subscripts<B: TensorBackend>(
367 &self,
368 subscripts: &EinsumSubscripts,
369 backend: &mut B,
370 out: &mut TypedTensor<T>,
371 ) -> Result<()> {
372 self.as_slice()
373 .einsum_into_subscripts(subscripts, backend, out)
374 }
375}
376
377pub trait TensorReadEinsumExt {
404 fn einsum_read<B: TensorBackend>(&self, subscripts: &str, backend: &mut B) -> Result<Tensor>;
406
407 fn einsum_read_subscripts<B: TensorBackend>(
410 &self,
411 subscripts: &EinsumSubscripts,
412 backend: &mut B,
413 ) -> Result<Tensor>;
414}
415
416impl<'a> TensorReadEinsumExt for [TensorRead<'a>] {
417 fn einsum_read<B: TensorBackend>(&self, subscripts: &str, backend: &mut B) -> Result<Tensor> {
418 let subscripts = parse_subscripts(subscripts, TENSOR_READ_EINSUM_OP)?;
419 eager_einsum_read_subscripts(backend, self, &subscripts)
420 }
421
422 fn einsum_read_subscripts<B: TensorBackend>(
423 &self,
424 subscripts: &EinsumSubscripts,
425 backend: &mut B,
426 ) -> Result<Tensor> {
427 let subscripts = Subscripts::from(subscripts);
428 eager_einsum_read_subscripts(backend, self, &subscripts)
429 }
430}
431
432impl<'a, const N: usize> TensorReadEinsumExt for [TensorRead<'a>; N] {
433 fn einsum_read<B: TensorBackend>(&self, subscripts: &str, backend: &mut B) -> Result<Tensor> {
434 self.as_slice().einsum_read(subscripts, backend)
435 }
436
437 fn einsum_read_subscripts<B: TensorBackend>(
438 &self,
439 subscripts: &EinsumSubscripts,
440 backend: &mut B,
441 ) -> Result<Tensor> {
442 self.as_slice().einsum_read_subscripts(subscripts, backend)
443 }
444}
445
446pub trait TensorReadEinsumIntoExt {
448 fn einsum_read_into<B: TensorBackend>(
450 &self,
451 subscripts: &str,
452 backend: &mut B,
453 out: TensorWrite<'_>,
454 ) -> Result<()>;
455
456 fn einsum_read_into_subscripts<B: TensorBackend>(
458 &self,
459 subscripts: &EinsumSubscripts,
460 backend: &mut B,
461 out: TensorWrite<'_>,
462 ) -> Result<()>;
463}
464
465impl<'a> TensorReadEinsumIntoExt for [TensorRead<'a>] {
466 fn einsum_read_into<B: TensorBackend>(
467 &self,
468 subscripts: &str,
469 backend: &mut B,
470 out: TensorWrite<'_>,
471 ) -> Result<()> {
472 let subscripts = parse_subscripts(subscripts, TENSOR_READ_EINSUM_INTO_OP)?;
473 tensor_read_einsum_into_subscripts(
474 backend,
475 self,
476 &subscripts,
477 out,
478 TENSOR_READ_EINSUM_INTO_OP,
479 )
480 }
481
482 fn einsum_read_into_subscripts<B: TensorBackend>(
483 &self,
484 subscripts: &EinsumSubscripts,
485 backend: &mut B,
486 out: TensorWrite<'_>,
487 ) -> Result<()> {
488 let subscripts = Subscripts::from(subscripts);
489 tensor_read_einsum_into_subscripts(
490 backend,
491 self,
492 &subscripts,
493 out,
494 TENSOR_READ_EINSUM_INTO_OP,
495 )
496 }
497}
498
499impl<'a, const N: usize> TensorReadEinsumIntoExt for [TensorRead<'a>; N] {
500 fn einsum_read_into<B: TensorBackend>(
501 &self,
502 subscripts: &str,
503 backend: &mut B,
504 out: TensorWrite<'_>,
505 ) -> Result<()> {
506 self.as_slice().einsum_read_into(subscripts, backend, out)
507 }
508
509 fn einsum_read_into_subscripts<B: TensorBackend>(
510 &self,
511 subscripts: &EinsumSubscripts,
512 backend: &mut B,
513 out: TensorWrite<'_>,
514 ) -> Result<()> {
515 self.as_slice()
516 .einsum_read_into_subscripts(subscripts, backend, out)
517 }
518}
519
520#[derive(Debug)]
544pub struct ConcreteEinsumPlan {
545 tree: ContractionTree,
546 inputs: Vec<ConcreteEinsumInputSpec>,
547}
548
549impl ConcreteEinsumPlan {
550 pub fn prepare<'a, I>(inputs: I, subscripts: &str) -> Result<Self>
553 where
554 I: AsRef<[&'a Tensor]>,
555 {
556 let subscripts = parse_subscripts(subscripts, PLAN_PREPARE_OP)?;
557 Self::prepare_subscripts_internal(input_specs(inputs.as_ref()), &subscripts)
558 }
559
560 pub fn prepare_subscripts<'a, I>(inputs: I, subscripts: &EinsumSubscripts) -> Result<Self>
563 where
564 I: AsRef<[&'a Tensor]>,
565 {
566 let subscripts = Subscripts::from(subscripts);
567 Self::prepare_subscripts_internal(input_specs(inputs.as_ref()), &subscripts)
568 }
569
570 pub fn prepare_typed<'a, T, I>(inputs: I, subscripts: &str) -> Result<Self>
572 where
573 T: TensorScalar,
574 I: AsRef<[&'a TypedTensor<T>]>,
575 {
576 let subscripts = parse_subscripts(subscripts, PLAN_PREPARE_OP)?;
577 Self::prepare_subscripts_internal(typed_input_specs(inputs.as_ref()), &subscripts)
578 }
579
580 pub fn prepare_typed_subscripts<'a, T, I>(
583 inputs: I,
584 subscripts: &EinsumSubscripts,
585 ) -> Result<Self>
586 where
587 T: TensorScalar,
588 I: AsRef<[&'a TypedTensor<T>]>,
589 {
590 let subscripts = Subscripts::from(subscripts);
591 Self::prepare_subscripts_internal(typed_input_specs(inputs.as_ref()), &subscripts)
592 }
593
594 pub fn prepare_read<'a, I>(inputs: I, subscripts: &str) -> Result<Self>
596 where
597 I: AsRef<[TensorRead<'a>]>,
598 {
599 let subscripts = parse_subscripts(subscripts, PLAN_PREPARE_OP)?;
600 Self::prepare_subscripts_internal(read_input_specs(inputs.as_ref()), &subscripts)
601 }
602
603 pub fn prepare_read_subscripts<'a, I>(inputs: I, subscripts: &EinsumSubscripts) -> Result<Self>
606 where
607 I: AsRef<[TensorRead<'a>]>,
608 {
609 let subscripts = Subscripts::from(subscripts);
610 Self::prepare_subscripts_internal(read_input_specs(inputs.as_ref()), &subscripts)
611 }
612
613 pub fn execute<'a, I, B>(&self, inputs: I, backend: &mut B) -> Result<Tensor>
615 where
616 I: AsRef<[&'a Tensor]>,
617 B: TensorBackend,
618 {
619 let inputs = inputs.as_ref();
620 self.validate_inputs(&input_specs(inputs), PLAN_EXECUTE_OP)?;
621 backend.with_backend_session(|exec| eager_einsum_exec(exec, inputs, &self.tree))
622 }
623
624 pub fn execute_typed<'a, T, I, B>(&self, inputs: I, backend: &mut B) -> Result<TypedTensor<T>>
626 where
627 T: TensorScalar,
628 I: AsRef<[&'a TypedTensor<T>]>,
629 B: TensorBackend,
630 {
631 let inputs = inputs.as_ref();
632 self.validate_inputs(&typed_input_specs(inputs), PLAN_EXECUTE_OP)?;
633 let reads: Vec<_> = inputs.iter().map(|tensor| T::tensor_read(tensor)).collect();
634 let result = backend
635 .with_backend_session(|exec| eager_einsum_exec_read(exec, &reads, &self.tree))?;
636 into_typed_result(result, PLAN_EXECUTE_OP)
637 }
638
639 pub fn execute_read<'a, I, B>(&self, inputs: I, backend: &mut B) -> Result<Tensor>
641 where
642 I: AsRef<[TensorRead<'a>]>,
643 B: TensorBackend,
644 {
645 let inputs = inputs.as_ref();
646 self.validate_inputs(&read_input_specs(inputs), PLAN_EXECUTE_OP)?;
647 backend.with_backend_session(|exec| eager_einsum_exec_read(exec, inputs, &self.tree))
648 }
649
650 pub fn execute_into<'a, I, B>(
652 &self,
653 inputs: I,
654 backend: &mut B,
655 out: TensorWrite<'_>,
656 ) -> Result<()>
657 where
658 I: AsRef<[&'a Tensor]>,
659 B: TensorBackend,
660 {
661 let inputs = inputs.as_ref();
662 let specs = input_specs(inputs);
663 self.validate_inputs(&specs, PLAN_EXECUTE_OP)?;
664 validate_output(&self.inputs, &self.tree, &out, PLAN_EXECUTE_OP)?;
665 let reads: Vec<_> = inputs
666 .iter()
667 .map(|tensor| TensorRead::from_tensor(tensor))
668 .collect();
669 backend
670 .with_backend_session(|exec| eager_einsum_exec_read_into(exec, &reads, &self.tree, out))
671 }
672
673 pub fn execute_typed_into<'a, T, I, B>(
675 &self,
676 inputs: I,
677 backend: &mut B,
678 out: &mut TypedTensor<T>,
679 ) -> Result<()>
680 where
681 T: TensorScalar,
682 I: AsRef<[&'a TypedTensor<T>]>,
683 B: TensorBackend,
684 {
685 let inputs = inputs.as_ref();
686 let specs = typed_input_specs(inputs);
687 self.validate_inputs(&specs, PLAN_EXECUTE_OP)?;
688 let out = T::tensor_write(out);
689 validate_output(&self.inputs, &self.tree, &out, PLAN_EXECUTE_OP)?;
690 let reads: Vec<_> = inputs.iter().map(|tensor| T::tensor_read(tensor)).collect();
691 backend
692 .with_backend_session(|exec| eager_einsum_exec_read_into(exec, &reads, &self.tree, out))
693 }
694
695 pub fn execute_read_into<'a, I, B>(
697 &self,
698 inputs: I,
699 backend: &mut B,
700 out: TensorWrite<'_>,
701 ) -> Result<()>
702 where
703 I: AsRef<[TensorRead<'a>]>,
704 B: TensorBackend,
705 {
706 let inputs = inputs.as_ref();
707 let specs = read_input_specs(inputs);
708 self.validate_inputs(&specs, PLAN_EXECUTE_OP)?;
709 validate_output(&self.inputs, &self.tree, &out, PLAN_EXECUTE_OP)?;
710 backend
711 .with_backend_session(|exec| eager_einsum_exec_read_into(exec, inputs, &self.tree, out))
712 }
713
714 pub fn execute_read_into_accum<'a, I, B>(
743 &self,
744 inputs: I,
745 backend: &mut B,
746 accumulation: DotGeneralAccumulation,
747 out: TensorWrite<'_>,
748 ) -> Result<()>
749 where
750 I: AsRef<[TensorRead<'a>]>,
751 B: TensorBackend,
752 {
753 let inputs = inputs.as_ref();
754 let specs = read_input_specs(inputs);
755 self.validate_inputs(&specs, PLAN_EXECUTE_OP)?;
756 validate_output(&self.inputs, &self.tree, &out, PLAN_EXECUTE_OP)?;
757 backend.with_backend_session(|exec| {
758 eager_einsum_exec_read_into_accum(exec, inputs, &self.tree, accumulation, out)
759 })
760 }
761
762 fn prepare_subscripts_internal(
763 inputs: Vec<ConcreteEinsumInputSpec>,
764 subscripts: &Subscripts,
765 ) -> Result<Self> {
766 let shapes: Vec<&[usize]> = inputs.iter().map(|input| input.shape.as_slice()).collect();
767 let tree = plan_subscripts(subscripts, &shapes)?;
768 Ok(Self { tree, inputs })
769 }
770
771 fn validate_inputs(&self, actual: &[ConcreteEinsumInputSpec], op: &'static str) -> Result<()> {
772 if actual.len() != self.inputs.len() {
773 return Err(Error::InvalidConfig {
774 op,
775 message: format!(
776 "prepared einsum expects {} inputs, got {}",
777 self.inputs.len(),
778 actual.len()
779 ),
780 });
781 }
782
783 for (expected, actual) in self.inputs.iter().zip(actual.iter()) {
784 if expected.dtype != actual.dtype {
785 return Err(Error::DTypeMismatch {
786 op,
787 lhs: expected.dtype,
788 rhs: actual.dtype,
789 });
790 }
791 if expected.shape != actual.shape {
792 return Err(Error::ShapeMismatch {
793 op,
794 lhs: expected.shape.clone(),
795 rhs: actual.shape.clone(),
796 });
797 }
798 }
799
800 Ok(())
801 }
802}
803
804#[derive(Clone, Debug)]
805struct ConcreteEinsumInputSpec {
806 dtype: DType,
807 shape: Vec<usize>,
808}
809
810fn parse_subscripts(subscripts: &str, op: &'static str) -> Result<Subscripts> {
811 Subscripts::parse(subscripts).map_err(|err| Error::InvalidConfig {
812 op,
813 message: format!("invalid subscripts: {err}"),
814 })
815}
816
817fn input_specs(inputs: &[&Tensor]) -> Vec<ConcreteEinsumInputSpec> {
818 inputs
819 .iter()
820 .map(|tensor| ConcreteEinsumInputSpec {
821 dtype: tensor.dtype(),
822 shape: tensor.shape().to_vec(),
823 })
824 .collect()
825}
826
827fn typed_input_specs<T: TensorScalar>(inputs: &[&TypedTensor<T>]) -> Vec<ConcreteEinsumInputSpec> {
828 inputs
829 .iter()
830 .map(|tensor| ConcreteEinsumInputSpec {
831 dtype: T::dtype(),
832 shape: tensor.shape().to_vec(),
833 })
834 .collect()
835}
836
837fn typed_view_input_specs<T: TensorScalar>(
838 inputs: &[TypedTensorView<'_, T>],
839) -> Vec<ConcreteEinsumInputSpec> {
840 inputs
841 .iter()
842 .map(|tensor| ConcreteEinsumInputSpec {
843 dtype: T::dtype(),
844 shape: tensor.shape().to_vec(),
845 })
846 .collect()
847}
848
849fn read_input_specs(inputs: &[TensorRead<'_>]) -> Vec<ConcreteEinsumInputSpec> {
850 inputs
851 .iter()
852 .map(|tensor| ConcreteEinsumInputSpec {
853 dtype: tensor.dtype(),
854 shape: tensor.shape().to_vec(),
855 })
856 .collect()
857}
858
859fn typed_view_einsum_subscripts<T: TensorScalar>(
860 backend: &mut impl TensorBackend,
861 inputs: &[TypedTensorView<'_, T>],
862 subscripts: &Subscripts,
863 op: &'static str,
864) -> Result<TypedTensor<T>> {
865 let reads: Vec<_> = inputs
866 .iter()
867 .cloned()
868 .map(|view| TensorRead::from_view(T::tensor_view(view)))
869 .collect();
870 let result = eager_einsum_read_subscripts(backend, &reads, subscripts)?;
871 into_typed_result(result, op)
872}
873
874fn tensor_einsum_into_subscripts(
875 backend: &mut impl TensorBackend,
876 inputs: &[&Tensor],
877 subscripts: &Subscripts,
878 out: TensorWrite<'_>,
879 op: &'static str,
880) -> Result<()> {
881 let specs = input_specs(inputs);
882 let plan = ConcreteEinsumPlan::prepare_subscripts_internal(specs.clone(), subscripts)?;
883 validate_output(&specs, &plan.tree, &out, op)?;
884 let reads: Vec<_> = inputs
885 .iter()
886 .map(|tensor| TensorRead::from_tensor(tensor))
887 .collect();
888 backend.with_backend_session(|exec| eager_einsum_exec_read_into(exec, &reads, &plan.tree, out))
889}
890
891fn typed_view_einsum_into_subscripts<T: TensorScalar>(
892 backend: &mut impl TensorBackend,
893 inputs: &[TypedTensorView<'_, T>],
894 subscripts: &Subscripts,
895 out: &mut TypedTensor<T>,
896 op: &'static str,
897) -> Result<()> {
898 let specs = typed_view_input_specs(inputs);
899 let plan = ConcreteEinsumPlan::prepare_subscripts_internal(specs.clone(), subscripts)?;
900 let out = T::tensor_write(out);
901 validate_output(&specs, &plan.tree, &out, op)?;
902 let reads: Vec<_> = inputs
903 .iter()
904 .cloned()
905 .map(|view| TensorRead::from_view(T::tensor_view(view)))
906 .collect();
907 backend.with_backend_session(|exec| eager_einsum_exec_read_into(exec, &reads, &plan.tree, out))
908}
909
910fn typed_einsum_into_subscripts<T: TensorScalar>(
911 backend: &mut impl TensorBackend,
912 inputs: &[&TypedTensor<T>],
913 subscripts: &Subscripts,
914 out: &mut TypedTensor<T>,
915 op: &'static str,
916) -> Result<()> {
917 let specs = typed_input_specs(inputs);
918 let plan = ConcreteEinsumPlan::prepare_subscripts_internal(specs.clone(), subscripts)?;
919 let out = T::tensor_write(out);
920 validate_output(&specs, &plan.tree, &out, op)?;
921 let reads: Vec<_> = inputs.iter().map(|tensor| T::tensor_read(tensor)).collect();
922 backend.with_backend_session(|exec| eager_einsum_exec_read_into(exec, &reads, &plan.tree, out))
923}
924
925fn tensor_read_einsum_into_subscripts(
926 backend: &mut impl TensorBackend,
927 inputs: &[TensorRead<'_>],
928 subscripts: &Subscripts,
929 out: TensorWrite<'_>,
930 op: &'static str,
931) -> Result<()> {
932 let specs = read_input_specs(inputs);
933 let plan = ConcreteEinsumPlan::prepare_subscripts_internal(specs.clone(), subscripts)?;
934 validate_output(&specs, &plan.tree, &out, op)?;
935 backend.with_backend_session(|exec| eager_einsum_exec_read_into(exec, inputs, &plan.tree, out))
936}
937
938fn validate_output(
939 inputs: &[ConcreteEinsumInputSpec],
940 tree: &ContractionTree,
941 out: &TensorWrite<'_>,
942 op: &'static str,
943) -> Result<()> {
944 let expected = output_spec(inputs, tree, op)?;
945 if out.dtype() != expected.dtype {
946 return Err(Error::DTypeMismatch {
947 op,
948 lhs: out.dtype(),
949 rhs: expected.dtype,
950 });
951 }
952 if out.shape() != expected.shape.as_slice() {
953 return Err(Error::ShapeMismatch {
954 op,
955 lhs: out.shape().to_vec(),
956 rhs: expected.shape,
957 });
958 }
959 Ok(())
960}
961
962fn output_spec(
963 inputs: &[ConcreteEinsumInputSpec],
964 tree: &ContractionTree,
965 op: &'static str,
966) -> Result<ConcreteEinsumInputSpec> {
967 let dtype = inputs
968 .first()
969 .ok_or_else(|| Error::InvalidConfig {
970 op,
971 message: "einsum requires at least one input tensor".to_string(),
972 })?
973 .dtype;
974 for input in inputs {
975 if input.dtype != dtype {
976 return Err(Error::DTypeMismatch {
977 op,
978 lhs: dtype,
979 rhs: input.dtype,
980 });
981 }
982 }
983
984 let mut output_shape = Vec::with_capacity(tree.subscripts.output.len());
985 for &label in &tree.subscripts.output {
986 let mut found = None;
987 for (input, labels) in inputs.iter().zip(tree.subscripts.inputs.iter()) {
988 if labels.len() != input.shape.len() {
989 return Err(Error::RankMismatch {
990 op,
991 expected: labels.len(),
992 actual: input.shape.len(),
993 });
994 }
995 if let Some(axis) = labels.iter().position(|candidate| *candidate == label) {
996 found = Some(input.shape[axis]);
997 break;
998 }
999 }
1000 let Some(extent) = found else {
1001 return Err(Error::InvalidConfig {
1002 op,
1003 message: format!("output label {label} is missing from inputs"),
1004 });
1005 };
1006 output_shape.push(extent);
1007 }
1008 Ok(ConcreteEinsumInputSpec {
1009 dtype,
1010 shape: output_shape,
1011 })
1012}
1013
1014fn typed_einsum_subscripts<T: TensorScalar>(
1015 backend: &mut impl TensorBackend,
1016 inputs: &[&TypedTensor<T>],
1017 subscripts: &Subscripts,
1018 op: &'static str,
1019) -> Result<TypedTensor<T>> {
1020 let reads: Vec<_> = inputs.iter().map(|tensor| T::tensor_read(tensor)).collect();
1021 let result = eager_einsum_read_subscripts(backend, &reads, subscripts)?;
1022 into_typed_result(result, op)
1023}
1024
1025pub(crate) fn into_typed_result<T: TensorScalar>(
1026 result: Tensor,
1027 op: &'static str,
1028) -> Result<TypedTensor<T>> {
1029 let actual = result.dtype();
1030 T::into_typed(result).map_err(|_| Error::DTypeMismatch {
1031 op,
1032 lhs: T::dtype(),
1033 rhs: actual,
1034 })
1035}