1use anyhow::{self, ensure, format_err};
4use duplicate::duplicate_item;
5use itertools::Itertools;
6use ndarray::{Array1, Array2, ArrayView1, ArrayView2, Axis};
7use ndarray_linalg::{Eig, EigGeneralized, Eigh, GeneralizedEigenvalue, Lapack, Scalar, UPLO};
8use num::traits::FloatConst;
9use num::{Float, One};
10use num_complex::{Complex, ComplexFloat};
11
12use crate::analysis::EigenvalueComparisonMode;
13use crate::auxiliary::misc::{
14 check_complex_matrix_symmetry, check_real_matrix_symmetry, normalise_eigenvectors_complex,
15 normalise_eigenvectors_real, regularise_eigenvectors, sort_eigenvalues_eigenvectors,
16};
17use crate::target::noci::backend::nonortho::CanonicalOrthogonalisable;
18
19pub mod noci;
20
21#[cfg(test)]
22#[path = "solver_tests.rs"]
23mod solver_tests;
24
25pub trait GeneralisedEigenvalueSolvable {
36 type NumType;
38
39 type RealType;
41
42 fn solve_generalised_eigenvalue_problem_with_canonical_orthogonalisation(
63 &self,
64 complex_symmetric: bool,
65 thresh_offdiag: Self::RealType,
66 thresh_zeroov: Self::RealType,
67 eigenvalue_comparison_mode: EigenvalueComparisonMode,
68 ) -> Result<GeneralisedEigenvalueResult<Self::NumType>, anyhow::Error>;
69
70 fn solve_generalised_eigenvalue_problem_with_ggev(
88 &self,
89 complex_symmetric: bool,
90 thresh_offdiag: Self::RealType,
91 thresh_zeroov: Self::RealType,
92 eigenvalue_comparison_mode: EigenvalueComparisonMode,
93 ) -> Result<GeneralisedEigenvalueResult<Self::NumType>, anyhow::Error>;
94}
95
96pub struct GeneralisedEigenvalueResult<T> {
99 eigenvalues: Array1<T>,
101
102 eigenvectors: Array2<T>,
104}
105
106impl<T> GeneralisedEigenvalueResult<T> {
107 pub fn eigenvalues(&'_ self) -> ArrayView1<'_, T> {
109 self.eigenvalues.view()
110 }
111
112 pub fn eigenvectors(&'_ self) -> ArrayView2<'_, T> {
114 self.eigenvectors.view()
115 }
116}
117
118#[duplicate_item(
119 [
120 dtype_ [ f64 ]
121 ]
122 [
123 dtype_ [ f32 ]
124 ]
125)]
126impl GeneralisedEigenvalueSolvable for (&ArrayView2<'_, dtype_>, &ArrayView2<'_, dtype_>) {
127 type NumType = dtype_;
128 type RealType = dtype_;
129
130 fn solve_generalised_eigenvalue_problem_with_canonical_orthogonalisation(
131 &self,
132 _: bool,
133 thresh_offdiag: dtype_,
134 thresh_zeroov: dtype_,
135 eigenvalue_comparison_mode: EigenvalueComparisonMode,
136 ) -> Result<GeneralisedEigenvalueResult<Self::NumType>, anyhow::Error> {
137 let (hmat, smat) = (self.0.to_owned(), self.1.to_owned());
138
139 let (hmat, smat): (Array2<dtype_>, Array2<dtype_>) = {
141 check_real_matrix_symmetry(&hmat.view(), thresh_offdiag, "Hamiltonian", "H")?;
143 check_real_matrix_symmetry(&smat.view(), thresh_offdiag, "Overlap", "S")?;
144
145 (
146 (hmat.to_owned() + hmat.t().to_owned()).map(|v| v / (2.0)),
147 (smat.to_owned() + smat.t().to_owned()).map(|v| v / (2.0)),
148 )
149 };
150
151 let xmat_res = smat.view().calc_canonical_orthogonal_matrix(
155 true,
156 false,
157 thresh_offdiag,
158 thresh_zeroov,
159 &eigenvalue_comparison_mode
160 )?;
161
162 let xmat = xmat_res.xmat();
163 let xmat_d = xmat_res.xmat_d();
164
165 let hmat_t = xmat_d.dot(&hmat).dot(&xmat);
166 let smat_t = xmat_d.dot(&smat).dot(&xmat);
167
168 log::debug!("Canonical-orthogonalised NOCI Hamiltonian matrix H~:\n {hmat_t:+.8e}");
169 log::debug!("Canonical-orthogonalised NOCI overlap matrix S~:\n {smat_t:+.8e}");
170
171 let (pos, max_diff) = (&smat_t - &Array2::<dtype_>::eye(smat_t.nrows()))
174 .iter()
175 .map(|x| ComplexFloat::abs(*x))
176 .enumerate()
177 .max_by(|(_, x), (_, y)| {
178 x.partial_cmp(y)
179 .expect("Unable to compare two `abs` values.")
180 })
181 .ok_or_else(|| {
182 format_err!("Unable to determine the maximum element of the |S - I| matrix.")
183 })?;
184 let (pos_i, pos_j) = (pos.div_euclid(hmat.ncols()), pos.rem_euclid(hmat.ncols()));
185 ensure!(
186 max_diff <= thresh_offdiag,
187 "The orthogonalised overlap matrix is not the identity matrix: the maximum absolute deviation is {max_diff:.3e} > {thresh_offdiag:.3e} at ({pos_i}, {pos_j})."
188 );
189
190 let (eigvals_t, eigvecs_t) = hmat_t.eigh(UPLO::Lower)?;
191
192 let (eigvals_t_sorted, eigvecs_t_sorted) = sort_eigenvalues_eigenvectors(
194 &eigvals_t.view(),
195 &eigvecs_t.view(),
196 &eigenvalue_comparison_mode,
197 );
198 let eigvecs_sorted = xmat.dot(&eigvecs_t_sorted);
199
200 let eigvecs_sorted_normalised =
202 normalise_eigenvectors_real(&eigvecs_sorted.view(), &smat.view(), thresh_offdiag)?;
203
204 let eigvecs_sorted_normalised_regularised =
206 regularise_eigenvectors(&eigvecs_sorted_normalised.view(), thresh_offdiag);
207
208 Ok(GeneralisedEigenvalueResult {
209 eigenvalues: eigvals_t_sorted,
210 eigenvectors: eigvecs_sorted_normalised_regularised,
211 })
212 }
213
214 fn solve_generalised_eigenvalue_problem_with_ggev(
215 &self,
216 _: bool,
217 thresh_offdiag: dtype_,
218 thresh_zeroov: dtype_,
219 eigenvalue_comparison_mode: EigenvalueComparisonMode,
220 ) -> Result<GeneralisedEigenvalueResult<Self::NumType>, anyhow::Error> {
221 let (hmat, smat) = (self.0.to_owned(), self.1.to_owned());
222
223 check_real_matrix_symmetry(&hmat.view(), thresh_offdiag, "Hamiltonian", "H")?;
225 check_real_matrix_symmetry(&smat.view(), thresh_offdiag, "Overlap", "S")?;
226
227 let (geneigvals, eigvecs) =
228 (hmat.clone(), smat.clone()).eig_generalized(Some(thresh_zeroov))?;
229
230 for gv in geneigvals.iter() {
231 if let GeneralizedEigenvalue::Finite(v, _) = gv {
232 ensure!(
233 v.im().abs() <= thresh_offdiag,
234 "Unexpected complex eigenvalue {v} for real, symmetric S and H."
235 );
236 }
237 }
238
239 let mut indices = (0..geneigvals.len())
241 .filter(|i| matches!(geneigvals[*i], GeneralizedEigenvalue::Finite(_, _)))
242 .collect_vec();
243
244 match eigenvalue_comparison_mode {
245 EigenvalueComparisonMode::Modulus => {
246 indices.sort_by(|i, j| {
247 if let (
248 GeneralizedEigenvalue::Finite(e_i, _),
249 GeneralizedEigenvalue::Finite(e_j, _),
250 ) = (&geneigvals[*i], &geneigvals[*j])
251 {
252 ComplexFloat::abs(*e_i)
253 .partial_cmp(&ComplexFloat::abs(*e_j))
254 .unwrap()
255 } else {
256 panic!("Unable to compare some eigenvalues.")
257 }
258 });
259 }
260 EigenvalueComparisonMode::Real => {
261 indices.sort_by(|i, j| {
262 if let (
263 GeneralizedEigenvalue::Finite(e_i, _),
264 GeneralizedEigenvalue::Finite(e_j, _),
265 ) = (&geneigvals[*i], &geneigvals[*j])
266 {
267 e_i.re().partial_cmp(&e_j.re()).unwrap()
268 } else {
269 panic!("Unable to compare some eigenvalues.")
270 }
271 });
272 }
273 }
274
275 let eigvals_re_sorted = geneigvals.select(Axis(0), &indices).map(|gv| {
276 if let GeneralizedEigenvalue::Finite(v, _) = gv {
277 v.re()
278 } else {
279 panic!("Unexpected indeterminate eigenvalue.")
280 }
281 });
282 let eigvecs_sorted = eigvecs.select(Axis(1), &indices);
283 ensure!(
284 eigvecs_sorted.iter().all(|v| v.im().abs() < thresh_offdiag),
285 "Unexpected complex eigenvectors."
286 );
287 let eigvecs_re_sorted = eigvecs_sorted.map(|v| v.re());
288
289 let eigvecs_re_sorted_normalised =
291 normalise_eigenvectors_real(&eigvecs_re_sorted.view(), &smat.view(), thresh_offdiag)?;
292
293 let eigvecs_re_sorted_normalised_regularised =
295 regularise_eigenvectors(&eigvecs_re_sorted_normalised.view(), thresh_offdiag);
296
297 Ok(GeneralisedEigenvalueResult {
298 eigenvalues: eigvals_re_sorted,
299 eigenvectors: eigvecs_re_sorted_normalised_regularised,
300 })
301 }
302}
303
304impl<T> GeneralisedEigenvalueSolvable for (&ArrayView2<'_, Complex<T>>, &ArrayView2<'_, Complex<T>>)
305where
306 T: Float + FloatConst + Scalar<Complex = Complex<T>>,
307 Complex<T>: ComplexFloat<Real = T> + Scalar<Real = T, Complex = Complex<T>> + Lapack,
308 for<'a> ArrayView2<'a, Complex<T>>:
309 CanonicalOrthogonalisable<NumType = Complex<T>, RealType = T>,
310{
311 type NumType = Complex<T>;
312
313 type RealType = T;
314
315 fn solve_generalised_eigenvalue_problem_with_canonical_orthogonalisation(
316 &self,
317 complex_symmetric: bool,
318 thresh_offdiag: T,
319 thresh_zeroov: T,
320 eigenvalue_comparison_mode: EigenvalueComparisonMode,
321 ) -> Result<GeneralisedEigenvalueResult<Complex<T>>, anyhow::Error> {
322 let (hmat, smat) = (self.0.to_owned(), self.1.to_owned());
323
324 let (hmat, smat): (Array2<Complex<T>>, Array2<Complex<T>>) = if complex_symmetric {
326 check_complex_matrix_symmetry(
328 &hmat.view(),
329 complex_symmetric,
330 thresh_offdiag,
331 "Hamiltonian",
332 "H",
333 )?;
334 check_complex_matrix_symmetry(
335 &smat.view(),
336 complex_symmetric,
337 thresh_offdiag,
338 "Overlap",
339 "S",
340 )?;
341 (
342 (hmat.to_owned() + hmat.t().to_owned())
343 .map(|v| v / (Complex::<T>::one() + Complex::<T>::one())),
344 (smat.to_owned() + smat.t().to_owned())
345 .map(|v| v / (Complex::<T>::one() + Complex::<T>::one())),
346 )
347 } else {
348 check_complex_matrix_symmetry(
350 &hmat.view(),
351 complex_symmetric,
352 thresh_offdiag,
353 "Hamiltonian",
354 "H",
355 )?;
356 check_complex_matrix_symmetry(
357 &smat.view(),
358 complex_symmetric,
359 thresh_offdiag,
360 "Overlap",
361 "S",
362 )?;
363 (
364 (hmat.to_owned() + hmat.map(|v| v.conj()).t().to_owned())
365 .map(|v| v / (Complex::<T>::one() + Complex::<T>::one())),
366 (smat.to_owned() + smat.map(|v| v.conj()).t().to_owned())
367 .map(|v| v / (Complex::<T>::one() + Complex::<T>::one())),
368 )
369 };
370
371 let xmat_res = smat.view().calc_canonical_orthogonal_matrix(
374 complex_symmetric,
375 false,
376 thresh_offdiag,
377 thresh_zeroov,
378 &eigenvalue_comparison_mode
379 )?;
380
381 let xmat = xmat_res.xmat();
382 let xmat_d = xmat_res.xmat_d();
383
384 let hmat_t = xmat_d.dot(&hmat).dot(&xmat);
385 let smat_t = xmat_d.dot(&smat).dot(&xmat);
386
387 let (hmat_t_sym, smat_t_sym): (Array2<Complex<T>>, Array2<Complex<T>>) =
389 if complex_symmetric {
390 check_complex_matrix_symmetry(
392 &hmat_t.view(),
393 complex_symmetric,
394 thresh_offdiag,
395 "Transformed Hamiltonian",
396 "H~",
397 )?;
398 check_complex_matrix_symmetry(
399 &smat_t.view(),
400 complex_symmetric,
401 thresh_offdiag,
402 "Transformed Overlap",
403 "S~",
404 )?;
405 let hmat_t_s = (hmat_t.to_owned() + hmat_t.t().to_owned())
406 .map(|v| v / (Complex::<T>::one() + Complex::<T>::one()));
407 let smat_t_s = (smat_t.to_owned() + smat_t.t().to_owned())
408 .map(|v| v / (Complex::<T>::one() + Complex::<T>::one()));
409 (hmat_t_s, smat_t_s)
410 } else {
411 check_complex_matrix_symmetry(
413 &hmat_t.view(),
414 complex_symmetric,
415 thresh_offdiag,
416 "Transformed Hamiltonian",
417 "H~",
418 )?;
419 check_complex_matrix_symmetry(
420 &smat_t.view(),
421 complex_symmetric,
422 thresh_offdiag,
423 "Transformed Overlap",
424 "S~",
425 )?;
426 let hmat_t_s = (hmat_t.to_owned() + hmat_t.map(|v| v.conj()).t().to_owned())
427 .map(|v| v / (Complex::<T>::one() + Complex::<T>::one()));
428 let smat_t_s = (smat_t.to_owned() + smat_t.map(|v| v.conj()).t().to_owned())
429 .map(|v| v / (Complex::<T>::one() + Complex::<T>::one()));
430 (hmat_t_s, smat_t_s)
431 };
432 let smat_t_sym_d = smat_t_sym.map(|v| v.conj()).t().to_owned();
433 log::debug!("Complex-symmetric? {complex_symmetric}");
434 log::debug!("Canonical orthogonalisation X matrix:\n {xmat:+.8e}");
435 log::debug!("Canonical-orthogonalised NOCI Hamiltonian matrix H~:\n {hmat_t_sym:+.8e}");
436 log::debug!("Canonical-orthogonalised NOCI overlap matrix S~:\n {smat_t_sym:+.8e}");
437
438 let max_diff = (&smat_t_sym_d.dot(&smat_t_sym) - &Array2::<T>::eye(smat_t_sym.nrows()))
440 .iter()
441 .map(|x| ComplexFloat::abs(*x))
442 .max_by(|x, y| {
443 x.partial_cmp(y)
444 .expect("Unable to compare two `abs` values.")
445 })
446 .ok_or_else(|| {
447 format_err!("Unable to determine the maximum element of the |S^†.S - I| matrix.")
448 })?;
449 ensure!(
450 max_diff <= thresh_offdiag,
451 "The S^†.S matrix is not the identity matrix. S is therefore not Hermitian."
452 );
453 let smat_t_sym_d_hmat_t_sym = smat_t_sym_d.dot(&hmat_t_sym);
454 log::debug!(
455 "Hamiltonian matrix for diagonalisation (S~)^†.(H~):\n {smat_t_sym_d_hmat_t_sym:+.8e}"
456 );
457
458 let (eigvals_t, eigvecs_t) = smat_t_sym_d_hmat_t_sym.eig()?;
459
460 let (eigvals_t_sorted, eigvecs_t_sorted) = sort_eigenvalues_eigenvectors(
462 &eigvals_t.view(),
463 &eigvecs_t.view(),
464 &eigenvalue_comparison_mode,
465 );
466 log::debug!("Sorted eigenvalues of (S~)^†.(H~):");
467 for (i, eigval) in eigvals_t_sorted.iter().enumerate() {
468 log::debug!(" {i}: {eigval:+.8e}");
469 }
470 log::debug!("");
471 log::debug!("Sorted eigenvectors of (S~)^†.(H~):\n {eigvecs_t_sorted:+.8e}");
472 log::debug!("");
473
474 let eigvecs_sorted = xmat.dot(&eigvecs_t_sorted);
483
484 let eigvecs_sorted_normalised = normalise_eigenvectors_complex(
486 &eigvecs_sorted.view(),
487 &smat.view(),
488 complex_symmetric,
489 None,
490 )?;
491
492 let eigvecs_sorted_normalised_regularised =
494 regularise_eigenvectors(&eigvecs_sorted_normalised.view(), thresh_offdiag);
495
496 Ok(GeneralisedEigenvalueResult {
497 eigenvalues: eigvals_t_sorted,
498 eigenvectors: eigvecs_sorted_normalised_regularised,
499 })
500 }
501
502 fn solve_generalised_eigenvalue_problem_with_ggev(
503 &self,
504 complex_symmetric: bool,
505 thresh_offdiag: T,
506 thresh_zeroov: T,
507 eigenvalue_comparison_mode: EigenvalueComparisonMode,
508 ) -> Result<GeneralisedEigenvalueResult<Self::NumType>, anyhow::Error> {
509 let (hmat, smat) = (self.0.to_owned(), self.1.to_owned());
510
511 check_complex_matrix_symmetry(
512 &hmat.view(),
513 complex_symmetric,
514 thresh_offdiag,
515 "Hamiltonian",
516 "H",
517 )?;
518 check_complex_matrix_symmetry(
519 &smat.view(),
520 complex_symmetric,
521 thresh_offdiag,
522 "Overlap",
523 "S",
524 )?;
525
526 let (geneigvals, eigvecs) =
527 (hmat.clone(), smat.clone()).eig_generalized(Some(thresh_zeroov))?;
528
529 let mut indices = (0..geneigvals.len())
531 .filter(|i| matches!(geneigvals[*i], GeneralizedEigenvalue::Finite(_, _)))
532 .collect_vec();
533
534 match eigenvalue_comparison_mode {
535 EigenvalueComparisonMode::Modulus => {
536 indices.sort_by(|i, j| {
537 if let (
538 GeneralizedEigenvalue::Finite(e_i, _),
539 GeneralizedEigenvalue::Finite(e_j, _),
540 ) = (&geneigvals[*i], &geneigvals[*j])
541 {
542 ComplexFloat::abs(*e_i)
543 .partial_cmp(&ComplexFloat::abs(*e_j))
544 .unwrap()
545 } else {
546 panic!("Unable to compare some eigenvalues.")
547 }
548 });
549 }
550 EigenvalueComparisonMode::Real => {
551 indices.sort_by(|i, j| {
552 if let (
553 GeneralizedEigenvalue::Finite(e_i, _),
554 GeneralizedEigenvalue::Finite(e_j, _),
555 ) = (&geneigvals[*i], &geneigvals[*j])
556 {
557 e_i.re().partial_cmp(&e_j.re()).unwrap()
558 } else {
559 panic!("Unable to compare some eigenvalues.")
560 }
561 });
562 }
563 }
564
565 let eigvals_sorted = geneigvals.select(Axis(0), &indices).map(|gv| {
566 if let GeneralizedEigenvalue::Finite(v, _) = gv {
567 *v
568 } else {
569 panic!("Unexpected indeterminate eigenvalue.")
570 }
571 });
572 let eigvecs_sorted = eigvecs.select(Axis(1), &indices);
573
574 let eigvecs_sorted_normalised = normalise_eigenvectors_complex(
576 &eigvecs_sorted.view(),
577 &smat.view(),
578 complex_symmetric,
579 Some(thresh_offdiag),
580 )?;
581
582 let eigvecs_sorted_normalised_regularised =
584 regularise_eigenvectors(&eigvecs_sorted_normalised.view(), thresh_offdiag);
585
586 Ok(GeneralisedEigenvalueResult {
587 eigenvalues: eigvals_sorted,
588 eigenvectors: eigvecs_sorted_normalised_regularised,
589 })
590 }
591}