qudit_expr/
index.rs

1/// A unique identifier for a tensor index.
2///
3/// Tensor indices are identified in two distinct locations by this type alias:
4///
5/// 1) Local Tensor indices: `TensorExpression` objects generate tensor data
6///    in a specific order. The ordering of their local tensor indices is identified
7///    by their local index ids. These do not change unless the `TensorExpression`'s
8///    internal symbolic expression changes. Therefore, it is important to track
9///    it's local tensor index ordering, so we can accurately perform contractions
10///    dictated by the network. See `[TensorIndex]` for more information.
11///
12/// 2) Global Tensor Network indices: The entire network will have a list of
13///    indices and each will have its own id. See `[NetworkIndex]` for more
14///    information.
15pub type IndexId = usize;
16
17/// Represents the size or dimension of an index in a tensor network.
18///
19/// This type alias is used to specify the number of possible values for an index.
20/// From a quantum circuit perspective, Input and Output indices usually have
21/// size equal to radix of the qudit captured by the leg (2 for qubits, 3 for
22/// qutrits, etc); however, during contraction simplification, many edges can
23/// be grouped together to simplify the number of contractions, leading to a
24/// larger index size.
25pub type IndexSize = usize;
26
27/// Represents a weighted-indexed edge in a tensor network.
28///
29/// This provides the core index information for contraction ordering solvers.
30pub type WeightedIndex = (IndexId, IndexSize);
31
32/// IndexDirection represents a tensor's leg direction from the quantum circuit perspective.
33///
34/// In the context of quantum circuits and tensor networks, indices (or "legs") of a tensor
35/// are categorized by their direction to signify their role in an operation.
36///
37/// While tensors may conceptually have multiple indices (legs), in the `qudit-expr` library,
38/// `TensorExpression` objects are always generated as 1-D, 2-D, or 3-D tensors. This enum
39/// links the conceptual/graphical tensor indices to the generated expression's dimensions by grouping
40/// them along directions. So all `Input`, `Output`, and `Batch` legs correspond to a
41/// specific dimension of the generated tensor. This tensor network library can
42/// use this to predict and manipulate the shape of a generated tensor.
43///
44/// For example, kets capture quantum states, and are represented conceptually
45/// by column vectors. In the tensor network perspective, they are represented by
46/// a tensor with only `Output` indices. They have as many indices as there are
47/// qudits in the state and the size of each index is the radix of the qudit.
48/// For qubits, the radix is 2, so all indices would have dimension 2. However,
49/// when generated in the `qudit-expr` library, they are generated as a vector.
50/// The `IndexDirection` is what is used by the `qudit-tensor` library to expect
51/// a vector shape, since a ket tensor will only have indices in one direction.
52///
53/// Similarly, gates which capture quantum operations, are represented by unitary
54/// matrices. As a tensor, they have indices that point in both `Input` and `Output`
55/// directions. Even though there will likely be more than 2 indices, the `qudit-tensor`
56/// library expects a matrix to be generated by the underlying `TensorExpression`
57/// because of the two distinct directions.
58///
59/// Batch indices are used for measurements/Kraus operators or for batch executions
60/// of a network.
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
62pub enum IndexDirection {
63    /// For gradients and hessians
64    Derivative,
65
66    /// Batch index of a tensor. These are typically represented in quantum circuits
67    /// through interactions with classical systems through measurements and/or
68    /// feed-forward operations. Additionally, they can capture batch computations
69    /// of a network.
70    Batch,
71
72    /// Output leg of a tensor, corresponds to rows of an operation. In circuit
73    /// diagrams, these represent the wires leaving a gate or ket.
74    Output,
75
76    /// Input leg of a tensor, corresponds to columns of an operation. In circuit
77    /// diagrams, these represent the wires going into a gate or bra.
78    Input,
79}
80
81impl std::fmt::Display for IndexDirection {
82    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83        match self {
84            IndexDirection::Input => write!(f, "Input"),
85            IndexDirection::Output => write!(f, "Output"),
86            IndexDirection::Batch => write!(f, "Batch"),
87            IndexDirection::Derivative => write!(f, "Derivative"),
88        }
89    }
90}
91
92impl IndexDirection {
93    pub fn is_derivative(&self) -> bool {
94        matches!(self, IndexDirection::Derivative)
95    }
96    pub fn is_batch(&self) -> bool {
97        matches!(self, IndexDirection::Batch)
98    }
99    pub fn is_output(&self) -> bool {
100        matches!(self, IndexDirection::Output)
101    }
102    pub fn is_input(&self) -> bool {
103        matches!(self, IndexDirection::Input)
104    }
105}
106
107/// Represents an index of a tensor.
108///
109/// Indices in this format are associated with a direction, and as a result,
110/// an expected output or expected generation (input) shape and ordering.
111/// For example, a `[super::network::QuditTensorNetwork]` will store its output
112/// indices in this format because after evaluation, there will
113/// be an expected shape and ordering. The shape is determined by all the
114/// indices directions and sizes, and the ordering will be determined by the ids.
115///
116/// # See Also
117///
118/// - `[super::tensor::QuditTensor]` - Tensor object capturing how tensors are generated.
119/// - `[super::network::QuditTensorNetwork]` - Tensor network describing a desired calculation.
120#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
121pub struct TensorIndex {
122    /// The direction of the tensor leg. Important for mapping to dimensions
123    /// of a TensorExpression's generated data.
124    direction: IndexDirection,
125
126    /// A unique identifier for the index associated with this leg.
127    index_id: IndexId,
128
129    /// The size or dimension of the index associated with this leg. This `index_size`
130    /// specifies the number of possible values that the index can take.
131    index_size: IndexSize,
132}
133
134impl TensorIndex {
135    /// Creates a new `TensorIndex` instance.
136    ///
137    /// # Arguments
138    ///
139    /// * `direction` - The direction of the tensor leg.
140    /// * `index_id` - A unique identifier for the index.
141    /// * `index_size` - The dimension or size of the index.
142    ///
143    /// # Returns
144    ///
145    /// A new `TensorIndex` instance with the specified properties.
146    ///
147    /// # Examples
148    ///
149    /// ```
150    /// use qudit_expr::index::{IndexDirection, TensorIndex};
151    ///
152    /// let idx = TensorIndex::new(IndexDirection::Input, 0, 2);
153    /// assert_eq!(idx.index_id(), 0);
154    /// assert_eq!(idx.index_size(), 2);
155    /// assert_eq!(idx.direction(), IndexDirection::Input);
156    /// ```
157    pub fn new(direction: IndexDirection, index_id: IndexId, index_size: IndexSize) -> Self {
158        Self {
159            direction,
160            index_id,
161            index_size,
162        }
163    }
164
165    /// Returns the `IndexDirection` of the tensor leg.
166    ///
167    /// # Examples
168    ///
169    /// ```
170    /// use qudit_expr::index::{IndexDirection, TensorIndex};
171    ///
172    /// let leg = TensorIndex::new(IndexDirection::Output, 1, 3);
173    /// assert_eq!(leg.direction(), IndexDirection::Output);
174    /// ```
175    pub fn direction(&self) -> IndexDirection {
176        self.direction
177    }
178
179    /// Returns the `IndexId` of the index associated with this leg.
180    ///
181    /// # Examples
182    ///
183    /// ```
184    /// use qudit_expr::index::{IndexDirection, TensorIndex};
185    ///
186    /// let leg = TensorIndex::new(IndexDirection::Batch, 2, 4);
187    /// assert_eq!(leg.index_id(), 2);
188    /// ```
189    pub fn index_id(&self) -> IndexId {
190        self.index_id
191    }
192
193    /// Returns the `IndexSize` of the index associated with this leg.
194    ///
195    /// # Examples
196    ///
197    /// ```
198    /// use qudit_expr::index::{IndexDirection, TensorIndex};
199    ///
200    /// let leg = TensorIndex::new(IndexDirection::Input, 3, 5);
201    /// assert_eq!(leg.index_size(), 5);
202    /// ```
203    pub fn index_size(&self) -> IndexSize {
204        self.index_size
205    }
206}
207
208impl std::fmt::Display for TensorIndex {
209    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
210        write!(
211            f,
212            "Leg {}, (size={}, dir={})",
213            self.index_id, self.index_size, self.direction,
214        )
215    }
216}