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