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