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}