Skip to main content

burn_tensor/tensor/api/einsum/
mod.rs

1//! Einstein summation through existing differentiable tensor operations.
2
3use core::marker::PhantomData;
4
5use crate::{Float, Tensor, kind::Numeric, ops::BridgeTensor};
6
7mod execution;
8
9/// An einsum operand whose rank is determined at runtime.
10///
11/// Allows [`Tensor::einsum`] to accept tensors of different ranks.
12/// All operands must have the same tensor kind, dtype and device.
13#[derive(Clone, Debug)]
14pub struct EinsumOperand<K: Numeric = Float> {
15    primitive: BridgeTensor,
16    kind: PhantomData<K>,
17}
18
19impl<const D: usize, K: Numeric> From<Tensor<D, K>> for EinsumOperand<K> {
20    fn from(tensor: Tensor<D, K>) -> Self {
21        Self {
22            primitive: tensor.primitive,
23            kind: PhantomData,
24        }
25    }
26}
27
28impl<const D: usize, K: Numeric> From<&Tensor<D, K>> for EinsumOperand<K> {
29    fn from(tensor: &Tensor<D, K>) -> Self {
30        tensor.clone().into()
31    }
32}
33
34impl<const D: usize, K: Numeric> Tensor<D, K> {
35    /// Computes Einstein summation with a runtime equation.
36    ///
37    /// Supports explicit (`"ij,jk->ik"`) and implicit (`"ij,jk"`) outputs,
38    /// any number of operands, repeated-label diagonals, and broadcasting.
39    /// See [`einsum!`](crate::einsum) for equation syntax and broadcasting rules.
40    /// The macro also checks literal equations and statically determined ranks
41    /// at compile time.
42    ///
43    /// Operands are contracted from left to right using multiplication,
44    /// reductions, and batched matrix multiplication. No optimized contraction
45    /// path is searched. Float operations retain automatic differentiation.
46    ///
47    /// Burn represents scalars as rank-one tensors with shape `[1]`: a scalar
48    /// result requires `D = 1`, and an empty input subscript accepts shape `[1]`.
49    /// Other inputs must have exactly the rank described by their subscripts.
50    ///
51    /// # Example
52    ///
53    /// ```
54    /// use burn_tensor::Tensor;
55    /// let device = Default::default();
56    /// let matrix = Tensor::<2>::from_floats([[1., 2.], [3., 4.]], &device);
57    /// let vector = Tensor::<1>::from_floats([10., 20.], &device);
58    /// let result = Tensor::<1>::einsum("ij,j->i", [matrix.into(), vector.into()]);
59    /// assert_eq!(result.into_data().try_to_vec::<f32>().unwrap(), [50., 110.]);
60    /// ```
61    ///
62    /// # Panics
63    ///
64    /// Panics for an invalid equation, no operands, incompatible ranks or
65    /// dimensions, unequal repeated-label dimensions, mismatched devices or
66    /// dtypes, quantized operands, or an incorrect output rank `D`.
67    pub fn einsum(equation: &str, operands: impl IntoIterator<Item = EinsumOperand<K>>) -> Self {
68        let equation =
69            burn_einsum::parse(equation).unwrap_or_else(|error| panic!("einsum: {error}"));
70        execution::execute(&equation.plan(), operands)
71    }
72}
73
74/// Implementation details used by the generated `einsum!` operation chain.
75#[doc(hidden)]
76pub mod __einsum {
77    pub use super::execution::{
78        alignment_shape, axes, broadcast_contract, can_contract_early, can_matmul, contract_early,
79        matmul_shapes, prepare, validate_broadcast,
80    };
81    pub use burn_einsum::Axis;
82}