1use crate::error::{QuantRS2Error, QuantRS2Result};
8use ndarray::{Array1, Array2};
9use num_complex::Complex64 as Complex;
10use std::f64::EPSILON;
11
12#[derive(Debug, Clone)]
14pub struct EigenDecomposition {
15 pub eigenvalues: Array1<Complex>,
17 pub eigenvectors: Array2<Complex>,
19}
20
21pub fn eigen_decompose_unitary(
23 matrix: &Array2<Complex>,
24 tolerance: f64,
25) -> QuantRS2Result<EigenDecomposition> {
26 let n = matrix.nrows();
27 if n != matrix.ncols() {
28 return Err(QuantRS2Error::InvalidInput(
29 "Matrix must be square".to_string(),
30 ));
31 }
32
33 match n {
35 1 => eigen_1x1(matrix),
36 2 => eigen_2x2(matrix, tolerance),
37 _ => eigen_general(matrix, tolerance),
38 }
39}
40
41fn eigen_1x1(matrix: &Array2<Complex>) -> QuantRS2Result<EigenDecomposition> {
43 let eigenvalues = Array1::from_vec(vec![matrix[(0, 0)]]);
44 let eigenvectors = Array2::from_shape_vec((1, 1), vec![Complex::new(1.0, 0.0)])
45 .map_err(|e| QuantRS2Error::ComputationError(e.to_string()))?;
46
47 Ok(EigenDecomposition {
48 eigenvalues,
49 eigenvectors,
50 })
51}
52
53fn eigen_2x2(matrix: &Array2<Complex>, tolerance: f64) -> QuantRS2Result<EigenDecomposition> {
55 let a = matrix[(0, 0)];
56 let b = matrix[(0, 1)];
57 let c = matrix[(1, 0)];
58 let d = matrix[(1, 1)];
59
60 let trace = a + d;
63 let det = a * d - b * c;
64
65 let discriminant = trace * trace - 4.0 * det;
67 let sqrt_disc = discriminant.sqrt();
68
69 let lambda1 = (trace + sqrt_disc) / 2.0;
70 let lambda2 = (trace - sqrt_disc) / 2.0;
71
72 let eigenvalues = Array1::from_vec(vec![lambda1, lambda2]);
73
74 let mut eigenvectors = Array2::zeros((2, 2));
76
77 for (i, &lambda) in eigenvalues.iter().enumerate() {
79 let _a_minus_lambda = a - lambda;
81
82 if b.norm() > tolerance {
83 let v1 = b;
85 let v2 = lambda - a;
86 let norm = (v1.norm_sqr() + v2.norm_sqr()).sqrt();
87 eigenvectors[(0, i)] = v1 / norm;
88 eigenvectors[(1, i)] = v2 / norm;
89 } else if c.norm() > tolerance {
90 let v1 = lambda - d;
92 let v2 = c;
93 let norm = (v1.norm_sqr() + v2.norm_sqr()).sqrt();
94 eigenvectors[(0, i)] = v1 / norm;
95 eigenvectors[(1, i)] = v2 / norm;
96 } else {
97 eigenvectors[(i, i)] = Complex::new(1.0, 0.0);
99 }
100 }
101
102 Ok(EigenDecomposition {
103 eigenvalues,
104 eigenvectors,
105 })
106}
107
108fn eigen_general(matrix: &Array2<Complex>, tolerance: f64) -> QuantRS2Result<EigenDecomposition> {
110 let n = matrix.nrows();
111 let max_iterations = 1000;
112
113 let mut h = matrix.clone();
115 hessenberg_reduction(&mut h)?;
116
117 let mut eigenvalues = Vec::with_capacity(n);
119 let mut eigenvectors = Array2::eye(n);
120
121 for _ in 0..max_iterations {
122 let mut converged = true;
124 for i in 1..n {
125 if h[(i, i - 1)].norm() > tolerance {
126 converged = false;
127 break;
128 }
129 }
130
131 if converged {
132 break;
133 }
134
135 let shift = wilkinson_shift(&h, n);
137
138 let mut h_shifted = h.clone();
140 for i in 0..n {
141 h_shifted[(i, i)] -= shift;
142 }
143 let (q, r) = qr_decompose(&h_shifted)?;
144
145 h = r.dot(&q);
147 for i in 0..n {
148 h[(i, i)] += shift;
149 }
150
151 eigenvectors = eigenvectors.dot(&q);
153 }
154
155 for i in 0..n {
157 eigenvalues.push(h[(i, i)]);
158 }
159
160 let refined_eigenvectors = refine_eigenvectors(matrix, &eigenvalues, tolerance)?;
162
163 Ok(EigenDecomposition {
164 eigenvalues: Array1::from_vec(eigenvalues),
165 eigenvectors: refined_eigenvectors,
166 })
167}
168
169fn hessenberg_reduction(matrix: &mut Array2<Complex>) -> QuantRS2Result<()> {
171 let n = matrix.nrows();
172
173 for k in 0..n - 2 {
174 let mut x = Array1::zeros(n - k - 1);
176 for i in k + 1..n {
177 x[i - k - 1] = matrix[(i, k)];
178 }
179
180 let alpha = x.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt();
181 if alpha < EPSILON {
182 continue;
183 }
184
185 let mut v = x.clone();
186 v[0] += alpha * Complex::new(x[0].re.signum(), x[0].im.signum());
187 let v_norm = v.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt();
188 if v_norm < EPSILON {
189 continue;
190 }
191 for i in 0..v.len() {
192 v[i] /= v_norm;
193 }
194
195 for j in k..n {
198 let mut sum = Complex::new(0.0, 0.0);
199 for i in k + 1..n {
200 sum += v[i - k - 1].conj() * matrix[(i, j)];
201 }
202 for i in k + 1..n {
203 matrix[(i, j)] -= 2.0 * v[i - k - 1] * sum;
204 }
205 }
206
207 for i in 0..n {
208 let mut sum = Complex::new(0.0, 0.0);
209 for j in k + 1..n {
210 sum += matrix[(i, j)] * v[j - k - 1];
211 }
212 for j in k + 1..n {
213 matrix[(i, j)] -= 2.0 * sum * v[j - k - 1].conj();
214 }
215 }
216 }
217
218 Ok(())
219}
220
221fn qr_decompose(matrix: &Array2<Complex>) -> QuantRS2Result<(Array2<Complex>, Array2<Complex>)> {
223 let n = matrix.nrows();
224 let mut r = matrix.clone();
225 let mut q = Array2::eye(n);
226
227 for j in 0..n - 1 {
229 for i in (j + 1)..n {
230 if r[(i, j)].norm() < EPSILON {
231 continue;
232 }
233
234 let (c, s) = givens_rotation(r[(j, j)], r[(i, j)]);
236
237 for k in j..n {
239 let rjk = r[(j, k)];
240 let rik = r[(i, k)];
241 r[(j, k)] = c * rjk + s * rik;
242 r[(i, k)] = -s.conj() * rjk + c * rik;
243 }
244
245 for k in 0..n {
247 let qkj = q[(k, j)];
248 let qki = q[(k, i)];
249 q[(k, j)] = c * qkj + s * qki;
250 q[(k, i)] = -s.conj() * qkj + c * qki;
251 }
252 }
253 }
254
255 Ok((q, r))
256}
257
258fn givens_rotation(a: Complex, b: Complex) -> (f64, Complex) {
260 let r = (a.norm_sqr() + b.norm_sqr()).sqrt();
261 if r < EPSILON {
262 (1.0, Complex::new(0.0, 0.0))
263 } else {
264 let c = a.norm() / r;
265 let s = (a.conj() * b) / (a.norm() * r);
266 (c, s)
267 }
268}
269
270fn wilkinson_shift(matrix: &Array2<Complex>, n: usize) -> Complex {
272 if n < 2 {
273 return Complex::new(0.0, 0.0);
274 }
275
276 let a = matrix[(n - 2, n - 2)];
278 let b = matrix[(n - 2, n - 1)];
279 let c = matrix[(n - 1, n - 2)];
280 let d = matrix[(n - 1, n - 1)];
281
282 let trace = a + d;
284 let det = a * d - b * c;
285 let discriminant = trace * trace - 4.0 * det;
286 let sqrt_disc = discriminant.sqrt();
287
288 let lambda1 = (trace + sqrt_disc) / 2.0;
289 let lambda2 = (trace - sqrt_disc) / 2.0;
290
291 if (lambda1 - d).norm() < (lambda2 - d).norm() {
293 lambda1
294 } else {
295 lambda2
296 }
297}
298
299fn refine_eigenvectors(
301 matrix: &Array2<Complex>,
302 eigenvalues: &[Complex],
303 tolerance: f64,
304) -> QuantRS2Result<Array2<Complex>> {
305 let n = matrix.nrows();
306 let mut eigenvectors = Array2::zeros((n, n));
307
308 for (i, &lambda) in eigenvalues.iter().enumerate() {
309 let mut v = Array1::from_vec(vec![Complex::new(1.0, 0.0); n]);
311 v[i] = Complex::new(1.0, 0.0); let norm = v.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt();
315 if norm > EPSILON {
316 for i in 0..v.len() {
317 v[i] /= norm;
318 }
319 }
320
321 let shifted = matrix - lambda * Array2::eye(n);
323
324 for _ in 0..10 {
325 let v_new = solve_system(&shifted, &v, tolerance)?;
327
328 let norm = v_new.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt();
330 if norm < EPSILON {
331 break;
332 }
333
334 let v_normalized = &v_new / norm;
335
336 let diff: f64 = (&v_normalized - &v)
338 .iter()
339 .map(|x| x.norm_sqr())
340 .sum::<f64>()
341 .sqrt();
342 if diff < tolerance {
343 v = v_normalized;
344 break;
345 }
346
347 v = v_normalized;
348 }
349
350 for j in 0..n {
352 eigenvectors[(j, i)] = v[j];
353 }
354 }
355
356 Ok(eigenvectors)
357}
358
359fn solve_system(
361 a: &Array2<Complex>,
362 b: &Array1<Complex>,
363 tolerance: f64,
364) -> QuantRS2Result<Array1<Complex>> {
365 let n = a.nrows();
366
367 let mut regularized = a.clone();
369 for i in 0..n {
370 regularized[(i, i)] += Complex::new(tolerance, 0.0);
371 }
372
373 let (q, r) = qr_decompose(®ularized)?;
375
376 let qtb = q.t().dot(b);
378 let mut x = Array1::zeros(n);
379
380 for i in (0..n).rev() {
381 let mut sum = qtb[i];
382 for j in i + 1..n {
383 sum -= r[(i, j)] * x[j];
384 }
385
386 if r[(i, i)].norm() > tolerance {
387 x[i] = sum / r[(i, i)];
388 } else {
389 x[i] = Complex::new(0.0, 0.0);
390 }
391 }
392
393 Ok(x)
394}
395
396#[cfg(test)]
397mod tests {
398 use super::*;
399 use std::f64::consts::PI;
400
401 #[test]
402 fn test_eigen_2x2_pauli_x() {
403 let matrix = Array2::from_shape_vec(
405 (2, 2),
406 vec![
407 Complex::new(0.0, 0.0),
408 Complex::new(1.0, 0.0),
409 Complex::new(1.0, 0.0),
410 Complex::new(0.0, 0.0),
411 ],
412 )
413 .unwrap();
414
415 let result = eigen_decompose_unitary(&matrix, 1e-10).unwrap();
416
417 let mut eigenvalues: Vec<_> = result.eigenvalues.iter().map(|&x| x.re).collect();
419 eigenvalues.sort_by(|a, b| a.partial_cmp(b).unwrap());
420
421 assert!((eigenvalues[0] - (-1.0)).abs() < 1e-10);
422 assert!((eigenvalues[1] - 1.0).abs() < 1e-10);
423 }
424
425 #[test]
426 fn test_eigen_2x2_rotation() {
427 let theta = PI / 4.0;
429 let c = theta.cos();
430 let s = theta.sin();
431
432 let matrix = Array2::from_shape_vec(
433 (2, 2),
434 vec![
435 Complex::new(c, 0.0),
436 Complex::new(-s, 0.0),
437 Complex::new(s, 0.0),
438 Complex::new(c, 0.0),
439 ],
440 )
441 .unwrap();
442
443 let result = eigen_decompose_unitary(&matrix, 1e-10).unwrap();
444
445 for eigenvalue in result.eigenvalues.iter() {
447 assert!((eigenvalue.norm() - 1.0).abs() < 1e-10);
448 }
449
450 let phases: Vec<_> = result.eigenvalues.iter().map(|&x| x.arg()).collect();
452 assert!(phases.iter().any(|&p| (p - theta).abs() < 1e-10));
453 assert!(phases.iter().any(|&p| (p + theta).abs() < 1e-10));
454 }
455
456 #[test]
457 fn test_eigenvector_orthogonality() {
458 let sqrt2_inv = 1.0 / 2.0_f64.sqrt();
460 let matrix = Array2::from_shape_vec(
461 (2, 2),
462 vec![
463 Complex::new(sqrt2_inv, 0.0),
464 Complex::new(sqrt2_inv, 0.0),
465 Complex::new(sqrt2_inv, 0.0),
466 Complex::new(-sqrt2_inv, 0.0),
467 ],
468 )
469 .unwrap();
470
471 let result = eigen_decompose_unitary(&matrix, 1e-10).unwrap();
472
473 let v1 = result.eigenvectors.column(0);
475 let v2 = result.eigenvectors.column(1);
476
477 assert!((v1.dot(&v1).norm() - 1.0).abs() < 1e-10);
479 assert!((v2.dot(&v2).norm() - 1.0).abs() < 1e-10);
480
481 assert!(v1.dot(&v2).norm() < 1e-10);
483 }
484}