1pub mod hessenberg;
16#[doc(hidden)]
17pub mod schur;
18
19pub mod tridiag;
21pub(crate) mod tridiag_evd;
22
23use crate::assert;
24use crate::internal_prelude::*;
25use hessenberg::HessenbergParams;
26use linalg::matmul::triangular::BlockStructure;
27use schur::SchurParams;
28use tridiag::TridiagParams;
29
30#[derive(Copy, Clone, Debug, PartialEq, Eq)]
32pub enum EvdError {
33 NoConvergence,
35}
36
37#[derive(Clone, Copy, Debug)]
39pub struct EvdFromSchurParams {
40 pub recursion_threshold: usize,
42 #[doc(hidden)]
43 pub non_exhaustive: NonExhaustive,
44}
45
46#[derive(Clone, Copy, Debug)]
48pub struct EvdParams {
49 pub hessenberg: HessenbergParams,
51 pub schur: SchurParams,
53 pub evd_from_schur: EvdFromSchurParams,
55
56 #[doc(hidden)]
57 pub non_exhaustive: NonExhaustive,
58}
59
60#[derive(Clone, Copy, Debug)]
62pub struct SelfAdjointEvdParams {
63 pub tridiag: TridiagParams,
65 pub recursion_threshold: usize,
67
68 #[doc(hidden)]
69 pub non_exhaustive: NonExhaustive,
70}
71
72impl<T: ComplexField> Auto<T> for EvdParams {
73 fn auto() -> Self {
74 Self {
75 hessenberg: auto!(T),
76 schur: auto!(T),
77 evd_from_schur: auto!(T),
78 non_exhaustive: NonExhaustive(()),
79 }
80 }
81}
82impl<T: ComplexField> Auto<T> for EvdFromSchurParams {
83 fn auto() -> Self {
84 Self {
85 recursion_threshold: 64,
86 non_exhaustive: NonExhaustive(()),
87 }
88 }
89}
90
91impl<T: ComplexField> Auto<T> for SelfAdjointEvdParams {
92 fn auto() -> Self {
93 Self {
94 tridiag: auto!(T),
95 recursion_threshold: 128,
96 non_exhaustive: NonExhaustive(()),
97 }
98 }
99}
100
101#[derive(Copy, Clone, Debug, PartialEq, Eq)]
103pub enum ComputeEigenvectors {
104 No,
106 Yes,
108}
109
110#[math]
113pub fn self_adjoint_evd_scratch<T: ComplexField>(
114 dim: usize,
115 compute_u: ComputeEigenvectors,
116 par: Par,
117 params: Spec<SelfAdjointEvdParams, T>,
118) -> StackReq {
119 let n = dim;
120 let bs = linalg::qr::no_pivoting::factor::recommended_blocksize::<T>(n, n);
121
122 let prologue = StackReq::all_of(&[
123 temp_mat_scratch::<T>(n, n),
124 temp_mat_scratch::<T>(bs, n),
125 StackReq::any_of(&[tridiag::tridiag_in_place_scratch::<T>(n, par, params.tridiag.into())]),
126 temp_mat_scratch::<T::Real>(n, 1).array(2),
127 ]);
128 if compute_u == ComputeEigenvectors::No {
129 return prologue;
130 }
131
132 StackReq::all_of(&[
133 prologue,
134 temp_mat_scratch::<T::Real>(n, if try_const! { T::IS_REAL } { 0 } else { n }).array(2),
135 StackReq::any_of(&[
136 if n < params.recursion_threshold {
137 StackReq::empty()
138 } else {
139 tridiag_evd::divide_and_conquer_scratch::<T>(n, par)
140 },
141 temp_mat_scratch::<T>(n, 1),
142 linalg::householder::apply_block_householder_sequence_on_the_left_in_place_scratch::<T>(n - 1, bs, n),
143 ]),
144 ])
145}
146
147#[math]
154pub fn self_adjoint_evd<T: ComplexField>(
155 A: MatRef<'_, T>,
156 s: DiagMut<'_, T>,
157 u: Option<MatMut<'_, T>>,
158 par: Par,
159 stack: &mut MemStack,
160 params: Spec<SelfAdjointEvdParams, T>,
161) -> Result<(), EvdError> {
162 let n = A.nrows();
163 assert!(all(A.nrows() == A.ncols(), s.dim() == n));
164 if let Some(u) = u.rb() {
165 assert!(all(u.nrows() == n, u.ncols() == n));
166 }
167 let s = s.column_vector_mut();
168
169 if n == 0 {
170 return Ok(());
171 }
172
173 #[cfg(feature = "perf-warn")]
174 if let Some(matrix) = u.rb() {
175 if matrix.row_stride().unsigned_abs() != 1 && crate::__perf_warn!(QR_WARN) {
176 if matrix.col_stride().unsigned_abs() == 1 {
177 log::warn!(target: "faer_perf", "EVD prefers column-major eigenvector matrix. Found row-major matrix.");
178 } else {
179 log::warn!(target: "faer_perf", "EVD prefers column-major eigenvector matrix. Found matrix with generic strides.");
180 }
181 }
182 }
183
184 let (mut trid, stack) = unsafe { temp_mat_uninit::<T, _, _>(n, n, stack) };
185 let mut trid = trid.as_mat_mut();
186
187 trid.copy_from_triangular_lower(A);
188
189 let bs = linalg::qr::no_pivoting::factor::recommended_blocksize::<T>(n, n);
190 let (mut householder, stack) = unsafe { temp_mat_uninit::<T, _, _>(bs, n - 1, stack) };
191 let mut householder = householder.as_mat_mut();
192
193 {
194 tridiag::tridiag_in_place(trid.rb_mut(), householder.rb_mut(), par, stack, params.tridiag.into());
195 }
196
197 let trid = trid.rb();
198
199 let (mut diag, stack) = unsafe { temp_mat_uninit::<T::Real, _, _>(n, 1, stack) };
200 let (mut offdiag, stack) = unsafe { temp_mat_uninit::<T::Real, _, _>(n, 1, stack) };
201
202 let mut diag = diag.as_mat_mut().col_mut(0).try_as_col_major_mut().unwrap();
203 let mut offdiag = offdiag.as_mat_mut().col_mut(0).try_as_col_major_mut().unwrap();
204
205 for i in 0..n {
206 diag[i] = real(trid[(i, i)]);
207
208 if i + 1 < n {
209 if try_const! { T::IS_REAL } {
210 offdiag[i] = real(trid[(i + 1, i)]);
211 } else {
212 offdiag[i] = abs(trid[(i + 1, i)]);
213 }
214 } else {
215 offdiag[i] = zero();
216 }
217 }
218
219 let mut s = s;
220 let mut u = match u {
221 Some(u) => u,
222 None => {
223 tridiag_evd::qr_algorithm(diag.rb_mut(), offdiag.rb_mut(), None)?;
224 for i in 0..n {
225 s[i] = from_real(diag[i]);
226 }
227
228 return Ok(());
229 },
230 };
231
232 let (mut u_real, stack) = unsafe { temp_mat_uninit::<T::Real, _, _>(n, if T::IS_REAL { 0 } else { n }, stack) };
233 let mut u_real = u_real.as_mat_mut();
234 let mut u_evd = if try_const! { T::IS_REAL } {
235 unsafe { core::mem::transmute(u.rb_mut()) }
236 } else {
237 u_real.rb_mut()
238 };
239
240 if n < params.recursion_threshold {
241 tridiag_evd::qr_algorithm(diag.rb_mut(), offdiag.rb_mut(), Some(u_evd.rb_mut()))?;
242 } else {
243 tridiag_evd::divide_and_conquer::<T::Real>(diag.rb_mut(), offdiag.rb_mut(), u_evd.rb_mut(), par, stack, params.recursion_threshold)?;
244 }
245
246 if try_const! { !T::IS_REAL } {
247 let normalized = |x: T| {
248 if x == zero() { one() } else { mul_real(x, recip(abs(x))) }
249 };
250
251 let (mut scale, _) = unsafe { temp_mat_uninit::<T, _, _>(n, 1, stack) };
252 let mut scale = scale.as_mat_mut().col_mut(0).try_as_col_major_mut().unwrap();
253
254 let mut x = one::<T>();
255 scale[0] = one();
256
257 for i in 1..n {
258 x = normalized(trid[(i, i - 1)] * x);
259 scale[i] = copy(x);
260 }
261 for j in 0..n {
262 z!(u.rb_mut().col_mut(j), u_real.rb().col(j), scale.rb()).for_each(|uz!(u, real, scale)| {
263 *u = mul_real(*scale, *real);
264 });
265 }
266 }
267
268 linalg::householder::apply_block_householder_sequence_on_the_left_in_place_with_conj(
269 trid.submatrix(1, 0, n - 1, n - 1),
270 householder.rb(),
271 Conj::No,
272 u.rb_mut().subrows_mut(1, n - 1),
273 par,
274 stack,
275 );
276
277 for i in 0..n {
278 s[i] = from_real(diag[i]);
279 }
280
281 Ok(())
282}
283
284pub fn pseudoinverse_from_self_adjoint_evd_scratch<T: ComplexField>(dim: usize, par: Par) -> StackReq {
287 _ = par;
288 temp_mat_scratch::<T>(dim, dim).array(2)
289}
290
291#[math]
293#[track_caller]
294pub fn pseudoinverse_from_self_adjoint_evd<T: ComplexField>(pinv: MatMut<'_, T>, s: ColRef<'_, T>, u: MatRef<'_, T>, par: Par, stack: &mut MemStack) {
295 pseudoinverse_from_self_adjoint_evd_with_tolerance(pinv, s, u, zero(), eps::<T::Real>() * from_f64::<T::Real>(u.ncols() as f64), par, stack);
296}
297
298#[math]
301#[track_caller]
302pub fn pseudoinverse_from_self_adjoint_evd_with_tolerance<T: ComplexField>(
303 pinv: MatMut<'_, T>,
304 s: ColRef<'_, T>,
305 u: MatRef<'_, T>,
306 abs_tol: T::Real,
307 rel_tol: T::Real,
308 par: Par,
309 stack: &mut MemStack,
310) {
311 let mut pinv = pinv;
312 let n = u.ncols();
313
314 assert!(all(u.nrows() == n, u.ncols() == n, s.nrows() == n));
315
316 let smax = s.norm_max();
317 let tol = max(abs_tol, rel_tol * smax);
318
319 let (mut u_trunc, stack) = unsafe { temp_mat_uninit::<T, _, _>(n, n, stack) };
320 let (mut up_trunc, _) = unsafe { temp_mat_uninit::<T, _, _>(n, n, stack) };
321
322 let mut u_trunc = u_trunc.as_mat_mut();
323 let mut up_trunc = up_trunc.as_mat_mut();
324 let mut len = 0;
325
326 for j in 0..n {
327 let x = absmax(s[j]);
328 if x > tol {
329 let p = recip(real(s[j]));
330 u_trunc.rb_mut().col_mut(len).copy_from(u.col(j));
331 z!(up_trunc.rb_mut().col_mut(len), u.col(j)).for_each(|uz!(dst, src)| *dst = mul_real(*src, p));
332
333 len += 1;
334 }
335 }
336
337 linalg::matmul::triangular::matmul(
338 pinv.rb_mut(),
339 BlockStructure::TriangularLower,
340 Accum::Replace,
341 up_trunc.rb(),
342 BlockStructure::Rectangular,
343 u_trunc.rb().adjoint(),
344 BlockStructure::Rectangular,
345 one(),
346 par,
347 );
348
349 for j in 0..n {
350 for i in 0..j {
351 pinv[(i, j)] = conj(pinv[(j, i)]);
352 }
353 }
354}
355
356#[math]
357fn dot2x1<T: RealField>(lhs0: RowRef<'_, T>, lhs1: RowRef<'_, T>, rhs: ColRef<'_, T>) -> (T, T) {
358 let n = rhs.nrows();
359 assert!(all(lhs0.ncols() == n, lhs1.ncols() == n));
360
361 let mut acc00 = zero::<T>();
362 let mut acc01 = zero::<T>();
363 let mut acc10 = zero::<T>();
364 let mut acc11 = zero::<T>();
365
366 let n2 = n / 2 * 2;
367
368 let mut i = 0;
369 while i < n2 {
370 acc00 = acc00 + lhs0[i] * rhs[i];
371 acc10 = acc10 + lhs1[i] * rhs[i];
372
373 acc01 = acc01 + lhs0[i + 1] * rhs[i + 1];
374 acc11 = acc11 + lhs1[i + 1] * rhs[i + 1];
375
376 i += 2;
377 }
378 while i < n {
379 acc00 = acc00 + lhs0[i] * rhs[i];
380 acc10 = acc10 + lhs1[i] * rhs[i];
381 i += 1;
382 }
383
384 (acc00 + acc01, acc10 + acc11)
385}
386
387#[math]
388fn dot2x2<T: RealField>(lhs0: RowRef<'_, T>, lhs1: RowRef<'_, T>, rhs0: ColRef<'_, T>, rhs1: ColRef<'_, T>) -> (T, T, T, T) {
389 let n = rhs0.nrows();
390 assert!(all(lhs0.ncols() == n, lhs1.ncols() == n));
391
392 let mut acc0 = zero::<T>();
393 let mut acc1 = zero::<T>();
394 let mut acc2 = zero::<T>();
395 let mut acc3 = zero::<T>();
396
397 let mut i = 0;
398 while i < n {
399 acc0 = acc0 + lhs0[i] * rhs0[i];
400 acc1 = acc1 + lhs1[i] * rhs0[i];
401 acc2 = acc2 + lhs0[i] * rhs1[i];
402 acc3 = acc3 + lhs1[i] * rhs1[i];
403
404 i += 1;
405 }
406
407 (acc0, acc1, acc2, acc3)
408}
409
410#[math]
411pub(crate) fn evd_from_real_schur_imp<T: RealField>(A: MatRef<'_, T>, V: MatMut<'_, T>, par: Par, params: EvdFromSchurParams) {
412 let one = one::<T>;
413 let zero = zero::<T>;
414
415 let mut V = V;
416 let n = A.nrows();
417
418 let mut norm = zero();
419
420 for j in 0..n {
421 for i in 0..Ord::min(j + 2, n) {
422 norm = norm + abs1(A[(i, j)]);
423 }
424 }
425
426 let mut k = n;
427 loop {
428 if k == 0 {
429 break;
430 }
431 k -= 1;
432
433 if k == 0 || A[(k, k - 1)] == zero() {
434 let p = copy(A[(k, k)]);
436
437 V[(k, k)] = one();
440 for i in 0..k {
441 V[(i, k)] = -A[(i, k)];
442 }
443
444 solve_real_shifted_upper_quasi_triangular_system(
445 A.submatrix(0, 0, k, k),
446 p,
447 V.rb_mut().subrows_mut(0, k).col_mut(k),
448 copy(norm),
449 par,
450 params,
451 );
452 } else {
453 let p = copy(A[(k, k)]);
455 let q = sqrt(abs(A[(k, k - 1)])) * sqrt(abs(A[(k - 1, k)]));
456
457 if abs(A[(k - 1, k)]) >= abs(A[(k, k - 1)]) {
460 V[(k - 1, k - 1)] = one();
461 V[(k, k)] = q / A[(k - 1, k)];
462 } else {
463 V[(k - 1, k - 1)] = -q / A[(k, k - 1)];
464 V[(k, k)] = one();
465 }
466
467 V[(k - 1, k)] = zero();
468 V[(k, k - 1)] = zero();
469
470 for i in 0..k - 1 {
471 V[(i, k - 1)] = -V[(k - 1, k - 1)] * A[(i, k - 1)];
472 V[(i, k)] = -V[(k, k)] * A[(i, k)];
473 }
474
475 solve_cplx_shifted_upper_quasi_triangular_system(
476 A.submatrix(0, 0, k - 1, k - 1),
477 p,
478 q,
479 V.rb_mut().submatrix_mut(0, k - 1, k - 1, 2),
480 copy(norm),
481 par,
482 params,
483 );
484
485 k -= 1;
486 }
487 }
488}
489
490#[math]
491pub(crate) fn evd_from_cplx_schur_imp<T: ComplexField>(A: MatRef<'_, T>, conj_A: Conj, V: MatMut<'_, T>, par: Par, params: EvdFromSchurParams) {
492 let one = one::<T>;
493
494 let mut V = V;
495 let n = A.nrows();
496
497 let mut norm = zero::<T::Real>();
498
499 for j in 0..n {
500 for i in 0..j + 1 {
501 norm = norm + abs1(A[(i, j)]);
502 }
503 }
504
505 let mut k = n;
506 loop {
507 if k == 0 {
508 break;
509 }
510 k -= 1;
511
512 let p = if conj_A == Conj::Yes { conj(A[(k, k)]) } else { copy(A[(k, k)]) };
513
514 V[(k, k)] = one();
517
518 if conj_A == Conj::Yes {
519 for i in 0..k {
520 V[(i, k)] = -conj(A[(i, k)]);
521 }
522 } else {
523 for i in 0..k {
524 V[(i, k)] = -A[(i, k)];
525 }
526 }
527
528 solve_shifted_upper_triangular_system(
529 A.submatrix(0, 0, k, k),
530 conj_A,
531 p,
532 V.rb_mut().subrows_mut(0, k).col_mut(k),
533 copy(norm),
534 par,
535 params,
536 );
537 }
538}
539
540#[math]
541fn solve_real_shifted_upper_quasi_triangular_system<T: RealField>(
542 A: MatRef<'_, T>,
543 p: T,
544 x: ColMut<'_, T>,
545 norm: T,
546 par: Par,
547 params: EvdFromSchurParams,
548) {
549 let n = A.nrows();
550
551 let one = one::<T>;
552 let zero = zero::<T>;
553
554 let eps = eps::<T>();
555
556 let mut x = x;
557
558 if par.degree() == 0 || n < params.recursion_threshold {
559 let mut i = n;
560 loop {
561 if i == 0 {
562 break;
563 }
564 i -= 1;
565
566 if i == 0 || A[(i, i - 1)] == zero() {
567 let dot = linalg::matmul::dot::inner_prod(A.row(i).subcols(i + 1, n - i - 1), Conj::No, x.rb().subrows(i + 1, n - i - 1), Conj::No);
569
570 x[i] = x[i] - dot;
571
572 let mut z = A[(i, i)] - p;
573
574 if abs(z) < eps * norm {
575 z = eps * norm;
576 }
577
578 if x[i] != zero() {
579 x[i] = x[i] / z;
580 }
581 } else {
582 let (dot0, dot1) = dot2x1(
583 A.row(i - 1).subcols(i + 1, n - i - 1),
584 A.row(i).subcols(i + 1, n - i - 1),
585 x.rb().subrows(i + 1, n - i - 1),
586 );
587
588 x[i - 1] = x[i - 1] - dot0;
589 x[i] = x[i] - dot1;
590
591 let a = A[(i, i)] - p;
598 let b = copy(A[(i - 1, i)]);
599 let c = copy(A[(i, i - 1)]);
600
601 let r0 = copy(x[i - 1]);
602 let r1 = copy(x[i]);
603
604 let inv_det = recip(abs2(a) - b * c);
605
606 x[i - 1] = (a * r0 - b * r1) * inv_det;
607 x[i] = (a * r1 - c * r0) * inv_det;
608
609 i -= 1;
610 }
611 }
612 } else {
613 let mut mid = n / 2;
614 if A[(mid, mid - 1)] != zero() {
615 mid -= 1;
616 }
617
618 let (A00, A01, _, A11) = A.split_at(mid, mid);
619 let (mut x0, mut x1) = x.rb_mut().split_at_row_mut(mid);
620
621 solve_real_shifted_upper_quasi_triangular_system(A11, copy(p), x1.rb_mut(), copy(norm), par, params);
622
623 linalg::matmul::matmul(x0.rb_mut().as_mat_mut(), Accum::Add, A01, x1.rb().as_mat(), -one(), par);
624
625 solve_real_shifted_upper_quasi_triangular_system(A00, p, x0.rb_mut(), norm, par, params);
626 }
627}
628
629#[math]
630fn solve_cplx_shifted_upper_quasi_triangular_system<T: RealField>(
631 A: MatRef<'_, T>,
632 p: T,
633 q: T,
634 x: MatMut<'_, T>,
635 norm: T,
636 par: Par,
637 params: EvdFromSchurParams,
638) {
639 let n = A.nrows();
640
641 let one = one::<T>;
642 let zero = zero::<T>;
643
644 let eps = eps::<T>();
645
646 let mut x = x;
647
648 if par.degree() == 0 || n < params.recursion_threshold {
649 let mut i = n;
650 loop {
651 if i == 0 {
652 break;
653 }
654 i -= 1;
655
656 if i == 0 || A[(i, i - 1)] == zero() {
657 let (re, im) = dot2x1(
659 x.rb().subrows(i + 1, n - i - 1).col(0).transpose(),
660 x.rb().subrows(i + 1, n - i - 1).col(1).transpose(),
661 A.row(i).subcols(i + 1, n - i - 1).transpose(),
662 );
663
664 x[(i, 0)] = x[(i, 0)] - re;
665 x[(i, 1)] = x[(i, 1)] - im;
666
667 let mut z_re = A[(i, i)] - p;
668 let mut z_im = -q;
669 let mut z = hypot(z_re, z_im);
670
671 if z < eps * norm {
672 z_re = eps * norm;
673 z_im = zero();
674 z = copy(z_re);
675 }
676
677 let z_re = (z_re / z) / z;
678 let z_im = (-z_im / z) / z;
679
680 let x_re = copy(x[(i, 0)]);
681 let x_im = copy(x[(i, 1)]);
682
683 x[(i, 0)] = x_re * z_re - x_im * z_im;
684 x[(i, 1)] = x_re * z_im + x_im * z_re;
685 } else {
686 let (re0, re1, im0, im1) = dot2x2(
687 A.row(i - 1).subcols(i + 1, n - i - 1),
688 A.row(i).subcols(i + 1, n - i - 1),
689 x.rb().col(0).subrows(i + 1, n - i - 1),
690 x.rb().col(1).subrows(i + 1, n - i - 1),
691 );
692
693 x[(i - 1, 0)] = x[(i - 1, 0)] - re0;
694 x[(i - 1, 1)] = x[(i - 1, 1)] - im0;
695 x[(i, 0)] = x[(i, 0)] - re1;
696 x[(i, 1)] = x[(i, 1)] - im1;
697
698 let a_re = A[(i, i)] - p;
699 let a_im = -q;
700 let b = copy(A[(i - 1, i)]);
701 let c = copy(A[(i, i - 1)]);
702
703 let r0_re = copy(x[(i - 1, 0)]);
704 let r0_im = copy(x[(i - 1, 1)]);
705 let r1_re = copy(x[(i, 0)]);
706 let r1_im = copy(x[(i, 1)]);
707
708 let mut z_re = abs2(a_re) - abs2(a_im) - b * c;
709 let mut z_im = mul_pow2(a_re * a_im, from_f64::<T>(2.0));
710 let mut z = hypot(z_re, z_im);
711
712 if z < eps * norm {
713 z_re = eps * norm;
714 z_im = zero();
715 z = copy(z_re);
716 }
717
718 let z_re = (z_re / z) / z;
719 let z_im = (-z_im / z) / z;
720
721 let x0_re = (a_re * r0_re - a_im * r0_im) - b * r1_re;
722 let x0_im = (a_re * r0_im + a_im * r0_re) - b * r1_im;
723
724 let x1_re = (a_re * r1_re - a_im * r1_im) - c * r0_re;
725 let x1_im = (a_re * r1_im + a_im * r1_re) - c * r0_im;
726
727 x[(i - 1, 0)] = x0_re * z_re - x0_im * z_im;
728 x[(i - 1, 1)] = x0_re * z_im + x0_im * z_re;
729
730 x[(i, 0)] = x1_re * z_re - x1_im * z_im;
731 x[(i, 1)] = x1_re * z_im + x1_im * z_re;
732
733 i -= 1;
734 }
735 }
736 } else {
737 let mut mid = n / 2;
738 if A[(mid, mid - 1)] != zero() {
739 mid -= 1;
740 }
741
742 let (A00, A01, _, A11) = A.split_at(mid, mid);
743 let (mut x0, mut x1) = x.rb_mut().split_at_row_mut(mid);
744
745 solve_cplx_shifted_upper_quasi_triangular_system(A11, copy(p), copy(q), x1.rb_mut(), copy(norm), par, params);
746
747 linalg::matmul::matmul(x0.rb_mut(), Accum::Add, A01, x1.rb(), -one(), par);
748
749 solve_cplx_shifted_upper_quasi_triangular_system(A00, p, q, x0.rb_mut(), norm, par, params);
750 }
751}
752
753#[math]
754fn solve_shifted_upper_triangular_system<T: ComplexField>(
755 A: MatRef<'_, T>,
756 conj_A: Conj,
757 p: T,
758 x: ColMut<'_, T>,
759 norm: T::Real,
760 par: Par,
761 params: EvdFromSchurParams,
762) {
763 let n = A.nrows();
764
765 let one = one::<T>;
766 let zero = zero::<T>;
767
768 let eps = eps::<T::Real>();
769
770 let mut x = x;
771
772 if par.degree() == 0 || n < params.recursion_threshold {
773 let mut i = n;
774 loop {
775 if i == 0 {
776 break;
777 }
778 i -= 1;
779
780 let dot = linalg::matmul::dot::inner_prod(A.row(i).subcols(i + 1, n - i - 1), conj_A, x.rb().subrows(i + 1, n - i - 1), Conj::No);
782
783 x[i] = x[i] - dot;
784
785 let mut z = if conj_A == Conj::Yes { conj(A[(i, i)]) } else { copy(A[(i, i)]) } - p;
786
787 if abs(z) < eps * norm {
788 z = from_real(eps * norm);
789 }
790
791 if x[i] != zero() {
792 x[i] = x[i] * recip(z);
793 }
794 }
795 } else {
796 let mid = n / 2;
797
798 let (A00, A01, _, A11) = A.split_at(mid, mid);
799 let (mut x0, mut x1) = x.rb_mut().split_at_row_mut(mid);
800
801 solve_shifted_upper_triangular_system(A11, conj_A, copy(p), x1.rb_mut(), copy(norm), par, params);
802
803 linalg::matmul::matmul_with_conj(x0.rb_mut().as_mat_mut(), Accum::Add, A01, conj_A, x1.rb().as_mat(), Conj::No, -one(), par);
804
805 solve_shifted_upper_triangular_system(A00, conj_A, p, x0.rb_mut(), norm, par, params);
806 }
807}
808
809pub fn evd_scratch<T: ComplexField>(
812 dim: usize,
813 eigen_left: ComputeEigenvectors,
814 eigen_right: ComputeEigenvectors,
815 par: Par,
816 params: Spec<EvdParams, T>,
817) -> StackReq {
818 let n = dim;
819
820 if n == 0 {
821 return StackReq::EMPTY;
822 }
823
824 let compute_eigen = eigen_left == ComputeEigenvectors::Yes || eigen_right == ComputeEigenvectors::Yes;
825 let bs = linalg::qr::no_pivoting::factor::recommended_blocksize::<T>(n - 1, n - 1);
826
827 let H = temp_mat_scratch::<T>(n, n);
828 let X = H;
829 let Z = temp_mat_scratch::<T>(n, if compute_eigen { n } else { 0 });
830 let householder = temp_mat_scratch::<T>(bs, n);
831 let apply = linalg::householder::apply_block_householder_sequence_on_the_right_in_place_scratch::<T>(n - 1, bs, n - 1);
832
833 StackReq::all_of(&[
834 H,
835 Z,
836 StackReq::any_of(&[
837 householder.and(hessenberg::hessenberg_in_place_scratch::<T>(n, bs, par, params.hessenberg.into()).or(apply)),
838 schur::multishift_qr_scratch::<T>(n, n, compute_eigen, compute_eigen, par, params.schur),
839 X,
840 ]),
841 ])
842}
843
844#[math]
845fn evd_imp<T: ComplexField>(
846 A: MatRef<'_, T>,
847 s: ColMut<'_, T>,
848 s_im: Option<ColMut<'_, T>>,
849 u_left: Option<MatMut<'_, T>>,
850 u_right: Option<MatMut<'_, T>>,
851 par: Par,
852 stack: &mut MemStack,
853 params: EvdParams,
854) -> Result<(), EvdError> {
855 let n = A.nrows();
856
857 if n == 0 {
858 return Ok(());
859 }
860
861 for j in 0..n {
862 for i in 0..n {
863 if !is_finite(A[(i, j)]) {
864 return Err(EvdError::NoConvergence);
865 }
866 }
867 }
868
869 let bs = linalg::qr::no_pivoting::factor::recommended_blocksize::<T>(n - 1, n - 1);
870 let mut s = s;
871 let mut s_im = s_im;
872
873 let (mut H, stack) = unsafe { temp_mat_uninit::<T, _, _>(n, n, stack) };
874 let (mut Z, stack) = unsafe { temp_mat_uninit::<T, _, _>(n, if u_left.is_some() || u_right.is_some() { n } else { 0 }, stack) };
875
876 let mut H = H.as_mat_mut();
877 let mut Z = if u_left.is_some() || u_right.is_some() {
878 Some(Z.as_mat_mut())
879 } else {
880 None
881 };
882
883 H.copy_from(A);
884
885 {
886 let (mut householder, stack) = unsafe { temp_mat_uninit::<T, _, _>(bs, n - 1, stack) };
887 let mut householder = householder.as_mat_mut();
888
889 hessenberg::hessenberg_in_place(H.rb_mut(), householder.rb_mut(), par, stack, params.hessenberg.into());
890
891 if let Some(mut Z) = Z.rb_mut() {
892 Z.fill(zero());
893 Z.rb_mut().diagonal_mut().fill(one());
894
895 linalg::householder::apply_block_householder_sequence_on_the_right_in_place_with_conj(
896 H.rb().submatrix(1, 0, n - 1, n - 1),
897 householder.rb(),
898 Conj::No,
899 Z.rb_mut().submatrix_mut(1, 1, n - 1, n - 1),
900 par,
901 stack,
902 );
903 }
904
905 for j in 0..n {
906 for i in j + 2..n {
907 H[(i, j)] = zero();
908 }
909 }
910 }
911
912 if try_const! { T::IS_REAL } {
913 schur::real_schur::multishift_qr::<T::Real>(
914 unsafe { core::mem::transmute(Z.is_some()) },
915 unsafe { core::mem::transmute(H.rb_mut()) },
916 unsafe { core::mem::transmute(Z.rb_mut()) },
917 unsafe { core::mem::transmute(s.rb_mut()) },
918 unsafe { core::mem::transmute(s_im.rb_mut().unwrap()) },
919 0,
920 n,
921 par,
922 stack,
923 params.schur,
924 );
925 } else {
926 schur::complex_schur::multishift_qr::<T>(Z.is_some(), H.rb_mut(), Z.rb_mut(), s.rb_mut(), 0, n, par, stack, params.schur);
927 }
928
929 let H = H.rb();
930
931 if let (Some(mut u), Some(Z)) = (u_right, Z.rb()) {
932 let (mut X, _) = unsafe { temp_mat_uninit::<T, _, _>(n, n, stack) };
933 let mut X = X.as_mat_mut();
934
935 if try_const! { T::IS_REAL } {
936 evd_from_real_schur_imp::<T::Real>(
937 unsafe { core::mem::transmute(H) },
938 unsafe { core::mem::transmute(X.rb_mut()) },
939 par,
940 params.evd_from_schur,
941 );
942 } else {
943 evd_from_cplx_schur_imp::<T>(H, Conj::No, X.rb_mut(), par, params.evd_from_schur);
944 }
945
946 linalg::matmul::triangular::matmul(
947 u.rb_mut(),
948 BlockStructure::Rectangular,
949 Accum::Replace,
950 Z,
951 BlockStructure::Rectangular,
952 X.rb(),
953 BlockStructure::TriangularUpper,
954 one(),
955 par,
956 );
957 }
958
959 if let (Some(mut u), Some(Z)) = (u_left, Z.rb()) {
960 let (mut X, _) = unsafe { temp_mat_uninit::<T, _, _>(n, n, stack) };
961 let mut X = X.as_mat_mut().reverse_rows_mut();
962
963 if try_const! { T::IS_REAL } {
964 evd_from_real_schur_imp::<T::Real>(
965 unsafe { core::mem::transmute(H.transpose().reverse_rows_and_cols()) },
966 unsafe { core::mem::transmute(X.rb_mut()) },
967 par,
968 params.evd_from_schur,
969 );
970 } else {
971 evd_from_cplx_schur_imp::<T>(H.transpose().reverse_rows_and_cols(), Conj::Yes, X.rb_mut(), par, params.evd_from_schur);
972 }
973
974 linalg::matmul::triangular::matmul(
975 u.rb_mut(),
976 BlockStructure::Rectangular,
977 Accum::Replace,
978 Z,
979 BlockStructure::Rectangular,
980 X.rb().reverse_rows_and_cols(),
981 BlockStructure::TriangularLower,
982 one(),
983 par,
984 );
985 }
986
987 Ok(())
988}
989
990#[track_caller]
995pub fn evd_cplx<T: RealField>(
996 A: MatRef<'_, Complex<T>>,
997 s: DiagMut<'_, Complex<T>>,
998 u_left: Option<MatMut<'_, Complex<T>>>,
999 u_right: Option<MatMut<'_, Complex<T>>>,
1000 par: Par,
1001 stack: &mut MemStack,
1002 params: Spec<EvdParams, Complex<T>>,
1003) -> Result<(), EvdError> {
1004 let n = A.nrows();
1005 assert!(all(A.nrows() == n, A.ncols() == n, s.dim() == n));
1006 if let Some(u) = u_left.rb() {
1007 assert!(all(u.nrows() == n, u.ncols() == n));
1008 }
1009 if let Some(u) = u_right.rb() {
1010 assert!(all(u.nrows() == n, u.ncols() == n));
1011 }
1012
1013 evd_imp(A, s.column_vector_mut(), None, u_left, u_right, par, stack, params.config)
1014}
1015
1016#[track_caller]
1021pub fn evd_real<T: RealField>(
1022 A: MatRef<'_, T>,
1023 s_re: DiagMut<'_, T>,
1024 s_im: DiagMut<'_, T>,
1025 u_left: Option<MatMut<'_, T>>,
1026 u_right: Option<MatMut<'_, T>>,
1027 par: Par,
1028 stack: &mut MemStack,
1029 params: Spec<EvdParams, T>,
1030) -> Result<(), EvdError> {
1031 let n = A.nrows();
1032 assert!(all(A.nrows() == n, A.ncols() == n, s_re.dim() == n, s_im.dim() == n));
1033 if let Some(u) = u_left.rb() {
1034 assert!(all(u.nrows() == n, u.ncols() == n));
1035 }
1036 if let Some(u) = u_right.rb() {
1037 assert!(all(u.nrows() == n, u.ncols() == n));
1038 }
1039
1040 evd_imp(
1041 A,
1042 s_re.column_vector_mut(),
1043 Some(s_im.column_vector_mut()),
1044 u_left,
1045 u_right,
1046 par,
1047 stack,
1048 params.config,
1049 )
1050}
1051
1052#[cfg(test)]
1053mod general_tests {
1054 use super::*;
1055 use crate::assert;
1056 use crate::stats::prelude::*;
1057 use crate::utils::approx::*;
1058 use dyn_stack::MemBuffer;
1059
1060 fn test_cplx_evd(mat: MatRef<'_, c64>) {
1061 let n = mat.nrows();
1062 let params = Spec::new(EvdParams {
1063 hessenberg: auto!(c64),
1064 schur: auto!(c64),
1065 evd_from_schur: EvdFromSchurParams {
1066 recursion_threshold: 8,
1067 ..auto!(c64)
1068 },
1069 ..auto!(c64)
1070 });
1071
1072 use faer_traits::math_utils::*;
1073 let approx_eq = CwiseMat(ApproxEq::eps() * sqrt(&from_f64(8.0 * n as f64)));
1074
1075 let mut s = Mat::zeros(n, n);
1076 {
1077 let mut ul = Mat::zeros(n, n);
1078 let mut ur = Mat::zeros(n, n);
1079
1080 evd_cplx(
1081 mat.as_ref(),
1082 s.as_mut().diagonal_mut(),
1083 Some(ul.as_mut()),
1084 Some(ur.as_mut()),
1085 Par::Seq,
1086 MemStack::new(&mut MemBuffer::new(evd_scratch::<c64>(
1087 n,
1088 ComputeEigenvectors::Yes,
1089 ComputeEigenvectors::Yes,
1090 Par::Seq,
1091 params,
1092 ))),
1093 params,
1094 )
1095 .unwrap();
1096
1097 assert!(&ur * &s ~ mat * &ur);
1098 assert!(&s * ul.adjoint() ~ ul.adjoint() * mat);
1099 }
1100 }
1101
1102 fn test_real_evd(mat: MatRef<'_, f64>) {
1103 let n = mat.nrows();
1104 let params = Spec::new(EvdParams {
1105 hessenberg: auto!(f64),
1106 schur: auto!(f64),
1107 evd_from_schur: EvdFromSchurParams {
1108 recursion_threshold: 8,
1109 ..auto!(f64)
1110 },
1111 ..auto!(f64)
1112 });
1113
1114 use faer_traits::math_utils::*;
1115 let approx_eq = CwiseMat(ApproxEq::<f64>::eps() * sqrt(&from_f64(8.0 * n as f64)));
1116
1117 let mut s_re = Diag::zeros(n);
1118 let mut s_im = Diag::zeros(n);
1119 {
1120 let mut ul = Mat::zeros(n, n);
1121 let mut ur = Mat::zeros(n, n);
1122
1123 evd_real(
1124 mat.as_ref(),
1125 s_re.as_mut(),
1126 s_im.as_mut(),
1127 Some(ul.as_mut()),
1128 Some(ur.as_mut()),
1129 Par::Seq,
1130 MemStack::new(&mut MemBuffer::new(evd_scratch::<f64>(
1131 n,
1132 ComputeEigenvectors::Yes,
1133 ComputeEigenvectors::Yes,
1134 Par::Seq,
1135 params,
1136 ))),
1137 params,
1138 )
1139 .unwrap();
1140
1141 let mut i = 0;
1142 while i < n {
1143 if s_im[i] == 0.0 {
1144 let ur = ur.col(i);
1145 let ul = ul.col(i);
1146
1147 let s = Scale(s_re[i]);
1148
1149 assert!((&ur * s).as_mat() ~ (mat * &ur).as_mat());
1150 assert!((&ul.adjoint() * s).as_mat() ~ (&ul.adjoint() * mat).as_mat());
1151
1152 i += 1;
1153 } else {
1154 let re = ur.col(i);
1155 let im = ur.col(i + 1);
1156 let ur = &Col::from_fn(n, |i| c64::new(re[i], im[i]));
1157
1158 let re = ul.col(i);
1159 let im = ul.col(i + 1);
1160 let ul = &Col::from_fn(n, |i| c64::new(re[i], im[i]));
1161
1162 let mat = &Mat::from_fn(n, n, |i, j| c64::from(mat[(i, j)]));
1163
1164 let s = Scale(c64::new(s_re[i], s_im[i]));
1165
1166 let approx_eq = CwiseMat(ApproxEq::eps() * sqrt(&from_f64(8.0 * n as f64)));
1167
1168 assert!((ur * s).as_mat() ~ (mat * ur).as_mat());
1169 assert!((ul.adjoint() * s).as_mat() ~ (ul.adjoint() * mat).as_mat());
1170
1171 i += 2;
1172 }
1173 }
1174 }
1175 }
1176
1177 #[test]
1178 fn test_cplx() {
1179 let rng = &mut StdRng::seed_from_u64(1);
1180
1181 for n in [2, 4, 10, 15, 20, 50, 100, 150] {
1182 let mat = CwiseMatDistribution {
1183 nrows: n,
1184 ncols: n,
1185 dist: ComplexDistribution::new(StandardNormal, StandardNormal),
1186 }
1187 .rand::<Mat<c64>>(rng);
1188
1189 test_cplx_evd(mat.as_ref());
1190 }
1191 }
1192
1193 #[test]
1194 fn test_real() {
1195 let rng = &mut StdRng::seed_from_u64(0);
1196
1197 for n in [2, 4, 10, 15, 20, 50, 100, 150] {
1198 let mat = CwiseMatDistribution {
1199 nrows: n,
1200 ncols: n,
1201 dist: StandardNormal,
1202 }
1203 .rand::<Mat<f64>>(rng);
1204
1205 test_real_evd(mat.as_ref());
1206 }
1207 }
1208}
1209
1210#[cfg(test)]
1211mod self_adjoint_tests {
1212 use super::*;
1213 use crate::assert;
1214 use crate::stats::prelude::*;
1215 use crate::utils::approx::*;
1216 use dyn_stack::MemBuffer;
1217
1218 fn test_self_adjoint_evd<T: ComplexField>(mat: MatRef<'_, T>) {
1219 let n = mat.nrows();
1220 let params = Spec::new(SelfAdjointEvdParams {
1221 recursion_threshold: 8,
1222 ..auto!(T)
1223 });
1224 use faer_traits::math_utils::*;
1225 let approx_eq = CwiseMat(ApproxEq::<T::Real>::eps() * sqrt(&from_f64(8.0 * n as f64)));
1226
1227 let mut s = Mat::zeros(n, n);
1228 {
1229 let mut u = Mat::zeros(n, n);
1230
1231 self_adjoint_evd(
1232 mat.as_ref(),
1233 s.as_mut().diagonal_mut(),
1234 Some(u.as_mut()),
1235 Par::Seq,
1236 MemStack::new(&mut MemBuffer::new(self_adjoint_evd_scratch::<T>(
1237 n,
1238 ComputeEigenvectors::Yes,
1239 Par::Seq,
1240 params,
1241 ))),
1242 params,
1243 )
1244 .unwrap();
1245
1246 let reconstructed = &u * &s * u.adjoint();
1247 assert!(reconstructed ~ mat);
1248 }
1249
1250 {
1251 let mut s2 = Mat::zeros(n, n);
1252
1253 self_adjoint_evd(
1254 mat.as_ref(),
1255 s2.as_mut().diagonal_mut(),
1256 None,
1257 Par::Seq,
1258 MemStack::new(&mut MemBuffer::new(self_adjoint_evd_scratch::<T>(
1259 n,
1260 ComputeEigenvectors::No,
1261 Par::Seq,
1262 params,
1263 ))),
1264 params,
1265 )
1266 .unwrap();
1267
1268 assert!(s2 ~ s);
1269 }
1270 }
1271
1272 #[test]
1273 fn test_real() {
1274 let rng = &mut StdRng::seed_from_u64(1);
1275
1276 for n in [1, 2, 4, 10, 15, 20, 50, 100, 150] {
1277 let mat = CwiseMatDistribution {
1278 nrows: n,
1279 ncols: n,
1280 dist: StandardNormal,
1281 }
1282 .rand::<Mat<f64>>(rng);
1283 let mat = &mat + mat.adjoint();
1284
1285 test_self_adjoint_evd(mat.as_ref());
1286 }
1287 }
1288
1289 #[test]
1290 fn test_cplx() {
1291 let rng = &mut StdRng::seed_from_u64(1);
1292
1293 for n in [2, 4, 10, 15, 20, 50, 100, 150] {
1294 let mat = CwiseMatDistribution {
1295 nrows: n,
1296 ncols: n,
1297 dist: ComplexDistribution::new(StandardNormal, StandardNormal),
1298 }
1299 .rand::<Mat<c64>>(rng);
1300 let mat = &mat + mat.adjoint();
1301
1302 test_self_adjoint_evd(mat.as_ref());
1303 }
1304 }
1305
1306 #[test]
1307 fn test_special() {
1308 for n in [1, 2, 4, 10, 15, 20, 50, 100, 150] {
1309 test_self_adjoint_evd(Mat::full(n, n, 0.0).as_ref());
1310 test_self_adjoint_evd(Mat::full(n, n, c64::ZERO).as_ref());
1311 test_self_adjoint_evd(Mat::full(n, n, 1.0).as_ref());
1312 test_self_adjoint_evd(Mat::full(n, n, c64::ONE).as_ref());
1313 test_self_adjoint_evd(Mat::<f64>::identity(n, n).as_ref());
1314 test_self_adjoint_evd(Mat::<c64>::identity(n, n).as_ref());
1315 }
1316 }
1317}