1use crate::error::QuantRS2Error;
7use ndarray::{Array1, Array2};
8use num_complex::Complex64;
9use std::f64::consts::PI;
10
11#[derive(Debug, Clone)]
13pub struct QPCAParams {
14 pub precision_qubits: usize,
16 pub num_samples: usize,
18 pub eigenvalue_threshold: f64,
20 pub max_iterations: usize,
22}
23
24impl Default for QPCAParams {
25 fn default() -> Self {
26 Self {
27 precision_qubits: 8,
28 num_samples: 1000,
29 eigenvalue_threshold: 0.01,
30 max_iterations: 100,
31 }
32 }
33}
34
35pub struct QuantumPCA {
37 params: QPCAParams,
38 data_matrix: Array2<f64>,
39 density_matrix: Option<Array2<Complex64>>,
40 eigenvalues: Option<Array1<f64>>,
41 eigenvectors: Option<Array2<Complex64>>,
42}
43
44impl QuantumPCA {
45 pub fn new(data: Array2<f64>, params: QPCAParams) -> Self {
47 Self {
48 params,
49 data_matrix: data,
50 density_matrix: None,
51 eigenvalues: None,
52 eigenvectors: None,
53 }
54 }
55
56 pub fn compute_density_matrix(&mut self) -> Result<&Array2<Complex64>, QuantRS2Error> {
58 let (n_samples, n_features) = (self.data_matrix.nrows(), self.data_matrix.ncols());
59
60 let mean = self
62 .data_matrix
63 .mean_axis(ndarray::Axis(0))
64 .ok_or_else(|| {
65 QuantRS2Error::UnsupportedOperation("Failed to compute mean".to_string())
66 })?;
67
68 let centered_data = &self.data_matrix - &mean;
69
70 let cov_matrix = centered_data.t().dot(¢ered_data) / (n_samples - 1) as f64;
72
73 let mut density = Array2::<Complex64>::zeros((n_features, n_features));
75 for i in 0..n_features {
76 for j in 0..n_features {
77 density[[i, j]] = Complex64::new(cov_matrix[[i, j]], 0.0);
78 }
79 }
80
81 let trace: Complex64 = (0..n_features).map(|i| density[[i, i]]).sum();
83 if trace.norm() > 1e-10 {
84 density /= trace;
85 }
86
87 self.density_matrix = Some(density);
88 self.density_matrix
89 .as_ref()
90 .ok_or(QuantRS2Error::UnsupportedOperation(
91 "Failed to create density matrix".to_string(),
92 ))
93 }
94
95 pub fn quantum_phase_estimation(
97 &self,
98 unitary: &Array2<Complex64>,
99 state: &Array1<Complex64>,
100 ) -> Result<f64, QuantRS2Error> {
101 let precision = self.params.precision_qubits;
102 let _n = 1 << precision;
103
104 let mut phase_estimate = 0.0;
107
108 for k in 0..precision {
110 let controlled_phase = self.estimate_controlled_phase(unitary, state, 1 << k)?;
112 phase_estimate += controlled_phase * (1.0 / (1 << (precision - k - 1)) as f64);
113 }
114
115 Ok(phase_estimate)
116 }
117
118 fn estimate_controlled_phase(
120 &self,
121 unitary: &Array2<Complex64>,
122 state: &Array1<Complex64>,
123 power: usize,
124 ) -> Result<f64, QuantRS2Error> {
125 let mut u_power = unitary.clone();
127 for _ in 1..power {
128 u_power = u_power.dot(unitary);
129 }
130
131 let result = u_power.dot(state);
133 let inner_product: Complex64 = state
134 .iter()
135 .zip(result.iter())
136 .map(|(a, b)| a.conj() * b)
137 .sum();
138
139 Ok(inner_product.arg() / (2.0 * PI))
140 }
141
142 pub fn extract_components(&mut self) -> Result<(), QuantRS2Error> {
144 if self.density_matrix.is_none() {
146 self.compute_density_matrix()?;
147 }
148
149 let density = self.density_matrix.as_ref().unwrap();
150 let dim = density.nrows();
151
152 let (eigenvalues, eigenvectors) = self.quantum_eigendecomposition(density)?;
155
156 let mut filtered_indices: Vec<usize> = eigenvalues
158 .iter()
159 .enumerate()
160 .filter(|(_, &val)| val.abs() > self.params.eigenvalue_threshold)
161 .map(|(idx, _)| idx)
162 .collect();
163
164 filtered_indices.sort_by(|&a, &b| {
166 eigenvalues[b]
167 .abs()
168 .partial_cmp(&eigenvalues[a].abs())
169 .unwrap()
170 });
171
172 let n_components = filtered_indices.len();
174 let mut filtered_eigenvalues = Array1::zeros(n_components);
175 let mut filtered_eigenvectors = Array2::zeros((dim, n_components));
176
177 for (new_idx, &old_idx) in filtered_indices.iter().enumerate() {
178 filtered_eigenvalues[new_idx] = eigenvalues[old_idx];
179 for i in 0..dim {
180 filtered_eigenvectors[[i, new_idx]] = eigenvectors[[i, old_idx]];
181 }
182 }
183
184 self.eigenvalues = Some(filtered_eigenvalues);
185 self.eigenvectors = Some(filtered_eigenvectors);
186
187 Ok(())
188 }
189
190 fn quantum_eigendecomposition(
192 &self,
193 matrix: &Array2<Complex64>,
194 ) -> Result<(Array1<f64>, Array2<Complex64>), QuantRS2Error> {
195 let hermitian = (matrix + &matrix.t().mapv(|x| x.conj())) / Complex64::new(2.0, 0.0);
200
201 let is_real = hermitian.iter().all(|x| x.im.abs() < 1e-10);
203
204 if is_real {
205 let n = hermitian.nrows();
206 let real_matrix = hermitian.mapv(|x| x.re);
207
208 let mut eigenvalues = Vec::with_capacity(n);
211 let mut eigenvectors = Array2::<Complex64>::zeros((n, n));
212
213 let num_components = n.min(self.params.precision_qubits);
215
216 for comp in 0..num_components {
217 let mut v = Array1::from_vec(vec![1.0 / (n as f64).sqrt(); n]);
219 let mut eigenvalue = 0.0;
220
221 for _ in 0..self.params.max_iterations {
222 let av = real_matrix.dot(&v);
223 eigenvalue = v.dot(&av);
224 let norm = av.dot(&av).sqrt();
225 if norm > 1e-10 {
226 v = av / norm;
227 }
228 }
229
230 eigenvalues.push(eigenvalue);
231 for i in 0..n {
232 eigenvectors[[i, comp]] = Complex64::new(v[i], 0.0);
233 }
234 }
235
236 eigenvalues.extend(vec![0.0; n - num_components]);
238
239 Ok((Array1::from_vec(eigenvalues), eigenvectors))
240 } else {
241 Err(QuantRS2Error::UnsupportedOperation(
244 "Complex eigendecomposition not yet implemented".to_string(),
245 ))
246 }
247 }
248
249 pub fn transform(&self, data: &Array2<f64>) -> Result<Array2<f64>, QuantRS2Error> {
251 let eigenvectors =
252 self.eigenvectors
253 .as_ref()
254 .ok_or(QuantRS2Error::UnsupportedOperation(
255 "Components not yet extracted".to_string(),
256 ))?;
257
258 let mean = self
260 .data_matrix
261 .mean_axis(ndarray::Axis(0))
262 .ok_or_else(|| {
263 QuantRS2Error::UnsupportedOperation("Failed to compute mean".to_string())
264 })?;
265
266 let centered_data = data - &mean;
267
268 let n_components = eigenvectors.ncols();
270 let n_samples = centered_data.nrows();
271 let mut transformed = Array2::zeros((n_samples, n_components));
272
273 for i in 0..n_samples {
274 for j in 0..n_components {
275 let mut sum = 0.0;
276 for k in 0..centered_data.ncols() {
277 sum += centered_data[[i, k]] * eigenvectors[[k, j]].re;
278 }
279 transformed[[i, j]] = sum;
280 }
281 }
282
283 Ok(transformed)
284 }
285
286 pub fn explained_variance_ratio(&self) -> Result<Array1<f64>, QuantRS2Error> {
288 let eigenvalues = self
289 .eigenvalues
290 .as_ref()
291 .ok_or(QuantRS2Error::UnsupportedOperation(
292 "Components not yet extracted".to_string(),
293 ))?;
294
295 let total_variance: f64 = eigenvalues.sum();
296 if total_variance.abs() < 1e-10 {
297 return Err(QuantRS2Error::UnsupportedOperation(
298 "Total variance is zero".to_string(),
299 ));
300 }
301
302 Ok(eigenvalues / total_variance)
303 }
304
305 pub fn n_components(&self) -> Option<usize> {
307 self.eigenvalues.as_ref().map(|e| e.len())
308 }
309
310 pub fn eigenvalues(&self) -> Option<&Array1<f64>> {
312 self.eigenvalues.as_ref()
313 }
314
315 pub fn eigenvectors(&self) -> Option<&Array2<Complex64>> {
317 self.eigenvectors.as_ref()
318 }
319}
320
321pub struct DensityMatrixPCA {
323 params: QPCAParams,
324 pub trace_threshold: f64,
325}
326
327impl DensityMatrixPCA {
328 pub fn new(params: QPCAParams) -> Self {
330 Self {
331 params,
332 trace_threshold: 0.95, }
334 }
335
336 pub fn fit_transform(
338 &self,
339 data: &Array2<f64>,
340 ) -> Result<(Array2<f64>, Array1<f64>), QuantRS2Error> {
341 let mut qpca = QuantumPCA::new(data.clone(), self.params.clone());
342
343 qpca.compute_density_matrix()?;
345 qpca.extract_components()?;
346
347 let explained_variance = qpca.explained_variance_ratio()?;
349 let mut cumsum = 0.0;
350 let mut n_components_retain = 0;
351
352 for (i, &var) in explained_variance.iter().enumerate() {
353 cumsum += var;
354 n_components_retain = i + 1;
355 if cumsum >= self.trace_threshold {
356 break;
357 }
358 }
359
360 let transformed = qpca.transform(data)?;
362
363 let retained_transform = transformed
365 .slice(ndarray::s![.., ..n_components_retain])
366 .to_owned();
367 let retained_variance = explained_variance
368 .slice(ndarray::s![..n_components_retain])
369 .to_owned();
370
371 Ok((retained_transform, retained_variance))
372 }
373}
374
375#[cfg(test)]
376mod tests {
377 use super::*;
378 use ndarray::Array2;
379
380 #[test]
381 fn test_qpca_basic() {
382 let data = Array2::from_shape_vec(
384 (5, 3),
385 vec![
386 1.0, 2.0, 3.0, 2.0, 4.0, 6.0, 3.0, 6.0, 9.0, 4.0, 8.0, 12.0, 5.0, 10.0, 15.0,
387 ],
388 )
389 .unwrap();
390
391 let params = QPCAParams::default();
392 let mut qpca = QuantumPCA::new(data.clone(), params);
393
394 let density = qpca.compute_density_matrix().unwrap();
396 assert_eq!(density.shape(), &[3, 3]);
397
398 qpca.extract_components().unwrap();
400
401 let eigenvalues = qpca.eigenvalues().unwrap();
402 assert!(!eigenvalues.is_empty());
403
404 for i in 1..eigenvalues.len() {
406 assert!(eigenvalues[i - 1] >= eigenvalues[i]);
407 }
408 }
409
410 #[test]
411 fn test_density_matrix_pca() {
412 let mut data = Array2::zeros((10, 4));
414 for i in 0..10 {
415 data[[i, 0]] = i as f64;
416 data[[i, 1]] = 2.0 * i as f64;
417 data[[i, 2]] = 0.1 * ((i * 7) % 10) as f64 / 10.0;
418 data[[i, 3]] = 0.1 * ((i * 13) % 10) as f64 / 10.0;
419 }
420
421 let params = QPCAParams::default();
422 let pca = DensityMatrixPCA::new(params);
423
424 let (transformed, variance) = pca.fit_transform(&data).unwrap();
425
426 assert!(transformed.ncols() >= 1);
428 assert!(transformed.ncols() <= data.ncols());
429 assert!(variance.sum() <= 1.0 + 1e-6);
431 }
432}