qudit_core/quantum/
ket.rs

1use crate::ComplexScalar;
2use crate::QuditSystem;
3use crate::Radices;
4use crate::RealScalar;
5use faer::{Col, Mat, Row};
6use num_complex::ComplexFloat;
7use num_traits::One;
8use std::ops::Index;
9
10/// Represents a quantum state vector as a Ket.
11pub struct Ket<R: RealScalar> {
12    radices: Radices,
13    vector: Col<R::C>,
14}
15
16impl<R: RealScalar> Ket<R> {
17    /// Create a new Ket.
18    ///
19    /// # Arguments
20    ///
21    /// * `radices` - The radices of the qudit system.
22    /// * `vector` - The vector to wrap.
23    ///
24    /// # Panics
25    ///
26    /// Panics if the vector is not a pure state.
27    ///
28    /// # Example
29    ///
30    /// ```
31    /// use faer::Col;
32    /// use qudit_core::Ket;
33    /// use qudit_core::c64;
34    /// let zero_state: Ket<f64> = Ket::new([2, 2], vec![
35    ///     c64::ONE, c64::ZERO, c64::ZERO, c64::ZERO
36    /// ]);
37    /// ```
38    ///
39    /// # See Also
40    ///
41    #[inline(always)]
42    #[track_caller]
43    pub fn new<T: Into<Radices>, V: Into<Self>>(radices: T, vector: V) -> Self {
44        let guessed_radices_ket = vector.into();
45        let radices = radices.into();
46        // assert!(Self::is_pure_state(&guessed_radices_ket));
47        Self {
48            radices,
49            vector: guessed_radices_ket.vector,
50        }
51    }
52
53    /// Create a zero state (all amplitudes zero).
54    ///
55    /// # Arguments
56    ///
57    /// * `radices` - The radices of the qudit system.
58    ///
59    /// # Example
60    ///
61    /// ```
62    /// use qudit_core::Ket;
63    /// let zero_state: Ket<f64> = Ket::zero([2, 2]);
64    /// ```
65    pub fn zero<T: Into<Radices>>(radices: T) -> Self {
66        Self::basis(radices, 0)
67    }
68
69    /// Create a computational basis state |i⟩.
70    ///
71    /// # Arguments
72    ///
73    /// * `radices` - The radices of the qudit system.
74    /// * `index` - The computational basis index.
75    ///
76    /// # Panics
77    ///
78    /// Panics if the index is out of bounds for the given radices.
79    ///
80    /// # Example
81    ///
82    /// ```
83    /// use qudit_core::Ket;
84    /// let state: Ket<f64> = Ket::basis([2, 2], 3); // |11⟩ state
85    /// ```
86    pub fn basis<T: Into<Radices>>(radices: T, index: usize) -> Self {
87        let radices = radices.into();
88        let dimension = radices.dimension();
89        assert!(
90            index < dimension,
91            "Index {} out of bounds for dimension {}",
92            index,
93            dimension
94        );
95
96        let mut vector = Col::zeros(dimension);
97        vector[index] = R::C::one();
98
99        Self { radices, vector }
100    }
101
102    /// Create a uniform superposition state (all amplitudes equal).
103    ///
104    /// # Arguments
105    ///
106    /// * `radices` - The radices of the qudit system.
107    ///
108    /// # Example
109    ///
110    /// ```
111    /// use qudit_core::Ket;
112    /// let uniform_state: Ket<f64> = Ket::uniform([2, 2]);
113    /// ```
114    pub fn uniform<T: Into<Radices>>(radices: T) -> Self {
115        let radices = radices.into();
116        let dimension = radices.dimension();
117        let amplitude = R::C::from_real(R::one() / R::from(dimension).unwrap().sqrt());
118
119        Self {
120            radices,
121            vector: Col::from_fn(dimension, |_| amplitude),
122        }
123    }
124
125    /// Check if this is a valid normalized pure quantum state.
126    ///
127    /// A pure state must have norm² ≈ 1.0 (normalized).
128    ///
129    /// # Arguments
130    ///
131    /// * `tolerance` - Optional tolerance for normalization check (default: 1e-10)
132    ///
133    /// # Example
134    ///
135    /// ```
136    /// use qudit_core::Ket;
137    /// let basis_state: Ket<f64> = Ket::basis([2, 2], 0);
138    /// assert!(basis_state.is_pure_state());
139    /// ```
140    pub fn is_pure_state(&self) -> bool {
141        let norm_squared = self.vector.squared_norm_l2();
142        R::ONE.is_close(norm_squared)
143    }
144
145    /// Get the distance from another quantum state.
146    ///
147    /// This computes 1 - |⟨ψ|φ⟩|² where |⟨ψ|φ⟩|² is the fidelity.
148    /// Distance of 0 means identical states, distance of 1 means orthogonal states.
149    ///
150    /// # Arguments
151    ///
152    /// * `other` - The other quantum state to compare with
153    ///
154    /// # Panics
155    ///
156    /// Panics if the states have different dimensions.
157    ///
158    /// # Example
159    ///
160    /// ```
161    /// use qudit_core::Ket;
162    /// let state1: Ket<f64> = Ket::basis([2], 0);
163    /// let state2: Ket<f64> = Ket::basis([2], 1);
164    /// let distance = state1.get_distance_from(&state2);
165    /// assert!((distance - 1.0).abs() < 1e-10); // Orthogonal states
166    /// ```
167    pub fn get_distance_from(&self, other: &Self) -> R {
168        assert_eq!(
169            self.vector.nrows(),
170            other.vector.nrows(),
171            "Cannot compute distance between states of different dimensions"
172        );
173
174        let inner_product = self.inner_product(other);
175        let fidelity = inner_product.norm_squared();
176        R::one() - fidelity
177    }
178
179    // TODO: should not have explicit operation for inner product
180    // rather: have a dagger that takes Ket -> Bra, and allow multiplication
181    /// Compute the inner product ⟨self|other⟩.
182    fn inner_product(&self, other: &Self) -> R::C {
183        self.vector
184            .iter()
185            .zip(other.vector.iter())
186            .map(|(&a, &b)| a.conj() * b)
187            .sum()
188    }
189
190    /// Get the probabilities for all computational basis states.
191    ///
192    /// Returns a vector where each element is |amplitude|² for the corresponding basis state.
193    ///
194    /// # Example
195    ///
196    /// ```
197    /// use qudit_core::Ket;
198    /// let uniform_state: Ket<f64> = Ket::uniform([2, 2]);
199    /// let probs = uniform_state.probabilities();
200    /// // Each probability should be 0.25 for uniform superposition
201    /// ```
202    pub fn probabilities(&self) -> Vec<R> {
203        self.vector.iter().map(|&z| z.norm_squared()).collect()
204    }
205
206    /// Get the probability of measuring the state in a specific computational basis state.
207    ///
208    /// # Arguments
209    ///
210    /// * `index` - The computational basis index
211    ///
212    /// # Panics
213    ///
214    /// Panics if the index is out of bounds.
215    ///
216    /// # Example
217    ///
218    /// ```
219    /// use qudit_core::Ket;
220    /// let basis_state: Ket<f64> = Ket::basis([2, 2], 1);
221    /// assert!((basis_state.probability_at(1) - 1.0).abs() < 1e-10);
222    /// assert!(basis_state.probability_at(0).abs() < 1e-10);
223    /// ```
224    pub fn probability_at(&self, index: usize) -> R {
225        assert!(index < self.vector.nrows(), "Index {} out of bounds", index);
226        self.vector[index].norm_squared()
227    }
228
229    /// Compute the tensor product with another quantum state.
230    ///
231    /// Creates a new state |ψ⟩ ⊗ |φ⟩ representing a composite quantum system.
232    ///
233    /// # Arguments
234    ///
235    /// * `other` - The other quantum state to tensor with
236    ///
237    /// # Example
238    ///
239    /// ```
240    /// use qudit_core::Ket;
241    /// let qubit1: Ket<f64> = Ket::basis([2], 0); // |0⟩
242    /// let qubit2: Ket<f64> = Ket::basis([2], 1); // |1⟩
243    /// let combined = qubit1.tensor_product(&qubit2); // |01⟩
244    /// ```
245    pub fn tensor_product(&self, other: &Self) -> Self {
246        // Combine the radices
247        let new_radices = self.radices.concat(&other.radices);
248
249        // Compute Kronecker product of the vectors
250        let self_dim = self.vector.nrows();
251        let other_dim = other.vector.nrows();
252        let new_dim = self_dim * other_dim;
253
254        let new_vector = Col::from_fn(new_dim, |idx| {
255            let i = idx / other_dim;
256            let j = idx % other_dim;
257            self.vector[i] * other.vector[j]
258        });
259
260        Self {
261            radices: new_radices,
262            vector: new_vector,
263        }
264    }
265}
266
267impl<R: RealScalar> QuditSystem for Ket<R> {
268    fn radices(&self) -> Radices {
269        self.radices.clone()
270    }
271
272    fn dimension(&self) -> usize {
273        self.radices.dimension()
274    }
275}
276
277// Index trait implementation for accessing amplitudes
278impl<R: RealScalar> Index<usize> for Ket<R> {
279    type Output = R::C;
280
281    fn index(&self, index: usize) -> &Self::Output {
282        &self.vector[index]
283    }
284}
285
286// TODO:
287// impl TryFrom<Tensor<R, 3>> for Ket<R>
288// impl TryFrom<Tensor<C, 3>> for Ket<C::R>
289
290impl<R: RealScalar, T: Into<R::C> + Copy> TryFrom<Mat<T>> for Ket<R> {
291    type Error = String;
292
293    fn try_from(value: Mat<T>) -> Result<Self, Self::Error> {
294        if value.ncols() != 1 {
295            return Err(format!(
296                "Matrix has {} columns, expected 1 column for conversion to Ket",
297                value.ncols()
298            ));
299        }
300
301        // TODO: If T == R::C then take ptr and manually drop value for zero-alloc
302
303        let vec: Vec<R::C> = value.col(0).iter().copied().map(|x| x.into()).collect();
304        Ok(vec.into())
305    }
306}
307
308impl<C: ComplexScalar> From<Col<C>> for Ket<C::R> {
309    fn from(value: Col<C>) -> Self {
310        let radices = Radices::guess(value.nrows());
311        Self {
312            radices,
313            vector: value,
314        }
315    }
316}
317
318impl<R: RealScalar, T: Into<R::C> + Copy> From<Row<T>> for Ket<R> {
319    fn from(value: Row<T>) -> Self {
320        // TODO: If T == R::C then take ptr and manually drop value for zero-alloc
321        // be extra safe here about strides and all that
322        let vec: Vec<R::C> = value.iter().copied().map(|x| x.into()).collect();
323        vec.into()
324    }
325}
326
327impl<R: RealScalar, T: Into<R::C> + Copy> From<Vec<T>> for Ket<R> {
328    fn from(value: Vec<T>) -> Self {
329        let radices = Radices::guess(value.len());
330        Self {
331            radices,
332            vector: Col::from_fn(value.len(), |i| value[i].into()),
333        }
334    }
335}
336
337impl<R: RealScalar, T: Into<R::C>> FromIterator<T> for Ket<R> {
338    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
339        let vec: Vec<R::C> = iter.into_iter().map(|x| x.into()).collect();
340        vec.into()
341    }
342}
343
344impl<R: RealScalar, T: Into<R::C> + Copy> From<&[T]> for Ket<R> {
345    fn from(value: &[T]) -> Self {
346        let radices = Radices::guess(value.len());
347        Self {
348            radices,
349            vector: Col::from_fn(value.len(), |i| value[i].into()),
350        }
351    }
352}
353
354impl<R: RealScalar, T: Into<R::C> + Copy, const N: usize> From<&[T; N]> for Ket<R> {
355    fn from(value: &[T; N]) -> Self {
356        let radices = Radices::guess(N);
357        Self {
358            radices,
359            vector: Col::from_fn(N, |i| value[i].into()),
360        }
361    }
362}
363
364impl<R: RealScalar, T: Into<R::C> + Copy, const N: usize> From<[T; N]> for Ket<R> {
365    fn from(value: [T; N]) -> Self {
366        let radices = Radices::guess(N);
367        Self {
368            radices,
369            vector: Col::from_fn(N, |i| value[i].into()),
370        }
371    }
372}