Skip to main content

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}