1use crate::error::QuantRS2Error;
7use scirs2_core::ndarray::{Array1, Array2};
8use scirs2_core::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 const 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(scirs2_core::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 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().ok_or_else(|| {
150 QuantRS2Error::UnsupportedOperation("Density matrix not computed".to_string())
151 })?;
152 let dim = density.nrows();
153
154 let (eigenvalues, eigenvectors) = self.quantum_eigendecomposition(density)?;
157
158 let mut filtered_indices: Vec<usize> = eigenvalues
160 .iter()
161 .enumerate()
162 .filter(|(_, &val)| val.abs() > self.params.eigenvalue_threshold)
163 .map(|(idx, _)| idx)
164 .collect();
165
166 filtered_indices.sort_by(|&a, &b| {
168 eigenvalues[b]
169 .abs()
170 .partial_cmp(&eigenvalues[a].abs())
171 .unwrap_or(std::cmp::Ordering::Equal)
172 });
173
174 let n_components = filtered_indices.len();
176 let mut filtered_eigenvalues = Array1::zeros(n_components);
177 let mut filtered_eigenvectors = Array2::zeros((dim, n_components));
178
179 for (new_idx, &old_idx) in filtered_indices.iter().enumerate() {
180 filtered_eigenvalues[new_idx] = eigenvalues[old_idx];
181 for i in 0..dim {
182 filtered_eigenvectors[[i, new_idx]] = eigenvectors[[i, old_idx]];
183 }
184 }
185
186 self.eigenvalues = Some(filtered_eigenvalues);
187 self.eigenvectors = Some(filtered_eigenvectors);
188
189 Ok(())
190 }
191
192 fn quantum_eigendecomposition(
194 &self,
195 matrix: &Array2<Complex64>,
196 ) -> Result<(Array1<f64>, Array2<Complex64>), QuantRS2Error> {
197 let hermitian = (matrix + &matrix.t().mapv(|x| x.conj())) / Complex64::new(2.0, 0.0);
202
203 let is_real = hermitian.iter().all(|x| x.im.abs() < 1e-10);
205
206 if is_real {
207 let n = hermitian.nrows();
208 let real_matrix = hermitian.mapv(|x| x.re);
209
210 let mut eigenvalues = Vec::with_capacity(n);
213 let mut eigenvectors = Array2::<Complex64>::zeros((n, n));
214
215 let num_components = n.min(self.params.precision_qubits);
217
218 for comp in 0..num_components {
219 let mut v = Array1::from_vec(vec![1.0 / (n as f64).sqrt(); n]);
221 let mut eigenvalue = 0.0;
222
223 for _ in 0..self.params.max_iterations {
224 let av = real_matrix.dot(&v);
225 eigenvalue = v.dot(&av);
226 let norm = av.dot(&av).sqrt();
227 if norm > 1e-10 {
228 v = av / norm;
229 }
230 }
231
232 eigenvalues.push(eigenvalue);
233 for i in 0..n {
234 eigenvectors[[i, comp]] = Complex64::new(v[i], 0.0);
235 }
236 }
237
238 eigenvalues.extend(vec![0.0; n - num_components]);
240
241 Ok((Array1::from_vec(eigenvalues), eigenvectors))
242 } else {
243 Err(QuantRS2Error::UnsupportedOperation(
246 "Complex eigendecomposition not yet implemented".to_string(),
247 ))
248 }
249 }
250
251 pub fn transform(&self, data: &Array2<f64>) -> Result<Array2<f64>, QuantRS2Error> {
253 let eigenvectors =
254 self.eigenvectors
255 .as_ref()
256 .ok_or(QuantRS2Error::UnsupportedOperation(
257 "Components not yet extracted".to_string(),
258 ))?;
259
260 let mean = self
262 .data_matrix
263 .mean_axis(scirs2_core::ndarray::Axis(0))
264 .ok_or_else(|| {
265 QuantRS2Error::UnsupportedOperation("Failed to compute mean".to_string())
266 })?;
267
268 let centered_data = data - &mean;
269
270 let n_components = eigenvectors.ncols();
272 let n_samples = centered_data.nrows();
273 let mut transformed = Array2::zeros((n_samples, n_components));
274
275 for i in 0..n_samples {
276 for j in 0..n_components {
277 let mut sum = 0.0;
278 for k in 0..centered_data.ncols() {
279 sum += centered_data[[i, k]] * eigenvectors[[k, j]].re;
280 }
281 transformed[[i, j]] = sum;
282 }
283 }
284
285 Ok(transformed)
286 }
287
288 pub fn explained_variance_ratio(&self) -> Result<Array1<f64>, QuantRS2Error> {
290 let eigenvalues = self
291 .eigenvalues
292 .as_ref()
293 .ok_or(QuantRS2Error::UnsupportedOperation(
294 "Components not yet extracted".to_string(),
295 ))?;
296
297 let total_variance: f64 = eigenvalues.sum();
298 if total_variance.abs() < 1e-10 {
299 return Err(QuantRS2Error::UnsupportedOperation(
300 "Total variance is zero".to_string(),
301 ));
302 }
303
304 Ok(eigenvalues / total_variance)
305 }
306
307 pub fn n_components(&self) -> Option<usize> {
309 self.eigenvalues.as_ref().map(|e| e.len())
310 }
311
312 pub const fn eigenvalues(&self) -> Option<&Array1<f64>> {
314 self.eigenvalues.as_ref()
315 }
316
317 pub const fn eigenvectors(&self) -> Option<&Array2<Complex64>> {
319 self.eigenvectors.as_ref()
320 }
321}
322
323pub struct DensityMatrixPCA {
325 params: QPCAParams,
326 pub trace_threshold: f64,
327}
328
329impl DensityMatrixPCA {
330 pub const fn new(params: QPCAParams) -> Self {
332 Self {
333 params,
334 trace_threshold: 0.95, }
336 }
337
338 pub fn fit_transform(
340 &self,
341 data: &Array2<f64>,
342 ) -> Result<(Array2<f64>, Array1<f64>), QuantRS2Error> {
343 let mut qpca = QuantumPCA::new(data.clone(), self.params.clone());
344
345 qpca.compute_density_matrix()?;
347 qpca.extract_components()?;
348
349 let explained_variance = qpca.explained_variance_ratio()?;
351 let mut cumsum = 0.0;
352 let mut n_components_retain = 0;
353
354 for (i, &var) in explained_variance.iter().enumerate() {
355 cumsum += var;
356 n_components_retain = i + 1;
357 if cumsum >= self.trace_threshold {
358 break;
359 }
360 }
361
362 let transformed = qpca.transform(data)?;
364
365 let retained_transform = transformed
367 .slice(scirs2_core::ndarray::s![.., ..n_components_retain])
368 .to_owned();
369 let retained_variance = explained_variance
370 .slice(scirs2_core::ndarray::s![..n_components_retain])
371 .to_owned();
372
373 Ok((retained_transform, retained_variance))
374 }
375}
376
377#[cfg(test)]
378mod tests {
379 use super::*;
380 use scirs2_core::ndarray::Array2;
381
382 #[test]
383 fn test_qpca_basic() {
384 let data = Array2::from_shape_vec(
386 (5, 3),
387 vec![
388 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,
389 ],
390 )
391 .expect("Failed to create test data array");
392
393 let params = QPCAParams::default();
394 let mut qpca = QuantumPCA::new(data.clone(), params);
395
396 let density = qpca
398 .compute_density_matrix()
399 .expect("Failed to compute density matrix");
400 assert_eq!(density.shape(), &[3, 3]);
401
402 qpca.extract_components()
404 .expect("Failed to extract components");
405
406 let eigenvalues = qpca.eigenvalues().expect("No eigenvalues computed");
407 assert!(!eigenvalues.is_empty());
408
409 for i in 1..eigenvalues.len() {
411 assert!(eigenvalues[i - 1] >= eigenvalues[i]);
412 }
413 }
414
415 #[test]
416 fn test_density_matrix_pca() {
417 let mut data = Array2::zeros((10, 4));
419 for i in 0..10 {
420 data[[i, 0]] = i as f64;
421 data[[i, 1]] = 2.0 * i as f64;
422 data[[i, 2]] = 0.1 * ((i * 7) % 10) as f64 / 10.0;
423 data[[i, 3]] = 0.1 * ((i * 13) % 10) as f64 / 10.0;
424 }
425
426 let params = QPCAParams::default();
427 let pca = DensityMatrixPCA::new(params);
428
429 let (transformed, variance) = pca.fit_transform(&data).expect("Failed to fit transform");
430
431 assert!(transformed.ncols() >= 1);
433 assert!(transformed.ncols() <= data.ncols());
434 assert!(variance.sum() <= 1.0 + 1e-6);
436 }
437}