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}